diff --git a/src/apps/wallet/sign_tx/addresses.py b/src/apps/wallet/sign_tx/addresses.py index 9ce4ff125..3ae006931 100644 --- a/src/apps/wallet/sign_tx/addresses.py +++ b/src/apps/wallet/sign_tx/addresses.py @@ -32,7 +32,8 @@ def get_address(script_type: InputScriptType, coin: CoinType, node, multisig=Non raise AddressError(FailureType.ProcessError, 'Multisig not enabled on this coin') - return address_multisig_p2sh(multisig_get_pubkeys(multisig), multisig.m, coin.address_type_p2sh) + pubkeys = multisig_get_pubkeys(multisig) + return address_multisig_p2sh(pubkeys, multisig.m, coin.address_type_p2sh) if script_type == InputScriptType.SPENDMULTISIG: raise AddressError(FailureType.ProcessError, 'Multisig details required') @@ -46,7 +47,8 @@ def get_address(script_type: InputScriptType, coin: CoinType, node, multisig=Non 'Segwit not enabled on this coin') # native p2wsh multisig if multisig is not None: - return address_multisig_p2wsh(multisig_get_pubkeys(multisig), multisig.m, coin.bech32_prefix) + pubkeys = multisig_get_pubkeys(multisig) + return address_multisig_p2wsh(pubkeys, multisig.m, coin.bech32_prefix) # native p2wpkh return address_p2wpkh(node.public_key(), coin.bech32_prefix) @@ -57,7 +59,8 @@ def get_address(script_type: InputScriptType, coin: CoinType, node, multisig=Non 'Segwit not enabled on this coin') # p2wsh multisig nested in p2sh if multisig is not None: - return address_multisig_p2wsh_in_p2sh(multisig_get_pubkeys(multisig), multisig.m, coin.address_type_p2sh) + pubkeys = multisig_get_pubkeys(multisig) + return address_multisig_p2wsh_in_p2sh(pubkeys, multisig.m, coin.address_type_p2sh) # p2wpkh nested in p2sh return address_p2wpkh_in_p2sh(node.public_key(), coin.address_type_p2sh) @@ -67,25 +70,31 @@ def get_address(script_type: InputScriptType, coin: CoinType, node, multisig=Non 'Invalid script type') -def address_multisig_p2sh(pubkeys: bytes, m: int, addrtype): - digest = output_script_multisig_p2sh(pubkeys, m) +def address_multisig_p2sh(pubkeys: bytes, m: int, addrtype: int): if addrtype is None: raise AddressError(FailureType.ProcessError, 'Multisig not enabled on this coin') - return address_p2sh(digest, addrtype) + redeem_script = output_script_multisig(pubkeys, m) + redeem_script_hash = sha256_ripemd160_digest(redeem_script) + return address_p2sh(redeem_script_hash, addrtype) -def address_multisig_p2wsh_in_p2sh(pubkeys: bytes, m: int, addrtype): - digest = output_script_multisig_p2wsh(pubkeys, m) +def address_multisig_p2wsh_in_p2sh(pubkeys: bytes, m: int, addrtype: int): if addrtype is None: raise AddressError(FailureType.ProcessError, 'Multisig not enabled on this coin') - return address_p2wsh_in_p2sh(digest, addrtype) + witness_script = output_script_multisig(pubkeys, m) + witness_script_hash = sha256(witness_script).digest() + return address_p2wsh_in_p2sh(witness_script_hash, addrtype) def address_multisig_p2wsh(pubkeys: bytes, m: int, hrp: str): - digest = output_script_multisig_p2wsh(pubkeys, m) - return address_p2wsh(digest, hrp) + if not hrp: + raise AddressError(FailureType.ProcessError, + 'Multisig not enabled on this coin') + witness_script = output_script_multisig(pubkeys, m) + witness_script_hash = sha256(witness_script).digest() + return address_p2wsh(witness_script_hash, hrp) def address_p2sh(redeem_script_hash: bytes, addrtype: int) -> str: @@ -95,43 +104,19 @@ def address_p2sh(redeem_script_hash: bytes, addrtype: int) -> str: return base58.encode_check(bytes(s)) -# P2WPKH nested in P2SH. The P2SH redeem script hash is created using the -# `raw` function def address_p2wpkh_in_p2sh(pubkey: bytes, addrtype: int) -> str: - redeem_script_hash = address_p2wpkh_in_p2sh_script(pubkey) + pubkey_hash = ecdsa_hash_pubkey(pubkey) + redeem_script = output_script_native_p2wpkh_or_p2wsh(pubkey_hash) + redeem_script_hash = sha256_ripemd160_digest(redeem_script) return address_p2sh(redeem_script_hash, addrtype) -# Generates a P2SH redeem script based on a public key hash in a P2WPKH manner -def address_p2wpkh_in_p2sh_script(pubkey: bytes) -> bytes: - s = bytearray(22) - s[0] = 0x00 # OP_0 - s[1] = 0x14 # pushing 20 bytes - s[2:22] = ecdsa_hash_pubkey(pubkey) - h = sha256(s).digest() - h = ripemd160(h).digest() - return h - - -# P2WSH nested in P2SH. The P2SH redeem script hash is created using the -# `raw` function def address_p2wsh_in_p2sh(witness_script_hash: bytes, addrtype: int) -> str: - redeem_script_hash = address_p2wsh_in_p2sh_script(witness_script_hash) + redeem_script = output_script_native_p2wpkh_or_p2wsh(witness_script_hash) + redeem_script_hash = sha256_ripemd160_digest(redeem_script) return address_p2sh(redeem_script_hash, addrtype) -# Generates a P2SH redeem script based on a hash of a witness redeem script hash -def address_p2wsh_in_p2sh_script(script_hash: bytes) -> bytes: - s = bytearray(34) - s[0] = 0x00 # OP_0 - s[1] = 0x20 # pushing 32 bytes - s[2:34] = script_hash - h = sha256(s).digest() - h = ripemd160(h).digest() - return h - - -# Native Bech32 P2WPKH def address_p2wpkh(pubkey: bytes, hrp: str) -> str: pubkeyhash = ecdsa_hash_pubkey(pubkey) address = bech32.encode(hrp, _BECH32_WITVER, pubkeyhash) @@ -141,9 +126,8 @@ def address_p2wpkh(pubkey: bytes, hrp: str) -> str: return address -# Native Bech32 P2WSH, script_hash is 32-byte SHA256(script) -def address_p2wsh(script_hash: bytes, hrp: str) -> str: - address = bech32.encode(hrp, _BECH32_WITVER, script_hash) +def address_p2wsh(witness_script_hash: bytes, hrp: str) -> str: + address = bech32.encode(hrp, _BECH32_WITVER, witness_script_hash) if address is None: raise AddressError(FailureType.ProcessError, 'Invalid address') diff --git a/src/apps/wallet/sign_tx/scripts.py b/src/apps/wallet/sign_tx/scripts.py index f97ac5759..1839ef12f 100644 --- a/src/apps/wallet/sign_tx/scripts.py +++ b/src/apps/wallet/sign_tx/scripts.py @@ -146,7 +146,7 @@ def witness_p2wsh(multisig: MultisigRedeemScriptType, signature: bytes, signatur # redeem script pubkeys = multisig_get_pubkeys(multisig) - redeem_script = script_multisig(pubkeys, multisig.m) + redeem_script = output_script_multisig(pubkeys, multisig.m) write_varint(w, len(redeem_script)) write_bytes(w, redeem_script) return w @@ -176,32 +176,20 @@ def input_script_multisig(multisig: MultisigRedeemScriptType, signature: bytes, # redeem script pubkeys = multisig_get_pubkeys(multisig) - redeem_script = script_multisig(pubkeys, multisig.m) + redeem_script = output_script_multisig(pubkeys, multisig.m) write_op_push(w, len(redeem_script)) write_bytes(w, redeem_script) - return w - - -# returns a ripedm(sha256()) hash of a multisig script used in P2SH -def output_script_multisig_p2sh(pubkeys, m) -> bytes: - script = script_multisig(pubkeys, m) - h = sha256(script).digest() - return ripemd160(h).digest() - -# returns a sha256() hash of a multisig script used in P2WSH -def output_script_multisig_p2wsh(pubkeys, m) -> bytes: - for pubkey in pubkeys: - if len(pubkey) != 33: - raise ScriptsError('Only compressed public keys are allowed for P2WSH') - script = script_multisig(pubkeys, m) - return sha256(script).digest() + return w -def script_multisig(pubkeys, m) -> bytearray: +def output_script_multisig(pubkeys, m: int) -> bytearray: n = len(pubkeys) if n < 1 or n > 15 or m < 1 or m > 15: raise ScriptsError('Invalid multisig parameters') + for pubkey in pubkeys: + if len(pubkey) != 33: + raise ScriptsError('Invalid multisig parameters') w = bytearray() w.append(0x50 + m) # numbers 1 to 16 are pushed as 0x50 + value @@ -239,3 +227,9 @@ def append_pubkey(w: bytearray, pubkey: bytes) -> bytearray: write_op_push(w, len(pubkey)) write_bytes(w, pubkey) return w + + +def sha256_ripemd160_digest(b: bytes) -> bytes: + h = sha256(b).digest() + h = ripemd160(h).digest() + return h diff --git a/src/apps/wallet/sign_tx/segwit_bip143.py b/src/apps/wallet/sign_tx/segwit_bip143.py index 9621e831a..0423a4631 100644 --- a/src/apps/wallet/sign_tx/segwit_bip143.py +++ b/src/apps/wallet/sign_tx/segwit_bip143.py @@ -3,7 +3,7 @@ from trezor.messages.SignTx import SignTx from trezor.messages import InputScriptType, FailureType from apps.wallet.sign_tx.writers import * -from apps.wallet.sign_tx.scripts import output_script_p2pkh, script_multisig +from apps.wallet.sign_tx.scripts import output_script_p2pkh, output_script_multisig from apps.wallet.sign_tx.multisig import multisig_get_pubkeys from apps.common.hash_writer import HashWriter @@ -65,7 +65,7 @@ class Bip143: def derive_script_code(self, txi: TxInputType, pubkeyhash: bytes) -> bytearray: if txi.multisig: - return script_multisig(multisig_get_pubkeys(txi.multisig), txi.multisig.m) + return output_script_multisig(multisig_get_pubkeys(txi.multisig), txi.multisig.m) p2pkh = (txi.script_type == InputScriptType.SPENDWITNESS or txi.script_type == InputScriptType.SPENDP2SHWITNESS or diff --git a/src/apps/wallet/sign_tx/signing.py b/src/apps/wallet/sign_tx/signing.py index 0cf6fde44..f18cb4bb8 100644 --- a/src/apps/wallet/sign_tx/signing.py +++ b/src/apps/wallet/sign_tx/signing.py @@ -250,7 +250,7 @@ async def sign_tx(tx: SignTx, root): # for the signing process the script_sig is equal # to the previous tx's scriptPubKey (P2PKH) or a redeem script (P2SH) if txi_sign.script_type == InputScriptType.SPENDMULTISIG: - txi_sign.script_sig = script_multisig( + txi_sign.script_sig = output_script_multisig( multisig_get_pubkeys(txi_sign.multisig), txi_sign.multisig.m) elif txi_sign.script_type == InputScriptType.SPENDADDRESS: @@ -512,8 +512,10 @@ def input_derive_script(coin: CoinType, i: TxInputType, pubkey: bytes, signature if i.script_type == InputScriptType.SPENDP2SHWITNESS: # p2wpkh or p2wsh using p2sh if i.multisig: # p2wsh in p2sh pubkeys = multisig_get_pubkeys(i.multisig) - script_hash = output_script_multisig_p2wsh(pubkeys, i.multisig.m) - return input_script_p2wsh_in_p2sh(script_hash) + witness_script = output_script_multisig(pubkeys, i.multisig.m) + witness_script_hash = sha256(witness_script).digest() + return input_script_p2wsh_in_p2sh(witness_script_hash) + # p2wpkh in p2sh return input_script_p2wpkh_in_p2sh(ecdsa_hash_pubkey(pubkey)) diff --git a/tests/test_apps.wallet.address.py b/tests/test_apps.wallet.address.py index f76bede77..465738267 100644 --- a/tests/test_apps.wallet.address.py +++ b/tests/test_apps.wallet.address.py @@ -16,12 +16,6 @@ class TestAddress(unittest.TestCase): ) self.assertEqual(address, '2Mww8dCYPUpKHofjgcXcBCEGmniw9CoaiD2') - def test_p2wpkh_in_p2sh_script_address(self): - raw = address_p2wpkh_in_p2sh_script( - unhexlify('03a1af804ac108a8a51782198c2d034b28bf90c8803f5a53f76276fa69a4eae77f') - ) - self.assertEqual(raw, unhexlify('336caa13e08b96080a32b5d818d59b4ab3b36742')) - def test_p2wpkh_in_p2sh_node_derive_address(self): coin = coins.by_name('Testnet') seed = bip39.seed(' '.join(['all'] * 12), '') @@ -84,23 +78,18 @@ class TestAddress(unittest.TestCase): ) self.assertEqual(address, '3Dwz1MXhM6EfFoJChHCxh1jWHb8GQqRenG') - def test_p2wsh_in_p2sh_script_address(self): - raw = address_p2wsh_in_p2sh_script( - unhexlify('1863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262') - ) - self.assertEqual(raw, unhexlify('e4300531190587e3880d4c3004f5355d88ff928d')) - def test_multisig_address_p2sh(self): - # test data from - # http://www.soroushjp.com/2014/12/20/bitcoin-multisig-the-hard-way-understanding-raw-multisignature-bitcoin-transactions/ - coin = coins.by_name('Bitcoin') - pubkeys = [ - unhexlify('04a882d414e478039cd5b52a92ffb13dd5e6bd4515497439dffd691a0f12af9575fa349b5694ed3155b136f09e63975a1700c9f4d4df849323dac06cf3bd6458cd'), - unhexlify('046ce31db9bdd543e72fe3039a1f1c047dab87037c36a669ff90e28da1848f640de68c2fe913d363a51154a0c62d7adea1b822d05035077418267b1a1379790187'), - unhexlify('0411ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef992f63280567f52f5ba870678b4ab4ff6c8ea600bd217870a8b4f1f09f3a8e83'), - ] - address = address_multisig_p2sh(pubkeys, 2, coin.address_type_p2sh) - self.assertEqual(address, '347N1Thc213QqfYCz3PZkjoJpNv5b14kBd') + # # test data from + # # http://www.soroushjp.com/2014/12/20/bitcoin-multisig-the-hard-way-understanding-raw-multisignature-bitcoin-transactions/ + # # commented out because uncompressed public keys are not supported + # coin = coins.by_name('Bitcoin') + # pubkeys = [ + # unhexlify('04a882d414e478039cd5b52a92ffb13dd5e6bd4515497439dffd691a0f12af9575fa349b5694ed3155b136f09e63975a1700c9f4d4df849323dac06cf3bd6458cd'), + # unhexlify('046ce31db9bdd543e72fe3039a1f1c047dab87037c36a669ff90e28da1848f640de68c2fe913d363a51154a0c62d7adea1b822d05035077418267b1a1379790187'), + # unhexlify('0411ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef992f63280567f52f5ba870678b4ab4ff6c8ea600bd217870a8b4f1f09f3a8e83'), + # ] + # address = address_multisig_p2sh(pubkeys, 2, coin.address_type_p2sh) + # self.assertEqual(address, '347N1Thc213QqfYCz3PZkjoJpNv5b14kBd') coin = coins.by_name('Bitcoin') pubkeys = [