diff --git a/core/src/apps/wallet/keychain.py b/core/src/apps/wallet/keychain.py index 99caa2dfe8..587ecd959d 100644 --- a/core/src/apps/wallet/keychain.py +++ b/core/src/apps/wallet/keychain.py @@ -19,17 +19,7 @@ if False: ] -async def get_keychain_for_coin( - ctx: wire.Context, coin_name: Optional[str] -) -> Tuple[Keychain, coininfo.CoinInfo]: - if coin_name is None: - coin_name = "Bitcoin" - - try: - coin = coininfo.by_name(coin_name) - except ValueError: - raise wire.DataError("Unsupported coin type") - +def get_namespaces_for_coin(coin: coininfo.CoinInfo): namespaces = [] curve = coin.curve_name slip44_id = coin.slip44 | HARDENED @@ -48,6 +38,21 @@ async def get_keychain_for_coin( # BIP-84 - native segwit: m/84'/slip44' (/account'/change/addr) namespaces.append((curve, [84 | HARDENED, slip44_id])) + return namespaces + + +async def get_keychain_for_coin( + ctx: wire.Context, coin_name: Optional[str] +) -> Tuple[Keychain, coininfo.CoinInfo]: + if coin_name is None: + coin_name = "Bitcoin" + + try: + coin = coininfo.by_name(coin_name) + except ValueError: + raise wire.DataError("Unsupported coin type") + + namespaces = get_namespaces_for_coin(coin) keychain = await get_keychain(ctx, namespaces) return keychain, coin diff --git a/core/tests/test_apps.common.seed.py b/core/tests/test_apps.common.seed.py index 994b1f560d..a342380a7e 100644 --- a/core/tests/test_apps.common.seed.py +++ b/core/tests/test_apps.common.seed.py @@ -8,10 +8,10 @@ from trezor.crypto.curve import secp256k1 class TestKeychain(unittest.TestCase): - def test_validate_path(self): + def test_match_path(self): n = [ - ["ed25519", 44 | HARDENED, 134 | HARDENED], - ["secp256k1", 44 | HARDENED, 11 | HARDENED], + ("ed25519", [44 | HARDENED, 134 | HARDENED]), + ("secp256k1", [44 | HARDENED, 11 | HARDENED]), ] k = Keychain(b"", n) @@ -20,54 +20,50 @@ class TestKeychain(unittest.TestCase): ([44 | HARDENED, 11 | HARDENED], "secp256k1"), ([44 | HARDENED, 11 | HARDENED, 12], "secp256k1"), ) - for c in correct: - self.assertEqual(None, k.validate_path(*c)) + for path, curve in correct: + i, suffix = k.match_path(path) + ns_curve, ns = k.namespaces[i] + self.assertEqual(curve, ns_curve) fails = [ - ([44 | HARDENED, 134], "ed25519"), # path does not match - ([44 | HARDENED, 134], "secp256k1"), # curve and path does not match - ([44 | HARDENED, 134 | HARDENED], "nist256p"), # curve not included - ([44, 134], "ed25519"), # path does not match (non-hardened items) - ([44 | HARDENED, 134 | HARDENED, 123], "ed25519"), # non-hardened item in ed25519 - ([44 | HARDENED, 13 | HARDENED], "secp256k1"), # invalid second item + [44 | HARDENED, 134], # path does not match + [44, 134], # path does not match (non-hardened items) + [44 | HARDENED, 134 | HARDENED, 123], # non-hardened item in ed25519 ns + [44 | HARDENED, 13 | HARDENED], # invalid second item ] for f in fails: with self.assertRaises(wire.DataError): - k.validate_path(*f) + k.match_path(f) - def test_validate_path_special_ed25519(self): + def test_match_path_special_ed25519(self): n = [ - ["ed25519-keccak", 44 | HARDENED, 134 | HARDENED], + ("ed25519-keccak", [44 | HARDENED, 134 | HARDENED]), ] k = Keychain(b"", n) correct = ( - ([44 | HARDENED, 134 | HARDENED], "ed25519-keccak"), + [44 | HARDENED, 134 | HARDENED], ) for c in correct: - self.assertEqual(None, k.validate_path(*c)) + k.match_path(c) fails = [ - ([44 | HARDENED, 134 | HARDENED, 1], "ed25519-keccak"), + [44 | HARDENED, 134 | HARDENED, 1], ] for f in fails: with self.assertRaises(wire.DataError): - k.validate_path(*f) + k.match_path(f) - def test_validate_path_empty_namespace(self): - k = Keychain(b"", [["secp256k1"]]) + def test_match_path_empty_namespace(self): + k = Keychain(b"", [("secp256k1", [])]) correct = ( - ([], "secp256k1"), - ([1, 2, 3, 4], "secp256k1"), - ([44 | HARDENED, 11 | HARDENED], "secp256k1"), - ([44 | HARDENED, 11 | HARDENED, 12], "secp256k1"), + [], + [1, 2, 3, 4], + [44 | HARDENED, 11 | HARDENED], + [44 | HARDENED, 11 | HARDENED, 12], ) for c in correct: - self.assertEqual(None, k.validate_path(*c)) - - with self.assertRaises(wire.DataError): - k.validate_path([1, 2, 3, 4], "ed25519") - k.validate_path([], "ed25519") + k.match_path(c) def test_path_hardened(self): self.assertTrue(_path_hardened([44 | HARDENED, 1 | HARDENED, 0 | HARDENED])) @@ -81,7 +77,7 @@ class TestKeychain(unittest.TestCase): seed = bip39.seed(' '.join(['all'] * 12), '') node1 = Slip21Node(seed) node2 = node1.clone() - keychain = Keychain(seed, [["slip21", b"SLIP-0021"]]) + keychain = Keychain(seed, [("slip21", [b"SLIP-0021"])]) # Key(m) KEY_M = unhexlify(b"dbf12b44133eaab506a740f6565cc117228cbf1dd70635cfa8ddfdc9af734756") @@ -91,45 +87,30 @@ class TestKeychain(unittest.TestCase): KEY_M_SLIP0021 = unhexlify(b"1d065e3ac1bbe5c7fad32cf2305f7d709dc070d672044a19e610c77cdf33de0d") node1.derive_path([b"SLIP-0021"]) self.assertEqual(node1.key(), KEY_M_SLIP0021) - self.assertIsNone(keychain.validate_path([b"SLIP-0021"], "slip21")) - self.assertEqual(keychain.derive([b"SLIP-0021"], "slip21").key(), KEY_M_SLIP0021) + keychain.match_path([b"SLIP-0021"]) + self.assertEqual(keychain.derive([b"SLIP-0021"]).key(), KEY_M_SLIP0021) # Key(m/"SLIP-0021"/"Master encryption key") KEY_M_SLIP0021_MEK = unhexlify(b"ea163130e35bbafdf5ddee97a17b39cef2be4b4f390180d65b54cf05c6a82fde") node1.derive_path([b"Master encryption key"]) self.assertEqual(node1.key(), KEY_M_SLIP0021_MEK) - self.assertIsNone(keychain.validate_path([b"SLIP-0021", b"Master encryption key"], "slip21")) - self.assertEqual(keychain.derive([b"SLIP-0021", b"Master encryption key"], "slip21").key(), KEY_M_SLIP0021_MEK) + keychain.match_path([b"SLIP-0021", b"Master encryption key"]) + self.assertEqual(keychain.derive([b"SLIP-0021", b"Master encryption key"]).key(), KEY_M_SLIP0021_MEK) # Key(m/"SLIP-0021"/"Authentication key") KEY_M_SLIP0021_AK = unhexlify(b"47194e938ab24cc82bfa25f6486ed54bebe79c40ae2a5a32ea6db294d81861a6") node2.derive_path([b"SLIP-0021", b"Authentication key"]) self.assertEqual(node2.key(), KEY_M_SLIP0021_AK) - self.assertIsNone(keychain.validate_path([b"SLIP-0021", b"Authentication key"], "slip21")) - self.assertEqual(keychain.derive([b"SLIP-0021", b"Authentication key"], "slip21").key(), KEY_M_SLIP0021_AK) + keychain.match_path([b"SLIP-0021", b"Authentication key"]) + self.assertEqual(keychain.derive([b"SLIP-0021", b"Authentication key"]).key(), KEY_M_SLIP0021_AK) # Forbidden paths. with self.assertRaises(wire.DataError): - self.assertFalse(keychain.validate_path([], "slip21")) + self.assertFalse(keychain.match_path([])) with self.assertRaises(wire.DataError): - self.assertFalse(keychain.validate_path([b"SLIP-9999", b"Authentication key"], "slip21")) + self.assertFalse(keychain.match_path([b"SLIP-9999", b"Authentication key"])) with self.assertRaises(wire.DataError): - keychain.derive([b"SLIP-9999", b"Authentication key"], "slip21").key() - - def test_slip77(self): - seed = bip39.seed("alcohol woman abuse must during monitor noble actual mixed trade anger aisle", "") - keychain = Keychain(seed, [["slip21", b"SLIP-0077"], ["secp256k1"]]) - - node = keychain.derive([44 | HARDENED, 1 | HARDENED, 0 | HARDENED, 0, 0]) - coin = coins.by_name('Elements') - pubkey_hash = addresses.ecdsa_hash_pubkey(node.public_key(), coin) - script = scripts.output_script_p2pkh(pubkey_hash) - - private_key = keychain.derive_slip77_blinding_private_key(script) - self.assertEqual(private_key, unhexlify(b"26f1dc2c52222394236d76e0809516255cfcca94069fd5187c0f090d18f42ad6")) - public_key = keychain.derive_slip77_blinding_public_key(script) - self.assertEqual(public_key, unhexlify(b"03e84cd853fea825bd94f5d2d46580ae0d059c734707fa7a08f5e2f612a51c1acb")) - self.assertEqual(secp256k1.publickey(private_key), public_key) + keychain.derive([b"SLIP-9999", b"Authentication key"]).key() if __name__ == '__main__': diff --git a/core/tests/test_apps.wallet.segwit.signtx.native_p2wpkh.py b/core/tests/test_apps.wallet.segwit.signtx.native_p2wpkh.py index 7c9715be9a..f571f682f2 100644 --- a/core/tests/test_apps.wallet.segwit.signtx.native_p2wpkh.py +++ b/core/tests/test_apps.wallet.segwit.signtx.native_p2wpkh.py @@ -16,6 +16,7 @@ from trezor.messages import OutputScriptType from apps.common import coins from apps.common.seed import Keychain +from apps.wallet.keychain import get_namespaces_for_coin from apps.wallet.sign_tx import helpers, bitcoin from apps.wallet.sign_tx.scripts import ScriptsError @@ -119,7 +120,8 @@ class TestSignSegwitTxNativeP2WPKH(unittest.TestCase): )), ] - keychain = Keychain(seed, [[coin.curve_name]]) + ns = get_namespaces_for_coin(coin) + keychain = Keychain(seed, ns) signer = bitcoin.Bitcoin(tx, keychain, coin).signer() for request, response in chunks(messages, 2): res = signer.send(request) @@ -218,7 +220,8 @@ class TestSignSegwitTxNativeP2WPKH(unittest.TestCase): )), ] - keychain = Keychain(seed, [[coin.curve_name]]) + ns = get_namespaces_for_coin(coin) + keychain = Keychain(seed, ns) signer = bitcoin.Bitcoin(tx, keychain, coin).signer() for request, response in chunks(messages, 2): self.assertEqual(signer.send(request), response) @@ -264,7 +267,8 @@ class TestSignSegwitTxNativeP2WPKH(unittest.TestCase): None ] - keychain = Keychain(seed, [[coin.curve_name]]) + ns = get_namespaces_for_coin(coin) + keychain = Keychain(seed, ns) signer = bitcoin.Bitcoin(tx, keychain, coin).signer() for request, response in chunks(messages, 2): if response is None: diff --git a/core/tests/test_apps.wallet.segwit.signtx.native_p2wpkh_grs.py b/core/tests/test_apps.wallet.segwit.signtx.native_p2wpkh_grs.py index e4039e6d56..1ec1619686 100644 --- a/core/tests/test_apps.wallet.segwit.signtx.native_p2wpkh_grs.py +++ b/core/tests/test_apps.wallet.segwit.signtx.native_p2wpkh_grs.py @@ -16,6 +16,7 @@ from trezor.messages import OutputScriptType from apps.common import coins from apps.common.seed import Keychain +from apps.wallet.keychain import get_namespaces_for_coin from apps.wallet.sign_tx import bitcoinlike, helpers @@ -120,7 +121,8 @@ class TestSignSegwitTxNativeP2WPKH_GRS(unittest.TestCase): )), ] - keychain = Keychain(seed, [[coin.curve_name]]) + ns = get_namespaces_for_coin(coin) + keychain = Keychain(seed, ns) signer = bitcoinlike.Bitcoinlike(tx, keychain, coin).signer() for request, response in chunks(messages, 2): self.assertEqual(signer.send(request), response) @@ -218,7 +220,8 @@ class TestSignSegwitTxNativeP2WPKH_GRS(unittest.TestCase): )), ] - keychain = Keychain(seed, [[coin.curve_name]]) + ns = get_namespaces_for_coin(coin) + keychain = Keychain(seed, ns) signer = bitcoinlike.Bitcoinlike(tx, keychain, coin).signer() for request, response in chunks(messages, 2): self.assertEqual(signer.send(request), response) diff --git a/core/tests/test_apps.wallet.segwit.signtx.p2wpkh_in_p2sh.py b/core/tests/test_apps.wallet.segwit.signtx.p2wpkh_in_p2sh.py index 604a458af4..f8c1b5a81f 100644 --- a/core/tests/test_apps.wallet.segwit.signtx.p2wpkh_in_p2sh.py +++ b/core/tests/test_apps.wallet.segwit.signtx.p2wpkh_in_p2sh.py @@ -16,6 +16,7 @@ from trezor.messages import OutputScriptType from apps.common import coins from apps.common.seed import Keychain +from apps.wallet.keychain import get_namespaces_for_coin from apps.wallet.sign_tx import bitcoin, common, helpers @@ -115,7 +116,8 @@ class TestSignSegwitTxP2WPKHInP2SH(unittest.TestCase): )), ] - keychain = Keychain(seed, [[coin.curve_name]]) + ns = get_namespaces_for_coin(coin) + keychain = Keychain(seed, ns) signer = bitcoin.Bitcoin(tx, keychain, coin).signer() for request, response in chunks(messages, 2): self.assertEqual(signer.send(request), response) @@ -221,7 +223,8 @@ class TestSignSegwitTxP2WPKHInP2SH(unittest.TestCase): )), ] - keychain = Keychain(seed, [[coin.curve_name]]) + ns = get_namespaces_for_coin(coin) + keychain = Keychain(seed, ns) signer = bitcoin.Bitcoin(tx, keychain, coin).signer() for request, response in chunks(messages, 2): self.assertEqual(signer.send(request), response) @@ -332,7 +335,8 @@ class TestSignSegwitTxP2WPKHInP2SH(unittest.TestCase): TxRequest(request_type=TXFINISHED, details=TxRequestDetailsType()) ] - keychain = Keychain(seed, [[coin.curve_name]]) + ns = get_namespaces_for_coin(coin) + keychain = Keychain(seed, ns) signer = bitcoin.Bitcoin(tx, keychain, coin).signer() i = 0 messages_count = int(len(messages) / 2) diff --git a/core/tests/test_apps.wallet.segwit.signtx.p2wpkh_in_p2sh_grs.py b/core/tests/test_apps.wallet.segwit.signtx.p2wpkh_in_p2sh_grs.py index 79592ed7a8..e00fb33d2f 100644 --- a/core/tests/test_apps.wallet.segwit.signtx.p2wpkh_in_p2sh_grs.py +++ b/core/tests/test_apps.wallet.segwit.signtx.p2wpkh_in_p2sh_grs.py @@ -16,6 +16,7 @@ from trezor.messages import OutputScriptType from apps.common import coins from apps.common.seed import Keychain +from apps.wallet.keychain import get_namespaces_for_coin from apps.wallet.sign_tx import bitcoinlike, helpers @@ -120,7 +121,8 @@ class TestSignSegwitTxP2WPKHInP2SH_GRS(unittest.TestCase): )), ] - keychain = Keychain(seed, [[coin.curve_name]]) + ns = get_namespaces_for_coin(coin) + keychain = Keychain(seed, ns) signer = bitcoinlike.Bitcoinlike(tx, keychain, coin).signer() for request, response in chunks(messages, 2): self.assertEqual(signer.send(request), response) @@ -226,7 +228,8 @@ class TestSignSegwitTxP2WPKHInP2SH_GRS(unittest.TestCase): )), ] - keychain = Keychain(seed, [[coin.curve_name]]) + ns = get_namespaces_for_coin(coin) + keychain = Keychain(seed, ns) signer = bitcoinlike.Bitcoinlike(tx, keychain, coin).signer() for request, response in chunks(messages, 2): self.assertEqual(signer.send(request), response) diff --git a/core/tests/test_apps.wallet.sign_tx.writers.py b/core/tests/test_apps.wallet.sign_tx.writers.py index 7f6bc53876..9134333bb4 100644 --- a/core/tests/test_apps.wallet.sign_tx.writers.py +++ b/core/tests/test_apps.wallet.sign_tx.writers.py @@ -4,7 +4,6 @@ from trezor.messages.TxInputType import TxInputType from trezor.messages import InputScriptType from apps.common import coins -from apps.common.seed import Keychain from apps.wallet.sign_tx import writers diff --git a/core/tests/test_apps.wallet.signtx.py b/core/tests/test_apps.wallet.signtx.py index 14e8c2b9f4..d183a6e555 100644 --- a/core/tests/test_apps.wallet.signtx.py +++ b/core/tests/test_apps.wallet.signtx.py @@ -16,6 +16,7 @@ from trezor.messages import OutputScriptType from apps.common import coins from apps.common.seed import Keychain +from apps.wallet.keychain import get_namespaces_for_coin from apps.wallet.sign_tx import bitcoin, helpers @@ -45,7 +46,7 @@ class TestSignTx(unittest.TestCase): pout1 = TxOutputBinType(script_pubkey=unhexlify('76a91424a56db43cf6f2b02e838ea493f95d8d6047423188ac'), amount=390000) - inp1 = TxInputType(address_n=[0], # 14LmW5k4ssUrtbAB4255zdqv3b4w1TuX9e + inp1 = TxInputType(address_n=[44 | 0x80000000, 0 | 0x80000000, 0 | 0x80000000, 0, 0], # amount=390000, prev_hash=unhexlify('d5f65ee80147b4bcc70b75e4bbf2d7382021b871bd8867ef8fa525ef50864882'), prev_index=0, @@ -56,7 +57,6 @@ class TestSignTx(unittest.TestCase): out1 = TxOutputType(address='1MJ2tj2ThBE62zXbBYA5ZaN3fdve5CPAz1', amount=390000 - 10000, script_type=OutputScriptType.PAYTOADDRESS, - address_n=[], multisig=None) tx = SignTx(coin_name=None, version=None, lock_time=None, inputs_count=1, outputs_count=1) @@ -65,8 +65,6 @@ class TestSignTx(unittest.TestCase): TxRequest(request_type=TXINPUT, details=TxRequestDetailsType(request_index=0, tx_hash=None), serialized=EMPTY_SERIALIZED), TxAck(tx=TransactionType(inputs=[inp1])), - helpers.UiConfirmForeignAddress(address_n=inp1.address_n), - True, TxRequest(request_type=TXMETA, details=TxRequestDetailsType(request_index=None, tx_hash=unhexlify('d5f65ee80147b4bcc70b75e4bbf2d7382021b871bd8867ef8fa525ef50864882')), serialized=EMPTY_SERIALIZED), TxAck(tx=ptx1), TxRequest(request_type=TXINPUT, details=TxRequestDetailsType(request_index=0, tx_hash=unhexlify('d5f65ee80147b4bcc70b75e4bbf2d7382021b871bd8867ef8fa525ef50864882')), serialized=EMPTY_SERIALIZED), @@ -89,8 +87,8 @@ class TestSignTx(unittest.TestCase): TxAck(tx=TransactionType(outputs=[out1])), TxRequest(request_type=TXOUTPUT, details=TxRequestDetailsType(request_index=0, tx_hash=None), serialized=TxRequestSerializedType( signature_index=0, - signature=unhexlify('30450221009a0b7be0d4ed3146ee262b42202841834698bb3ee39c24e7437df208b8b7077102202b79ab1e7736219387dffe8d615bbdba87e11477104b867ef47afed1a5ede781'), - serialized_tx=unhexlify('82488650ef25a58fef6788bd71b8212038d7f2bbe4750bc7bcb44701e85ef6d5000000006b4830450221009a0b7be0d4ed3146ee262b42202841834698bb3ee39c24e7437df208b8b7077102202b79ab1e7736219387dffe8d615bbdba87e11477104b867ef47afed1a5ede7810121023230848585885f63803a0a8aecdd6538792d5c539215c91698e315bf0253b43dffffffff01'))), + signature=unhexlify('30440220198146fa987da8d78c4c7a471614fceb54d161ede244412f3369f436a7aec386022066bbede7644baa38abbdb4b1f3037f8db225c04e107099b625339a55614c3db3'), + serialized_tx=unhexlify('82488650ef25a58fef6788bd71b8212038d7f2bbe4750bc7bcb44701e85ef6d5000000006a4730440220198146fa987da8d78c4c7a471614fceb54d161ede244412f3369f436a7aec386022066bbede7644baa38abbdb4b1f3037f8db225c04e107099b625339a55614c3db30121027a4cebff51c97c047637cda66838e8b64421a4af6bf8ef3c99717f92d09b3c1dffffffff01'))), TxAck(tx=TransactionType(outputs=[out1])), TxRequest(request_type=TXFINISHED, details=TxRequestDetailsType(), serialized=TxRequestSerializedType( signature_index=None, @@ -100,7 +98,8 @@ class TestSignTx(unittest.TestCase): ] seed = bip39.seed('alcohol woman abuse must during monitor noble actual mixed trade anger aisle', '') - keychain = Keychain(seed, [[coin_bitcoin.curve_name]]) + ns = get_namespaces_for_coin(coin_bitcoin) + keychain = Keychain(seed, ns) signer = bitcoin.Bitcoin(tx, keychain, coin_bitcoin).signer() for request, response in chunks(messages, 2): diff --git a/core/tests/test_apps.wallet.signtx_grs.py b/core/tests/test_apps.wallet.signtx_grs.py index 556c3ba236..946bb262c9 100644 --- a/core/tests/test_apps.wallet.signtx_grs.py +++ b/core/tests/test_apps.wallet.signtx_grs.py @@ -16,6 +16,7 @@ from trezor.messages import OutputScriptType from apps.common import coins from apps.common.seed import Keychain +from apps.wallet.keychain import get_namespaces_for_coin from apps.wallet.sign_tx import bitcoinlike, helpers @@ -93,7 +94,8 @@ class TestSignTx_GRS(unittest.TestCase): ] seed = bip39.seed(' '.join(['all'] * 12), '') - keychain = Keychain(seed, [[coin.curve_name]]) + ns = get_namespaces_for_coin(coin) + keychain = Keychain(seed, ns) signer = bitcoinlike.Bitcoinlike(tx, keychain, coin).signer() for request, response in chunks(messages, 2): self.assertEqual(signer.send(request), response)