diff --git a/src/apps/wallet/sign_tx/addresses.py b/src/apps/wallet/sign_tx/addresses.py index afa8963c1..137061404 100644 --- a/src/apps/wallet/sign_tx/addresses.py +++ b/src/apps/wallet/sign_tx/addresses.py @@ -28,7 +28,7 @@ def get_address(script_type: InputScriptType, coin: CoinType, node) -> str: return address_p2wpkh(node.public_key(), coin.bech32_prefix) elif script_type == InputScriptType.SPENDP2SHWITNESS: # p2wpkh using p2sh - if not coin.segwit or not coin.address_type_p2sh: + if not coin.segwit or coin.address_type_p2sh is None: raise AddressError(FailureType.ProcessError, 'Segwit not enabled on this coin') return address_p2wpkh_in_p2sh(node.public_key(), coin.address_type_p2sh) @@ -38,14 +38,22 @@ def get_address(script_type: InputScriptType, coin: CoinType, node) -> str: 'Invalid script type') -def address_p2wpkh_in_p2sh(pubkey: bytes, addrtype: int) -> str: +def address_p2sh(redeem_script_hash: bytes, addrtype: int) -> str: s = bytearray(21) s[0] = addrtype - s[1:21] = address_p2wpkh_in_p2sh_raw(pubkey) + s[1:21] = redeem_script_hash return base58.encode_check(bytes(s)) -def address_p2wpkh_in_p2sh_raw(pubkey: bytes) -> bytes: +# 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) + 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 @@ -55,6 +63,25 @@ def address_p2wpkh_in_p2sh_raw(pubkey: bytes) -> bytes: 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) + 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) @@ -64,6 +91,15 @@ 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) + if address is None: + raise AddressError(FailureType.ProcessError, + 'Invalid address') + return address + + def decode_bech32_address(prefix: str, address: str) -> bytes: witver, raw = bech32.decode(prefix, address) if witver != _BECH32_WITVER: diff --git a/tests/test_apps.wallet.address.py b/tests/test_apps.wallet.address.py new file mode 100644 index 000000000..6ff4e5dda --- /dev/null +++ b/tests/test_apps.wallet.address.py @@ -0,0 +1,95 @@ +from common import * + +from apps.wallet.sign_tx.signing import * +from apps.common import coins +from trezor.crypto import bip32, bip39 + + +class TestAddress(unittest.TestCase): + # pylint: disable=C0301 + + def test_p2wpkh_in_p2sh_address(self): + coin = coins.by_name('Testnet') + address = address_p2wpkh_in_p2sh( + unhexlify('03a1af804ac108a8a51782198c2d034b28bf90c8803f5a53f76276fa69a4eae77f'), + coin.address_type_p2sh + ) + 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), '') + root = bip32.from_seed(seed, 'secp256k1') + + node = node_derive(root, [49 | 0x80000000, 1 | 0x80000000, 0 | 0x80000000, 1, 0]) + address = address_p2wpkh_in_p2sh(node.public_key(), coin.address_type_p2sh) + + self.assertEqual(address, '2N1LGaGg836mqSQqiuUBLfcyGBhyZbremDX') + + node = node_derive(root, [49 | 0x80000000, 1 | 0x80000000, 0 | 0x80000000, 1, 1]) + address = address_p2wpkh_in_p2sh(node.public_key(), coin.address_type_p2sh) + + self.assertEqual(address, '2NFWLCJQBSpz1oUJwwLpX8ECifFWGznBVqs') + + node = node_derive(root, [49 | 0x80000000, 1 | 0x80000000, 0 | 0x80000000, 0, 0]) + address = address_p2wpkh_in_p2sh(node.public_key(), coin.address_type_p2sh) + + self.assertEqual(address, '2N4Q5FhU2497BryFfUgbqkAJE87aKHUhXMp') + + def test_p2wpkh_address(self): + # data from https://bc-2.jp/tools/bech32demo/index.html + coin = coins.by_name('Testnet') + address = address_p2wpkh( + unhexlify('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), + coin.bech32_prefix + ) + self.assertEqual(address, 'tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx') + + def test_p2sh_address(self): + coin = coins.by_name('Testnet') + + address = address_p2sh( + unhexlify('7a55d61848e77ca266e79a39bfc85c580a6426c9'), + coin.address_type_p2sh + ) + self.assertEqual(address, '2N4Q5FhU2497BryFfUgbqkAJE87aKHUhXMp') + + def test_p2wsh_address(self): + coin = coins.by_name('Testnet') + + # pubkey OP_CHECKSIG + script = unhexlify('210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ac') + h = HashWriter(sha256) + write_bytes(h, script) + + address = address_p2wsh( + h.get_digest(), + coin.bech32_prefix + ) + self.assertEqual(address, 'tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7') + + def test_p2wsh_in_p2sh_address(self): + coin = coins.by_name('Bitcoin') + + # data from Mastering Bitcoin + address = address_p2wsh_in_p2sh( + unhexlify('9592d601848d04b172905e0ddb0adde59f1590f1e553ffc81ddc4b0ed927dd73'), + coin.address_type_p2sh + ) + 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')) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_apps.wallet.segwit.address.py b/tests/test_apps.wallet.segwit.address.py deleted file mode 100644 index 8bfb45e2a..000000000 --- a/tests/test_apps.wallet.segwit.address.py +++ /dev/null @@ -1,48 +0,0 @@ -from common import * - -from apps.wallet.sign_tx.signing import * -from apps.common import coins -from trezor.crypto import bip32, bip39 - - -class TestSegwitAddress(unittest.TestCase): - # pylint: disable=C0301 - - def test_p2wpkh_in_p2sh_address(self): - coin = coins.by_name('Testnet') - - address = address_p2wpkh_in_p2sh( - unhexlify('03a1af804ac108a8a51782198c2d034b28bf90c8803f5a53f76276fa69a4eae77f'), - coin.address_type_p2sh - ) - self.assertEqual(address, '2Mww8dCYPUpKHofjgcXcBCEGmniw9CoaiD2') - - def test_p2wpkh_in_p2sh_raw_address(self): - raw = address_p2wpkh_in_p2sh_raw( - 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), '') - root = bip32.from_seed(seed, 'secp256k1') - - node = node_derive(root, [49 | 0x80000000, 1 | 0x80000000, 0 | 0x80000000, 1, 0]) - address = address_p2wpkh_in_p2sh(node.public_key(), coin.address_type_p2sh) - - self.assertEqual(address, '2N1LGaGg836mqSQqiuUBLfcyGBhyZbremDX') - - node = node_derive(root, [49 | 0x80000000, 1 | 0x80000000, 0 | 0x80000000, 1, 1]) - address = address_p2wpkh_in_p2sh(node.public_key(), coin.address_type_p2sh) - - self.assertEqual(address, '2NFWLCJQBSpz1oUJwwLpX8ECifFWGznBVqs') - - node = node_derive(root, [49 | 0x80000000, 1 | 0x80000000, 0 | 0x80000000, 0, 0]) - address = address_p2wpkh_in_p2sh(node.public_key(), coin.address_type_p2sh) - - self.assertEqual(address, '2N4Q5FhU2497BryFfUgbqkAJE87aKHUhXMp') - - -if __name__ == '__main__': - unittest.main()