From ae79314869b02f5074625ce807c7044357546ccc Mon Sep 17 00:00:00 2001 From: Lex Berezhny Date: Wed, 16 Mar 2022 00:06:31 -0400 Subject: [PATCH] wip --- lbry/extras/daemon/daemon.py | 2 +- lbry/wallet/script.py | 112 ++++++++------------------ lbry/wallet/transaction.py | 15 ++-- tests/unit/wallet/test_transaction.py | 40 +++++++++ 4 files changed, 82 insertions(+), 87 deletions(-) diff --git a/lbry/extras/daemon/daemon.py b/lbry/extras/daemon/daemon.py index 47c62afde..6881889bc 100644 --- a/lbry/extras/daemon/daemon.py +++ b/lbry/extras/daemon/daemon.py @@ -1907,7 +1907,7 @@ class Daemon(metaclass=JSONRPCServerType): pk = PrivateKey.from_bytes( account.ledger, Base58.decode_check(private_key)[1:-1] ) - tx.sign([account], {pk.address: pk}) + await tx.sign([account], {pk.address: pk}) if not preview: await self.broadcast_or_release(tx, blocking) self.component_manager.loop.create_task(self.analytics_manager.send_credits_sent()) diff --git a/lbry/wallet/script.py b/lbry/wallet/script.py index dcf3cf10a..cfcf1e5da 100644 --- a/lbry/wallet/script.py +++ b/lbry/wallet/script.py @@ -358,25 +358,27 @@ class InputScript(Script): REDEEM_PUBKEY_HASH = Template('pubkey_hash', ( PUSH_SINGLE('signature'), PUSH_SINGLE('pubkey') )) - REDEEM_SCRIPT = Template('script', ( + MULTI_SIG_SCRIPT = Template('multi_sig', ( SMALL_INTEGER('signatures_count'), PUSH_MANY('pubkeys'), SMALL_INTEGER('pubkeys_count'), OP_CHECKMULTISIG )) - REDEEM_SCRIPT_HASH = Template('script_hash', ( - OP_0, PUSH_MANY('signatures'), PUSH_SUBSCRIPT('script', REDEEM_SCRIPT) + REDEEM_SCRIPT_HASH_MULTI_SIG = Template('script_hash+mult_sig', ( + PUSH_SINGLE('signature'), PUSH_SINGLE('pubkey'), PUSH_SUBSCRIPT('script', MULTI_SIG_SCRIPT) )) - REDEEM_TIME_LOCK = Template('timelock', ( - SMALL_INTEGER('height'), OP_CHECKLOCKTIMEVERIFY, OP_DROP, + TIME_LOCK_SCRIPT = Template('timelock', ( + PUSH_INTEGER('height'), OP_CHECKLOCKTIMEVERIFY, OP_DROP, # rest is identical to OutputScript.PAY_PUBKEY_HASH: OP_DUP, OP_HASH160, PUSH_SINGLE('pubkey_hash'), OP_EQUALVERIFY, OP_CHECKSIG )) + REDEEM_SCRIPT_HASH_TIME_LOCK = Template('script_hash+timelock', ( + PUSH_SINGLE('signature'), PUSH_SINGLE('pubkey'), PUSH_SUBSCRIPT('script', TIME_LOCK_SCRIPT) + )) templates = [ REDEEM_PUBKEY, REDEEM_PUBKEY_HASH, - REDEEM_SCRIPT_HASH, - REDEEM_SCRIPT, - REDEEM_TIME_LOCK + REDEEM_SCRIPT_HASH_TIME_LOCK, + REDEEM_SCRIPT_HASH_MULTI_SIG, ] @classmethod @@ -387,31 +389,33 @@ class InputScript(Script): }) @classmethod - def redeem_script_hash(cls, signatures, pubkeys): - return cls(template=cls.REDEEM_SCRIPT_HASH, values={ + def redeem_mult_sig_script_hash(cls, signatures, pubkeys): + return cls(template=cls.REDEEM_SCRIPT_HASH_MULTI_SIG, values={ 'signatures': signatures, - 'script': cls.redeem_script(signatures, pubkeys) + 'script': cls(template=cls.MULTI_SIG_SCRIPT, values={ + 'signatures_count': len(signatures), + 'pubkeys': pubkeys, + 'pubkeys_count': len(pubkeys) + }) }) @classmethod - def redeem_script(cls, signatures, pubkeys): - return cls(template=cls.REDEEM_SCRIPT, values={ - 'signatures_count': len(signatures), - 'pubkeys': pubkeys, - 'pubkeys_count': len(pubkeys) + def redeem_time_lock_script_hash(cls, signature, pubkey, height=None, pubkey_hash=None, script_source=None): + if height and pubkey_hash: + script = cls(template=cls.TIME_LOCK_SCRIPT, values={ + 'height': height, + 'pubkey_hash': pubkey_hash + }) + elif script_source: + script = cls(source=script_source, template=cls.TIME_LOCK_SCRIPT) + else: + raise ValueError("script_source or both height and pubkey_hash are required.") + return cls(template=cls.REDEEM_SCRIPT_HASH_TIME_LOCK, values={ + 'signature': signature, + 'pubkey': pubkey, + 'script': script }) - @classmethod - def redeem_time_lock(cls, height, pubkey_hash): - return cls(template=cls.REDEEM_SCRIPT, values={ - 'height': height, - 'pubkey_hash': pubkey_hash - }) - - @classmethod - def redeem_time_lock_from_script(cls, script: bytes): - return cls.from_source_with_template(script, cls.REDEEM_SCRIPT) - class OutputScript(Script): @@ -478,21 +482,6 @@ class OutputScript(Script): UPDATE_CLAIM_OPCODES + PAY_SCRIPT_HASH.opcodes )) - SELL_SCRIPT = Template('sell_script', ( - OP_VERIFY, OP_DROP, OP_DROP, OP_DROP, PUSH_INTEGER('price'), OP_PRICECHECK - )) - SELL_CLAIM = Template('sell_claim+pay_script_hash', ( - OP_SELL_CLAIM, PUSH_SINGLE('claim_id'), PUSH_SUBSCRIPT('sell_script', SELL_SCRIPT), - PUSH_SUBSCRIPT('receive_script', InputScript.REDEEM_SCRIPT), OP_2DROP, OP_2DROP - ) + PAY_SCRIPT_HASH.opcodes) - - BUY_CLAIM = Template('buy_claim+pay_script_hash', ( - OP_BUY_CLAIM, PUSH_SINGLE('sell_id'), - PUSH_SINGLE('claim_id'), PUSH_SINGLE('claim_version'), - PUSH_SINGLE('owner_pubkey_hash'), PUSH_SINGLE('negotiation_signature'), - OP_2DROP, OP_2DROP, OP_2DROP, - ) + PAY_SCRIPT_HASH.opcodes) - templates = [ PAY_PUBKEY_FULL, PAY_PUBKEY_HASH, @@ -507,8 +496,6 @@ class OutputScript(Script): SUPPORT_CLAIM_DATA_SCRIPT, UPDATE_CLAIM_PUBKEY, UPDATE_CLAIM_SCRIPT, - SELL_CLAIM, SELL_SCRIPT, - BUY_CLAIM, ] @classmethod @@ -568,30 +555,6 @@ class OutputScript(Script): 'pubkey_hash': pubkey_hash }) - @classmethod - def sell_script(cls, price): - return cls(template=cls.SELL_SCRIPT, values={ - 'price': price, - }) - - @classmethod - def sell_claim(cls, claim_id, price, signatures, pubkeys): - return cls(template=cls.SELL_CLAIM, values={ - 'claim_id': claim_id, - 'sell_script': OutputScript.sell_script(price), - 'receive_script': InputScript.redeem_script(signatures, pubkeys) - }) - - @classmethod - def buy_claim(cls, sell_id, claim_id, claim_version, owner_pubkey_hash, negotiation_signature): - return cls(template=cls.BUY_CLAIM, values={ - 'sell_id': sell_id, - 'claim_id': claim_id, - 'claim_version': claim_version, - 'owner_pubkey_hash': owner_pubkey_hash, - 'negotiation_signature': negotiation_signature, - }) - @property def is_pay_pubkey_hash(self): return self.template.name.endswith('pay_pubkey_hash') @@ -620,17 +583,6 @@ class OutputScript(Script): def is_support_claim_data(self): return self.template.name.startswith('support_claim+data+') - @property - def is_sell_claim(self): - return self.template.name.startswith('sell_claim+') - - @property - def is_buy_claim(self): - return self.template.name.startswith('buy_claim+') - @property def is_claim_involved(self): - return any(( - self.is_claim_name, self.is_support_claim, self.is_update_claim, - self.is_sell_claim, self.is_buy_claim - )) + return any((self.is_claim_name, self.is_support_claim, self.is_update_claim)) diff --git a/lbry/wallet/transaction.py b/lbry/wallet/transaction.py index f504c3eac..fdc225a06 100644 --- a/lbry/wallet/transaction.py +++ b/lbry/wallet/transaction.py @@ -148,7 +148,9 @@ class Input(InputOutput): @classmethod def spend_time_lock(cls, txo: 'Output', script_source: bytes) -> 'Input': """ Create an input to spend time lock script.""" - script = InputScript.redeem_time_lock_from_script(script_source) + script = InputScript.redeem_time_lock_script_hash( + cls.NULL_SIGNATURE, cls.NULL_PUBLIC_KEY, script_source=script_source + ) return cls(txo.ref, script) @property @@ -867,11 +869,12 @@ class Transaction: assert txi.script is not None assert txi.txo_ref.txo is not None txo_script = txi.txo_ref.txo.script - if txo_script.is_pay_pubkey_hash: - address = ledger.hash160_to_address(txo_script.values['pubkey_hash']) - private_key = await ledger.get_private_key_for_address(wallet, address) - if private_key is None and extra_keys: - private_key = extra_keys.get(address) + if txo_script.is_pay_pubkey_hash or txo_script.is_pay_script_hash: + if 'pubkey_hash' in txo_script.values: + address = ledger.hash160_to_address(txo_script.values.get('pubkey_hash', '')) + private_key = await ledger.get_private_key_for_address(wallet, address) + else: + private_key = next(iter(extra_keys.values())) assert private_key is not None, 'Cannot find private key for signing output.' tx = self._serialize_for_signature(i) txi.script.values['signature'] = \ diff --git a/tests/unit/wallet/test_transaction.py b/tests/unit/wallet/test_transaction.py index 5468b85b6..509c09ba7 100644 --- a/tests/unit/wallet/test_transaction.py +++ b/tests/unit/wallet/test_transaction.py @@ -262,6 +262,46 @@ class TestTransactionSerialization(unittest.TestCase): tx._reset() self.assertEqual(tx.raw, raw) + def test_redeem_scripthash_transaction(self): + raw = unhexlify( + "0200000001409223c2405238fdc516d4f2e8aa57637ce52d3b1ac42b26f1accdcda9697e79010000008a4" + "730440220033d5286f161da717d9d1bc3c2bc28da7636b38fc0c6aefb1e0864212f05282c02205df3ce13" + "5e79c76d44489212f77ad4e3a838562e601e6377704fa6206a6ae44f012102261773e7eebe9da80a5653d" + "865cc600362f8e7b2b598661139dd902b5b01ea101f03aaf30ab17576a914a3328f18ac1892a6667f713d" + "7020ff3437d973c888acfeffffff0180ed3e17000000001976a914353352b7ce1e3c9c05ffcd6ae97609d" + "e2999744488accdf50a00" + ) + tx = Transaction(raw) + self.assertEqual(tx.id, 'e466881128889d1cc4110627753051c22e72a81d11229a1a1337da06940bebcf') + self.assertEqual(tx.version, 2) + self.assertEqual(tx.locktime, 718285,) + self.assertEqual(len(tx.inputs), 1) + self.assertEqual(len(tx.outputs), 1) + + txin = tx.inputs[0] + self.assertEqual( + txin.txo_ref.id, + '797e69a9cdcdacf1262bc41a3b2de57c6357aae8f2d416c5fd385240c2239240:1' + ) + self.assertEqual(txin.txo_ref.position, 1) + self.assertEqual(txin.sequence, 4294967294) + self.assertIsNone(txin.coinbase) + self.assertEqual(txin.script.template.name, 'script_hash+timelock') + self.assertEqual( + hexlify(txin.script.values['signature']), + b'30440220033d5286f161da717d9d1bc3c2bc28da7636b38fc0c6aefb1e0864212f' + b'05282c02205df3ce135e79c76d44489212f77ad4e3a838562e601e6377704fa620' + b'6a6ae44f01' + ) + self.assertEqual( + hexlify(txin.script.values['pubkey']), + b'02261773e7eebe9da80a5653d865cc600362f8e7b2b598661139dd902b5b01ea10' + ) + script = txin.script.values['script'] + self.assertEqual(script.template.name, 'timelock') + self.assertEqual(script.values['height'], 717738) + self.assertEqual(hexlify(script.values['pubkey_hash']), b'a3328f18ac1892a6667f713d7020ff3437d973c8') + class TestTransactionSigning(AsyncioTestCase):