From cd1b194be839845a5eca81e7aa3a7c3d0e815308 Mon Sep 17 00:00:00 2001 From: Andrew Kozlik Date: Tue, 15 Apr 2025 09:56:19 +0200 Subject: [PATCH] feat(core): Include address_n in address MAC. --- core/src/apps/bitcoin/get_address.py | 2 +- core/src/apps/common/address_mac.py | 12 +++++++++--- core/src/apps/common/payment_request.py | 8 ++++++-- core/src/apps/ethereum/get_address.py | 2 +- core/tests/test_apps.bitcoin.address.py | 16 ++++++++++------ tests/device_tests/bitcoin/test_getaddress.py | 10 +++++----- tests/device_tests/payment_req.py | 5 ++++- 7 files changed, 36 insertions(+), 19 deletions(-) diff --git a/core/src/apps/bitcoin/get_address.py b/core/src/apps/bitcoin/get_address.py index cd5fadf9c7..53510110ff 100644 --- a/core/src/apps/bitcoin/get_address.py +++ b/core/src/apps/bitcoin/get_address.py @@ -102,7 +102,7 @@ async def get_address(msg: GetAddress, keychain: Keychain, coin: CoinInfo) -> Ad keychain.is_in_keychain(address_n) and validate_path_against_script_type(coin, msg) ): - mac = get_address_mac(address, coin.slip44, keychain) + mac = get_address_mac(address, coin.slip44, address_n, keychain) if msg.show_display: path = address_n_to_str(address_n) diff --git a/core/src/apps/common/address_mac.py b/core/src/apps/common/address_mac.py index f4eccf38f6..9b9645b7da 100644 --- a/core/src/apps/common/address_mac.py +++ b/core/src/apps/common/address_mac.py @@ -4,22 +4,25 @@ from trezor import utils if TYPE_CHECKING: from apps.common.keychain import Keychain + from apps.common.paths import Bip32Path _ADDRESS_MAC_KEY_PATH = [b"SLIP-0024", b"Address MAC key"] def check_address_mac( - address: str, mac: bytes, slip44: int, keychain: Keychain + address: str, mac: bytes, slip44: int, address_n: Bip32Path, keychain: Keychain ) -> None: from trezor import wire from trezor.crypto import hashlib - expected_mac = get_address_mac(address, slip44, keychain) + expected_mac = get_address_mac(address, slip44, address_n, keychain) if len(mac) != hashlib.sha256.digest_size or not utils.consteq(expected_mac, mac): raise wire.DataError("Invalid address MAC.") -def get_address_mac(address: str, slip44: int, keychain: Keychain) -> bytes: +def get_address_mac( + address: str, slip44: int, address_n: Bip32Path, keychain: Keychain +) -> bytes: from trezor.crypto import hmac from .writers import write_bytes_unchecked, write_compact_size, write_uint32_le @@ -31,6 +34,9 @@ def get_address_mac(address: str, slip44: int, keychain: Keychain) -> bytes: mac = utils.HashWriter(hmac(hmac.SHA256, node.key())) address_bytes = address.encode() write_uint32_le(mac, slip44) + write_compact_size(mac, len(address_n)) + for n in address_n: + write_uint32_le(mac, n) write_compact_size(mac, len(address_bytes)) write_bytes_unchecked(mac, address_bytes) return mac.get_digest() diff --git a/core/src/apps/common/payment_request.py b/core/src/apps/common/payment_request.py index a11c92ace4..8336b00c76 100644 --- a/core/src/apps/common/payment_request.py +++ b/core/src/apps/common/payment_request.py @@ -59,12 +59,16 @@ class PaymentRequestVerifier: elif m.refund_memo is not None: memo = m.refund_memo # Unlike in a coin purchase memo, the coin type is implied by the payment request. - check_address_mac(memo.address, memo.mac, slip44_id, keychain) + check_address_mac( + memo.address, memo.mac, slip44_id, memo.address_n, keychain + ) writers.write_uint32_le(self.h_pr, _MEMO_TYPE_REFUND) writers.write_bytes_prefixed(self.h_pr, memo.address.encode()) elif m.coin_purchase_memo is not None: memo = m.coin_purchase_memo - check_address_mac(memo.address, memo.mac, memo.coin_type, keychain) + check_address_mac( + memo.address, memo.mac, memo.coin_type, memo.address_n, keychain + ) writers.write_uint32_le(self.h_pr, _MEMO_TYPE_COIN_PURCHASE) writers.write_uint32_le(self.h_pr, memo.coin_type) writers.write_bytes_prefixed(self.h_pr, memo.amount.encode()) diff --git a/core/src/apps/ethereum/get_address.py b/core/src/apps/ethereum/get_address.py index b6f88c1cb4..ccb2d10c99 100644 --- a/core/src/apps/ethereum/get_address.py +++ b/core/src/apps/ethereum/get_address.py @@ -34,7 +34,7 @@ async def get_address( address = address_from_bytes(node.ethereum_pubkeyhash(), defs.network) slip44_id = address_n[1] # it depends on the network (ETH vs ETC...) - mac = get_address_mac(address, paths.unharden(slip44_id), keychain) + mac = get_address_mac(address, paths.unharden(slip44_id), address_n, keychain) if msg.show_display: coin = "ETH" diff --git a/core/tests/test_apps.bitcoin.address.py b/core/tests/test_apps.bitcoin.address.py index ec8d4d4c07..a6ebbe413d 100644 --- a/core/tests/test_apps.bitcoin.address.py +++ b/core/tests/test_apps.bitcoin.address.py @@ -328,18 +328,20 @@ class TestAddress(unittest.TestCase): VECTORS = ( ( "Bitcoin", + [H_(44), H_(0), H_(0), 1, 0], "1DyHzbQUoQEsLxJn6M7fMD8Xdt1XvNiwNE", - "9cf7c230041d6ed95b8273bd32e023d3f227ec8c44257f6463c743a4b4add028", + "158dd8df21894cc1cb01a33736a50884ecd6d5c2bcc2ffd2398f4d147d19c191", ), ( "Testnet", + [H_(44), H_(0), H_(0), 1, 0], "mm6kLYbGEL1tGe4ZA8xacfgRPdW1NLjCbZ", - "4375089e50423505dc3480e6e85b0ba37a52bd1e009db5d260b6329f22c950d9", + "0b1048fcf82a0a08cffc87a8db2e2512e0d1379eb8d15c9adae8672ba2e00be0", ), ) seed = bip39.seed(" ".join(["all"] * 12), "") - for coin_name, address, mac in VECTORS: + for coin_name, address_n, address, mac in VECTORS: coin = coins.by_name(coin_name) mac = unhexlify(mac) keychain = Keychain( @@ -348,11 +350,13 @@ class TestAddress(unittest.TestCase): [AlwaysMatchingSchema], slip21_namespaces=[[b"SLIP-0024"]], ) - self.assertEqual(get_address_mac(address, coin.slip44, keychain), mac) - check_address_mac(address, mac, coin.slip44, keychain) + self.assertEqual( + get_address_mac(address, coin.slip44, address_n, keychain), mac + ) + check_address_mac(address, mac, coin.slip44, address_n, keychain) with self.assertRaises(wire.DataError): mac = bytes([mac[0] ^ 1]) + mac[1:] - check_address_mac(address, mac, coin.slip44, keychain) + check_address_mac(address, mac, coin.slip44, address_n, keychain) if __name__ == "__main__": diff --git a/tests/device_tests/bitcoin/test_getaddress.py b/tests/device_tests/bitcoin/test_getaddress.py index 5367bcbb3e..5aee372979 100644 --- a/tests/device_tests/bitcoin/test_getaddress.py +++ b/tests/device_tests/bitcoin/test_getaddress.py @@ -146,7 +146,7 @@ def test_address_mac(client: Client): assert resp.address == "1DyHzbQUoQEsLxJn6M7fMD8Xdt1XvNiwNE" assert ( resp.mac.hex() - == "9cf7c230041d6ed95b8273bd32e023d3f227ec8c44257f6463c743a4b4add028" + == "158dd8df21894cc1cb01a33736a50884ecd6d5c2bcc2ffd2398f4d147d19c191" ) resp = btc.get_authenticated_address( @@ -155,7 +155,7 @@ def test_address_mac(client: Client): assert resp.address == "mm6kLYbGEL1tGe4ZA8xacfgRPdW1NLjCbZ" assert ( resp.mac.hex() - == "4375089e50423505dc3480e6e85b0ba37a52bd1e009db5d260b6329f22c950d9" + == "bc555ef1d74814b26a7a2c8039c87414cdd0730027aee8e038ad236ce57875c1" ) # Script type mismatch. @@ -174,7 +174,7 @@ def test_altcoin_address_mac(client: Client): assert resp.address == "LWj6ApswZxay4cJEJES2sGe7fLMLRvvv8h" assert ( resp.mac.hex() - == "eaf47182d7ae17d2046ec2e204bc5b67477db20a5eaea3cec5393c25664bc4d2" + == "13ae756e50735626639dcaf037e65d62b152593e9f088f8bbf452a3c148b5ea6" ) resp = btc.get_authenticated_address( @@ -183,7 +183,7 @@ def test_altcoin_address_mac(client: Client): assert resp.address == "bitcoincash:qzc5q87w069lzg7g3gzx0c8dz83mn7l02scej5aluw" assert ( resp.mac.hex() - == "46d8e369b499a9dc62eb9e4472f4a12640ae0fb7a63c1a4dde6752123b2b7274" + == "d5f54aea2200f50d1a0419d2faae62a82198f27df63ae4eac56d0e625de174f5" ) resp = btc.get_authenticated_address( @@ -192,7 +192,7 @@ def test_altcoin_address_mac(client: Client): assert resp.address == "Fmhtxeh7YdCBkyQF7AQG4QnY8y3rJg89di" assert ( resp.mac.hex() - == "08d67c5f1ee20fd03f3e5aa26f798574716c122238ac280e33a6f3787d531552" + == "c988b6f968a55b5be918d97f0f672acfd3bdbc91c148bc1a04a0ae4e8753464c" ) diff --git a/tests/device_tests/payment_req.py b/tests/device_tests/payment_req.py index 62c1f44047..f83114f0c3 100644 --- a/tests/device_tests/payment_req.py +++ b/tests/device_tests/payment_req.py @@ -71,7 +71,9 @@ def make_payment_request( hash_bytes_prefixed(h_pr, memo.text.encode()) elif isinstance(memo, RefundMemo): msg_memo = messages.RefundMemo( - address=memo.address_resp.address, mac=memo.address_resp.mac + address=memo.address_resp.address, + address_n=memo.address_n, + mac=memo.address_resp.mac, ) msg_memos.append(messages.PaymentRequestMemo(refund_memo=msg_memo)) memo_type = 2 @@ -82,6 +84,7 @@ def make_payment_request( coin_type=memo.slip44, amount=memo.amount, address=memo.address_resp.address, + address_n=memo.address_n, mac=memo.address_resp.mac, ) msg_memos.append(messages.PaymentRequestMemo(coin_purchase_memo=msg_memo))