work in progress

This commit is contained in:
Lex Berezhny 2021-12-10 10:46:19 -05:00
parent e4cc4521d9
commit 8216f4a873
6 changed files with 87 additions and 2 deletions

View file

@ -126,6 +126,10 @@ class PubKey(_KeyBase):
self.pubkey_bytes self.pubkey_bytes
) )
def verify(self, signature, data):
""" Produce a signature for piece of data by double hashing it and signing the hash. """
return self.verifying_key.verify(signature, data, hasher=double_sha256)
class PrivateKey(_KeyBase): class PrivateKey(_KeyBase):
"""A BIP32 private key.""" """A BIP32 private key."""

View file

@ -1244,7 +1244,7 @@ class Database(SQLiteMixin):
async def is_channel_key_used(self, wallet, address): async def is_channel_key_used(self, wallet, address):
channels = await self.get_txos(wallet, txo_type=TXO_TYPES['channel']) channels = await self.get_txos(wallet, txo_type=TXO_TYPES['channel'])
for channel in channels: for channel in channels:
if channel.private_key.address() == address: if channel.private_key is not None and channel.private_key.address() == address:
return True return True
return False return False

View file

@ -470,6 +470,7 @@ class Ledger(metaclass=LedgerRegistry):
for address_manager in account.address_managers.values(): for address_manager in account.address_managers.values():
await self.subscribe_addresses(address_manager, await address_manager.get_addresses()) await self.subscribe_addresses(address_manager, await address_manager.get_addresses())
await account.ensure_address_gap() await account.ensure_address_gap()
await account.deterministic_channel_keys.ensure_cache_primed()
async def unsubscribe_account(self, account: Account): async def unsubscribe_account(self, account: Account):
for address in await account.get_addresses(): for address in await account.get_addresses():

View file

@ -28,6 +28,7 @@ from .constants import COIN, NULL_HASH32
from .bcd_data_stream import BCDataStream from .bcd_data_stream import BCDataStream
from .hash import TXRef, TXRefImmutable from .hash import TXRef, TXRefImmutable
from .util import ReadOnlyList from .util import ReadOnlyList
from .bip32 import PubKey
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from lbry.wallet.account import Account from lbry.wallet.account import Account

View file

@ -1,5 +1,9 @@
from binascii import unhexlify
from lbry.testcase import CommandTestCase from lbry.testcase import CommandTestCase
from lbry.wallet.dewies import dewies_to_lbc from lbry.wallet.dewies import dewies_to_lbc
from lbry.wallet.account import DeterministicChannelKeyManager
from lbry.wallet.transaction import Transaction
def extract(d, keys): def extract(d, keys):
@ -175,8 +179,17 @@ class AccountManagement(CommandTestCase):
with self.assertRaisesRegex(Exception, f"'{bad_address}' is not a valid address"): with self.assertRaisesRegex(Exception, f"'{bad_address}' is not a valid address"):
await self.daemon.jsonrpc_account_send('0.1', addresses=[bad_address]) await self.daemon.jsonrpc_account_send('0.1', addresses=[bad_address])
async def test_backwards_compatibility(self):
pk = {
'mpAt7RQJUWe3RWPyyYQ9cinQoPH9HomPdh':
'-----BEGIN EC PRIVATE KEY-----\nMHQCAQEEIMrKg13+6mj5zdqN2wCx24GgYD8PUiYVzGewgOvu24SfoA'
'cGBSuBBAAK\noUQDQgAE1/oT/Y5X86C4eOqvPReRRNJd2+Sj5EQKZh9RtBNMahPJyYZ4/4QRky5g\n/ZfXuvA+'
'pn68whCXIwz7IkE0iq21Xg==\n-----END EC PRIVATE KEY-----\n'
}
async def test_deterministic_channel_keys(self): async def test_deterministic_channel_keys(self):
seed = self.account.seed seed = self.account.seed
keys = self.account.deterministic_channel_keys
# create two channels and make sure they have different keys # create two channels and make sure they have different keys
channel1a = await self.channel_create('@foo1') channel1a = await self.channel_create('@foo1')
@ -202,11 +215,41 @@ class AccountManagement(CommandTestCase):
channel2b.private_key.public_key.address channel2b.private_key.public_key.address
) )
# repeatedly calling next channel key returns the same key when not used
current_known = keys.last_known
next_key = await keys.generate_next_key()
self.assertEqual(current_known, keys.last_known)
self.assertEqual(next_key.address(), (await keys.generate_next_key()).address())
# again, should be idempotent
next_key = await keys.generate_next_key()
self.assertEqual(current_known, keys.last_known)
self.assertEqual(next_key.address(), (await keys.generate_next_key()).address())
# create third channel while both daemons running, second daemon should pick it up # create third channel while both daemons running, second daemon should pick it up
channel3a = await self.channel_create('@foo3') channel3a = await self.channel_create('@foo3')
self.assertEqual(current_known+1, keys.last_known)
self.assertNotEqual(next_key.address(), (await keys.generate_next_key()).address())
channel3b, = (await self.daemon2.jsonrpc_channel_list(name='@foo3'))['items'] channel3b, = (await self.daemon2.jsonrpc_channel_list(name='@foo3'))['items']
self.assertTrue(channel3b.has_private_key) self.assertTrue(channel3b.has_private_key)
self.assertEqual( self.assertEqual(
channel3a['outputs'][0]['value']['public_key_id'], channel3a['outputs'][0]['value']['public_key_id'],
channel3b.private_key.public_key.address channel3b.private_key.public_key.address
) )
# channel key cache re-populated after simulated restart
# reset cache
self.account.deterministic_channel_keys = DeterministicChannelKeyManager(self.account)
channel3c, channel2c, channel1c = (await self.daemon.jsonrpc_channel_list())['items']
self.assertFalse(channel1c.has_private_key)
self.assertFalse(channel2c.has_private_key)
self.assertFalse(channel3c.has_private_key)
# repopulate cache
await self.account.deterministic_channel_keys.ensure_cache_primed()
self.assertEqual(self.account.deterministic_channel_keys.last_known, keys.last_known)
channel3c, channel2c, channel1c = (await self.daemon.jsonrpc_channel_list())['items']
self.assertTrue(channel1c.has_private_key)
self.assertTrue(channel2c.has_private_key)
self.assertTrue(channel3c.has_private_key)

View file

@ -2,10 +2,13 @@ from binascii import unhexlify
from lbry.testcase import AsyncioTestCase from lbry.testcase import AsyncioTestCase
from lbry.wallet.constants import CENT, NULL_HASH32 from lbry.wallet.constants import CENT, NULL_HASH32
from lbry.wallet.bip32 import PrivateKey
from lbry.wallet.mnemonic import Mnemonic
from lbry.wallet import Ledger, Database, Headers, Transaction, Input, Output from lbry.wallet import Ledger, Database, Headers, Transaction, Input, Output
from lbry.schema.claim import Claim from lbry.schema.claim import Claim
from lbry.crypto.hash import sha256 from lbry.crypto.hash import sha256
def get_output(amount=CENT, pubkey_hash=NULL_HASH32): def get_output(amount=CENT, pubkey_hash=NULL_HASH32):
return Transaction() \ return Transaction() \
.add_outputs([Output.pay_pubkey_hash(amount, pubkey_hash)]) \ .add_outputs([Output.pay_pubkey_hash(amount, pubkey_hash)]) \
@ -22,7 +25,9 @@ def get_tx():
async def get_channel(claim_name='@foo'): async def get_channel(claim_name='@foo'):
channel_txo = Output.pay_claim_name_pubkey_hash(CENT, claim_name, Claim(), b'abc') channel_txo = Output.pay_claim_name_pubkey_hash(CENT, claim_name, Claim(), b'abc')
await channel_txo.generate_channel_private_key() channel_txo.set_channel_private_key(PrivateKey.from_seed(
Ledger, Mnemonic.mnemonic_to_seed(Mnemonic().make_seed(), '')
))
get_tx().add_outputs([channel_txo]) get_tx().add_outputs([channel_txo])
return channel_txo return channel_txo
@ -114,6 +119,37 @@ class TestValidatingOldSignatures(AsyncioTestCase):
self.assertTrue(stream.is_signed_by(channel, ledger)) self.assertTrue(stream.is_signed_by(channel, ledger))
def test_claim_signed_using_ecdsa_validates_with_coincurve(self):
channel_tx = Transaction(unhexlify(
"0100000001b91d829283c0d80cb8113d5f36b6da3dfe9df3e783f158bfb3fd1b2b178d7fc9010000006b48"
"3045022100f4e2b4ee38388c3d3a62f4b12fdd413f6f140168e85884bbeb33a3f2d3159ef502201721200f"
"4a4f3b87484d4f47c9054e31cd3ba451dd3886a7f9f854893e7c8cf90121023f9e906e0c120f3bf74feb40"
"f01ddeafbeb1856d91938c3bef25bed06767247cffffffff0200e1f5050000000081b505406368616e4c5d"
"00125a0a583056301006072a8648ce3d020106052b8104000a03420004d7fa13fd8e57f3a0b878eaaf3d17"
"9144d25ddbe4a3e4440a661f51b4134c6a13c9c98678ff8411932e60fd97d7baf03ea67ebcc21097230cfb"
"2241348aadb55e6d7576a9149c6d700f89c77f0e8c650ba05656f8f2392782d388acf47c95350000000019"
"76a914d9502233e0e1fc76e13e36c546f704c3124d5eaa88ac00000000"
))
channel = channel_tx.outputs[0]
stream_tx = Transaction(unhexlify(
"010000000116a1d90763f2e3a2348c7fb438a23f232b15e3ffe3f058c3b2ab52c8bed8dcb5010000006b48"
"30450221008f38561b3a16944c63b4f4f1562f1efe1b2060f31d249e234003ee5e3461756f02205773c99e"
"83c968728e4f2433a13871c6ad23f6c10368ac52fa62a09f3f7ef5fd012102597f39845b98e2415b777aa0"
"3849d346d287af7970deb05f11214b3418ae9d82ffffffff0200e1f50500000000fd0c01b505636c61696d"
"4ce8012e6e40fa5fee1b915af3b55131dcbcebee34ab9148292b084ce3741f2e0db49783f3d854ac885f2b"
"6304a76ef7048046e338dd414ba4c64e8468651768ffaaf550c8560637ac8c477ea481ac2a9264097240f4"
"ab0a90010a8d010a3056bf5dbae43f77a63d075b0f2ae9c7c3e3098db93779c7f9840da0f4db9c2f8c8454"
"f4edd1373e2b64ee2e68350d916e120b746d706c69647879363171180322186170706c69636174696f6e2f"
"6f637465742d73747265616d3230f293f5acf4310562d4a41f6620167fe6d83761a98d36738908ce5c8776"
"1642710e55352a396276a42eda92ff5856f46f6d7576a91434bd3dc4c45cc0635eb2ad5da658727e5442ca"
"0f88ace82f902f000000001976a91427b27c89eaebf68d063c107241584c07e5a6ccc688ac00000000"
))
stream = stream_tx.outputs[0]
ledger = Ledger({'db': Database(':memory:'), 'headers': Headers(':memory:')})
self.assertTrue(stream.is_signed_by(channel, ledger))
class TestValidateSignContent(AsyncioTestCase): class TestValidateSignContent(AsyncioTestCase):