From f741b00768d032c20b0a7f769b60fa32decd7dbb Mon Sep 17 00:00:00 2001 From: Lex Berezhny <lex@damoti.com> Date: Wed, 13 Oct 2021 10:56:10 -0400 Subject: [PATCH] progress on deterministic channel keys --- lbry/extras/daemon/daemon.py | 5 +++-- lbry/wallet/account.py | 22 +++++++++++++++++++ lbry/wallet/database.py | 6 +++++ lbry/wallet/transaction.py | 10 ++++----- .../blockchain/test_account_commands.py | 5 +++++ 5 files changed, 40 insertions(+), 8 deletions(-) diff --git a/lbry/extras/daemon/daemon.py b/lbry/extras/daemon/daemon.py index 1cd731dcb..620ad36d8 100644 --- a/lbry/extras/daemon/daemon.py +++ b/lbry/extras/daemon/daemon.py @@ -2704,12 +2704,13 @@ class Daemon(metaclass=JSONRPCServerType): name, claim, amount, claim_address, funding_accounts, funding_accounts[0] ) txo = tx.outputs[0] - await txo.generate_channel_private_key() + txo.set_channel_private_key( + await funding_accounts[0].generate_channel_private_key() + ) await tx.sign(funding_accounts) if not preview: - account.add_channel_private_key(txo.private_key) wallet.save() await self.broadcast_or_release(tx, blocking) self.component_manager.loop.create_task(self.storage.save_claims([self._old_get_temp_claim_info( diff --git a/lbry/wallet/account.py b/lbry/wallet/account.py index 05989c324..64d2d81af 100644 --- a/lbry/wallet/account.py +++ b/lbry/wallet/account.py @@ -34,6 +34,22 @@ def validate_claim_id(claim_id): raise Exception("Claim id is not hex encoded") +class DeterministicChannelKeyManager: + + def __init__(self, account): + self.account = account + self.public_key = account.public_key.child(2) + self.private_key = account.private_key.child(2) if account.private_key else None + + def generate_next_key(self): + db = self.account.ledger.db + i = 0 + while True: + next_key = self.private_key.child(i) + if not await db.is_channel_key_used(self.account, next_key.address): + return next_key + + class AddressManager: name: str @@ -252,6 +268,7 @@ class Account: self.receiving, self.change = self.address_generator.from_dict(self, address_generator) self.address_managers = {am.chain_number: am for am in (self.receiving, self.change)} self.channel_keys = channel_keys + self.deterministic_channel_keys = DeterministicChannelKeyManager(self) ledger.add_account(self) wallet.add_account(self) @@ -520,6 +537,11 @@ class Account: return tx + async def generate_channel_private_key(self): + key = self.deterministic_channel_keys.generate_next_key() + self.add_channel_private_key(key) + return key + def add_channel_private_key(self, private_key): public_key_bytes = private_key.get_verifying_key().to_der() channel_pubkey_hash = self.ledger.public_key_to_address(public_key_bytes) diff --git a/lbry/wallet/database.py b/lbry/wallet/database.py index 4507bd7dd..086e3dec8 100644 --- a/lbry/wallet/database.py +++ b/lbry/wallet/database.py @@ -1241,6 +1241,12 @@ class Database(SQLiteMixin): async def set_address_history(self, address, history): await self._set_address_history(address, history) + async def is_channel_key_used(self, account, address): + for channel in await self.get_channels(accounts=[account]): + if channel.private_key.address == address: + return True + return False + @staticmethod def constrain_purchases(constraints): accounts = constraints.pop('accounts', None) diff --git a/lbry/wallet/transaction.py b/lbry/wallet/transaction.py index fd8ad1961..ced71f4a7 100644 --- a/lbry/wallet/transaction.py +++ b/lbry/wallet/transaction.py @@ -469,13 +469,11 @@ class Output(InputOutput): self.channel = None self.signable.clear_signature() - async def generate_channel_private_key(self): - self.private_key = await asyncio.get_event_loop().run_in_executor( - None, ecdsa.SigningKey.generate, ecdsa.SECP256k1, None, hashlib.sha256 - ) - self.claim.channel.public_key_bytes = self.private_key.get_verifying_key().to_der() + def set_channel_private_key(self, private_key): + self.private_key = private_key + self.claim.channel.public_key_bytes = private_key.get_verifying_key().to_der() self.script.generate() - return self.private_key + return private_key def is_channel_private_key(self, private_key): return self.claim.channel.public_key_bytes == private_key.get_verifying_key().to_der() diff --git a/tests/integration/blockchain/test_account_commands.py b/tests/integration/blockchain/test_account_commands.py index 09d57a100..5a5763cce 100644 --- a/tests/integration/blockchain/test_account_commands.py +++ b/tests/integration/blockchain/test_account_commands.py @@ -174,3 +174,8 @@ class AccountManagement(CommandTestCase): bad_address = address[0:20] + '9999999' + address[27:] with self.assertRaisesRegex(Exception, f"'{bad_address}' is not a valid address"): await self.daemon.jsonrpc_account_send('0.1', addresses=[bad_address]) + + async def test_deterministic_channel_keys(self): + seed = self.account.seed + await self.channel_create('@foo1') + self.daemon2 = await self.add_daemon(seed=seed)