Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
eukreign committed Mar 16, 2022
1 parent 58a6061 commit 4468433
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 87 deletions.
2 changes: 1 addition & 1 deletion lbry/extras/daemon/daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -1885,7 +1885,7 @@ async def jsonrpc_account_deposit(
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())
Expand Down
112 changes: 32 additions & 80 deletions lbry/wallet/script.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -387,31 +389,33 @@ def redeem_pubkey_hash(cls, signature, pubkey):
})

@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):

Expand Down Expand Up @@ -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,
Expand All @@ -507,8 +496,6 @@ class OutputScript(Script):
SUPPORT_CLAIM_DATA_SCRIPT,
UPDATE_CLAIM_PUBKEY,
UPDATE_CLAIM_SCRIPT,
SELL_CLAIM, SELL_SCRIPT,
BUY_CLAIM,
]

@classmethod
Expand Down Expand Up @@ -568,30 +555,6 @@ def pay_support_data_pubkey_hash(
'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')
Expand Down Expand Up @@ -620,17 +583,6 @@ def is_support_claim(self):
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))
15 changes: 9 additions & 6 deletions lbry/wallet/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,9 @@ def spend(cls, txo: 'Output') -> 'Input':
@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
Expand Down Expand Up @@ -867,11 +869,12 @@ async def sign(self, funding_accounts: Iterable['Account'], extra_keys: dict = N
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'] = \
Expand Down
40 changes: 40 additions & 0 deletions tests/unit/wallet/test_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,46 @@ def test_claim_transaction(self):
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):

Expand Down

0 comments on commit 4468433

Please sign in to comment.