diff --git a/common/protob/messages-zcash.proto b/common/protob/messages-zcash.proto new file mode 100644 index 00000000000..b3075aefecd --- /dev/null +++ b/common/protob/messages-zcash.proto @@ -0,0 +1,17 @@ +syntax = "proto2"; +package hw.trezor.messages.zcash; + +// Sugar for easier handling in Java +option java_package = "com.satoshilabs.trezor.lib.protobuf"; +option java_outer_classname = "TrezorMessageZcash"; + +/** + * Receiver typecodes for unified addresses. + * see: https://zips.z.cash/zip-0316#encoding-of-unified-addresses + */ +enum ZcashReceiverTypecode { + P2PKH = 0; + P2SH = 1; + SAPLING = 2; + ORCHARD = 3; +} diff --git a/core/.changelog.d/2398.added b/core/.changelog.d/2398.added new file mode 100644 index 00000000000..6966402d82c --- /dev/null +++ b/core/.changelog.d/2398.added @@ -0,0 +1 @@ +Add support for Zcash unified addresses diff --git a/core/src/all_modules.py b/core/src/all_modules.py index 5aeffb3e843..7db7ef75cc6 100644 --- a/core/src/all_modules.py +++ b/core/src/all_modules.py @@ -459,6 +459,8 @@ import trezor.enums.TezosBallotType trezor.enums.TezosContractType import trezor.enums.TezosContractType + trezor.enums.ZcashReceiverTypecode + import trezor.enums.ZcashReceiverTypecode trezor.ui.components.common.webauthn import trezor.ui.components.common.webauthn trezor.ui.components.tt.webauthn @@ -775,10 +777,14 @@ import apps.webauthn.resident_credentials apps.zcash import apps.zcash + apps.zcash.f4jumble + import apps.zcash.f4jumble apps.zcash.hasher import apps.zcash.hasher apps.zcash.signer import apps.zcash.signer + apps.zcash.unified_addresses + import apps.zcash.unified_addresses # generate full alphabet a diff --git a/core/src/apps/zcash/f4jumble.py b/core/src/apps/zcash/f4jumble.py new file mode 100644 index 00000000000..16c437f035c --- /dev/null +++ b/core/src/apps/zcash/f4jumble.py @@ -0,0 +1,58 @@ +""" +Memory-optimized implementation of F4jumble permutation specified in ZIP-316. +specification: https://zips.z.cash/zip-0316#jumbling +reference implementation: https://github.com/zcash/librustzcash/blob/main/components/f4jumble/src/lib.rs +""" + +from micropython import const + +from trezor.crypto.hashlib import blake2b + +HASH_LENGTH = const(64) + + +def xor(target: memoryview, mask: bytes) -> None: + for i in range(len(target)): + target[i] ^= mask[i] + + +def G_round(i: int, left: memoryview, right: memoryview) -> None: + for j in range((len(right) + HASH_LENGTH - 1) // HASH_LENGTH): + mask = blake2b( + personal=b"UA_F4Jumble_G" + bytes([i]) + j.to_bytes(2, "little"), + data=bytes(left), + ).digest() + xor(right[j * HASH_LENGTH : (j + 1) * HASH_LENGTH], mask) + + +def H_round(i: int, left: memoryview, right: memoryview) -> None: + mask = blake2b( + personal=b"UA_F4Jumble_H" + bytes([i, 0, 0]), + outlen=len(left), + data=bytes(right), + ).digest() + xor(left, mask) + + +def f4jumble(message: memoryview) -> None: + assert len(message) >= 22 + left_length = min(HASH_LENGTH, len(message) // 2) + + left = message[:left_length] + right = message[left_length:] + G_round(0, left, right) + H_round(0, left, right) + G_round(1, left, right) + H_round(1, left, right) + + +def f4unjumble(message: memoryview) -> None: + assert len(message) >= 22 + left_length = min(HASH_LENGTH, len(message) // 2) + + left = message[:left_length] + right = message[left_length:] + H_round(1, left, right) + G_round(1, left, right) + H_round(0, left, right) + G_round(0, left, right) diff --git a/core/src/apps/zcash/signer.py b/core/src/apps/zcash/signer.py index c25660a93d7..1d6ec00e0dd 100644 --- a/core/src/apps/zcash/signer.py +++ b/core/src/apps/zcash/signer.py @@ -1,14 +1,17 @@ from micropython import const from typing import TYPE_CHECKING +from trezor.enums import OutputScriptType, ZcashReceiverTypecode as Receiver from trezor.messages import SignTx from trezor.utils import ensure from trezor.wire import DataError, ProcessError +from apps.bitcoin import scripts from apps.bitcoin.common import ecdsa_sign from apps.bitcoin.sign_tx.bitcoinlike import Bitcoinlike from apps.common.writers import write_compact_size, write_uint32_le +from . import unified_addresses from .hasher import ZcashHasher if TYPE_CHECKING: @@ -21,6 +24,7 @@ from trezor.messages import ( PrevTx, TxInput, + TxOutput, ) from apps.bitcoin.keychain import Keychain @@ -110,3 +114,20 @@ def write_tx_footer(self, w: Writer, tx: SignTx | PrevTx) -> None: write_compact_size(w, 0) # nOutputsSapling # serialize Orchard bundle write_compact_size(w, 0) # nActionsOrchard + + def output_derive_script(self, txo: TxOutput) -> bytes: + # unified addresses + if txo.address is not None and txo.address[0] == "u": + assert txo.script_type is OutputScriptType.PAYTOADDRESS + + receivers = unified_addresses.decode(txo.address, self.coin) + if Receiver.P2PKH in receivers: + pubkeyhash = receivers[Receiver.P2PKH] + return scripts.output_script_p2pkh(pubkeyhash) + if Receiver.P2SH in receivers: + scripthash = receivers[Receiver.P2SH] + return scripts.output_script_p2sh(scripthash) + raise DataError("Unified address does not include a transparent receiver.") + + # transparent addresses + return super().output_derive_script(txo) diff --git a/core/src/apps/zcash/unified_addresses.py b/core/src/apps/zcash/unified_addresses.py new file mode 100644 index 00000000000..02bdaebf7b8 --- /dev/null +++ b/core/src/apps/zcash/unified_addresses.py @@ -0,0 +1,122 @@ +""" +Implementation of encoding and decoding of Zcash +unified addresses according to the ZIP-316. +see: https://zips.z.cash/zip-0316 +""" + +from typing import Dict + +from trezor.crypto.bech32 import Encoding, bech32_decode, bech32_encode, convertbits +from trezor.enums import ZcashReceiverTypecode as Typecode +from trezor.utils import BufferReader, empty_bytearray +from trezor.wire import DataError + +from apps.common.coininfo import CoinInfo +from apps.common.readers import read_compact_size +from apps.common.writers import write_bytes_fixed, write_compact_size + +from .f4jumble import f4jumble, f4unjumble + + +def receiver_length(typecode: int): + """Byte length of a receiver.""" + if typecode == Typecode.P2PKH: + return 20 + if typecode == Typecode.P2SH: + return 20 + if typecode == Typecode.SAPLING: + return 43 + if typecode == Typecode.ORCHARD: + return 43 + raise ValueError + + +def prefix(coin: CoinInfo): + """Prefix for a unified address.""" + if coin.coin_name == "Zcash": + return "u" + if coin.coin_name == "Zcash Testnet": + return "utest" + raise ValueError + + +def padding(hrp: str) -> bytes: + assert len(hrp) <= 16 + return bytes(hrp, "utf8") + bytes(16 - len(hrp)) + + +def encode(receivers: Dict[Typecode, bytes], coin: CoinInfo) -> str: + # multiple transparent receivers forbidden + assert not (Typecode.P2PKH in receivers and Typecode.P2SH in receivers) + + length = 16 # 16 bytes for padding + for typecode in receivers.keys(): + length += 2 # typecode (1 byte) + length (1 byte) + length += receiver_length(typecode) + + w = empty_bytearray(length) + + # receivers in decreasing order + receivers_list = list(receivers.items()) + receivers_list.sort(reverse=True) + + for (typecode, raw_bytes) in receivers_list: + write_compact_size(w, typecode) + write_compact_size(w, receiver_length(typecode)) + write_bytes_fixed(w, raw_bytes, receiver_length(typecode)) + + hrp = prefix(coin) + write_bytes_fixed(w, padding(hrp), 16) + f4jumble(memoryview(w)) + converted = convertbits(w, 8, 5) + return bech32_encode(hrp, converted, Encoding.BECH32M) + + +def decode(addr_str: str, coin: CoinInfo) -> Dict[int, bytes]: + (hrp, data, encoding) = bech32_decode(addr_str, max_bech_len=1000) + if (hrp, data, encoding) == (None, None, None): + raise DataError("Bech32m decoding failed.") + assert hrp is not None # to satisfy typecheckers + assert data is not None # to satisfy typecheckers + assert encoding is not None # to satisfy typecheckers + if hrp != prefix(coin): + raise DataError("Unexpected address prefix.") + if encoding != Encoding.BECH32M: + raise DataError("Bech32m encoding required.") + + decoded = bytearray(convertbits(data, 5, 8, False)) + f4unjumble(memoryview(decoded)) + + # check trailing padding bytes + if decoded[-16:] != padding(hrp): + raise DataError("Invalid padding bytes") + + r = BufferReader(decoded[:-16]) + + last_typecode = None + receivers = {} + while r.remaining_count() > 0: + typecode = read_compact_size(r) + if typecode in receivers: + raise DataError("Duplicated typecode") + if typecode > 0x02000000: + raise DataError("Invalid typecode") + if last_typecode is not None and typecode > last_typecode: + raise DataError("Invalid receivers order") + last_typecode = typecode + + length = read_compact_size(r) + # if the typecode of the receiver is known, then verify receiver length + try: + expected_length = receiver_length(typecode) + if length != expected_length: + raise DataError("Unexpected receiver length") + except ValueError: + pass + + if r.remaining_count() < length: + raise DataError("Invalid receiver length") + + receivers[typecode] = r.read(length) + + return receivers diff --git a/core/src/trezor/enums/ZcashReceiverTypecode.py b/core/src/trezor/enums/ZcashReceiverTypecode.py new file mode 100644 index 00000000000..62d176aeb54 --- /dev/null +++ b/core/src/trezor/enums/ZcashReceiverTypecode.py @@ -0,0 +1,8 @@ +# Automatically generated by pb2py +# fmt: off +# isort:skip_file + +P2PKH = 0 +P2SH = 1 +SAPLING = 2 +ORCHARD = 3 diff --git a/core/src/trezor/enums/__init__.py b/core/src/trezor/enums/__init__.py index 9b0045bb0e2..5b05b85e651 100644 --- a/core/src/trezor/enums/__init__.py +++ b/core/src/trezor/enums/__init__.py @@ -504,3 +504,9 @@ class TezosBallotType(IntEnum): Yay = 0 Nay = 1 Pass = 2 + + class ZcashReceiverTypecode(IntEnum): + P2PKH = 0 + P2SH = 1 + SAPLING = 2 + ORCHARD = 3 diff --git a/core/src/trezor/messages.py b/core/src/trezor/messages.py index 295d82e3af5..66a4a65746e 100644 --- a/core/src/trezor/messages.py +++ b/core/src/trezor/messages.py @@ -57,6 +57,7 @@ def __getattr__(name: str) -> Any: from trezor.enums import TezosBallotType # noqa: F401 from trezor.enums import TezosContractType # noqa: F401 from trezor.enums import WordRequestType # noqa: F401 + from trezor.enums import ZcashReceiverTypecode # noqa: F401 class BinanceGetAddress(protobuf.MessageType): address_n: "list[int]" diff --git a/core/tests/test_apps.zcash.f4jumble.py b/core/tests/test_apps.zcash.f4jumble.py new file mode 100644 index 00000000000..2c502731a7e --- /dev/null +++ b/core/tests/test_apps.zcash.f4jumble.py @@ -0,0 +1,30 @@ +from common import * + +from apps.zcash.f4jumble import f4jumble, f4unjumble + +@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin") +class TestZcashF4jumble(unittest.TestCase): + def test_f4jumble(self): + #source: https://github.com/zcash/librustzcash/blob/main/components/f4jumble/src/test_vectors.rs + TEST_VECTORS = [ + {'jumbled': unhexlify('0304d029141b995da5387c125970673504d6c764d91ea6c082123770c7139ccd88ee27368cd0c0921a0444c8e5858d22'), + 'normal': unhexlify('5d7a8f739a2d9e945b0ce152a8049e294c4d6e66b164939daffa2ef6ee6921481cdd86b3cc4318d9614fc820905d042b')}, + {'jumbled': unhexlify('5271fa3321f3adbcfb075196883d542b438ec6339176537daf859841fe6a56222bff76d1662b5509a9e1079e446eeedd2e683c31aae3ee1851d7954328526be1'), + 'normal': unhexlify('b1ef9ca3f24988c7b3534201cfb1cd8dbf69b8250c18ef41294ca97993db546c1fe01f7e9c8e36d6a5e29d4e30a73594bf5098421c69378af1e40f64e125946f')}, + {'jumbled': unhexlify('498cf1b1ba6f4577effe64151d67469adc30acc325e326207e7d78487085b4162669f82f02f9774c0cc26ae6e1a76f1e266c6a9a8a2f4ffe8d2d676b1ed71cc47195a3f19208998f7d8cdfc0b74d2a96364d733a62b4273c77d9828aa1fa061588a7c4c88dd3d3dde02239557acfaad35c55854f4541e1a1b3bc8c17076e7316'), + 'normal': unhexlify('62c2fa7b2fecbcb64b6968912a6381ce3dc166d56a1d62f5a8d7551db5fd9313e8c7203d996af7d477083756d59af80d06a745f44ab023752cb5b406ed8985e18130ab33362697b0e4e4c763ccb8f676495c222f7fba1e31defa3d5a57efc2e1e9b01a035587d5fb1a38e01d94903d3c3e0ad3360c1d3710acd20b183e31d49f')}, + {'jumbled': unhexlify('7508a3a146714f229db91b543e240633ed57853f6451c9db6d64c6e86af1b88b28704f608582c53c51ce7d5b8548827a971d2b98d41b7f6258655902440cd66ee11e84dbfac7d2a43696fd0468810a3d9637c3fa58e7d2d341ef250fa09b9fb71a78a41d389370138a55ea58fcde779d714a04e0d30e61dc2d8be0da61cd684509'), + 'normal': unhexlify('25c9a138f49b1a537edcf04be34a9851a7af9db6990ed83dd64af3597c04323ea51b0052ad8084a8b9da948d320dadd64f5431e61ddf658d24ae67c22c8d1309131fc00fe7f235734276d38d47f1e191e00c7a1d48af046827591e9733a97fa6b679f3dc601d008285edcbdae69ce8fc1be4aac00ff2711ebd931de518856878f7')}, + {'jumbled': unhexlify('5139912fe8b95492c12731995a0f4478dbeb81ec36653a21bc80d673f3c6a0feef70b6c566f9d34bb726c098648382d105afb19b2b8486b73cbd47a17a0d2d1fd593b14bb9826c5d114b850c6f0cf3083a6f61e38e42713a37ef7997ebd2b376c8a410d797b3932e5a6e39e726b2894ce79604b4ae3c00acaea3be2c1dfe697fa644755102cf9ad78794d0594585494fe38ab56fa6ef3271a68a33481015adf3944c115311421a7dc3ce73ef2abf47e18a6aca7f9dd25a85ce8dbd6f1ad89c8d'), + 'normal': unhexlify('3476f21a482ec9378365c8f7393c94e2885315eb4671098b79535e790fe53e29fef2b3766697ac32b4f473f468a008e72389fc03880d780cb07fcfaabe3f1a84b27db59a4a153d882d2b2103596555ed9494c6ac893c49723833ec8926c1039586a7afcf4a0d9c731e985d99589c8bb838e8aaf745533ed9e8ae3a1cd074a51a20da8aba18d1dbebbc862ded42435e92476930d069896cff30eb414f727b89e001afa2fb8dc3436d75a4a6f26572504b192232ecb9f0c02411e52596bc5e9045')} + ] + + for tv in TEST_VECTORS: + message = memoryview(bytearray(tv["normal"])) + f4jumble(message) + self.assertEqual(bytes(message), tv["jumbled"]) + f4unjumble(message) + self.assertEqual(bytes(message), tv["normal"]) + +if __name__ == '__main__': + unittest.main() diff --git a/core/tests/test_apps.zcash.unified_addresses.py b/core/tests/test_apps.zcash.unified_addresses.py new file mode 100644 index 00000000000..143b1a6fc14 --- /dev/null +++ b/core/tests/test_apps.zcash.unified_addresses.py @@ -0,0 +1,91 @@ +from common import * + +from apps.common import coininfo +from apps.zcash import unified_addresses +from trezor.enums.ZcashReceiverTypecode import P2PKH, P2SH, SAPLING, ORCHARD + +# source: https://github.com/jarys/zcash-test-vectors/blob/trezor/unified_address.py +TESTVECTORS_UNIFIED = [ + { + "receivers": { + ORCHARD: unhexlify("8ff3386971cb64b8e7789908dd8ebd7de92a68e586a34db8fea999efd2016fae76750afae7ee941646bcb9"), + }, + "address": "u1qylzskzykhk5l5vk6zlyqqruvskzv74hk20lmrllzy3vdz6pvny5t9zwlrm86ukw77y5pu8uep2m33s7sc7gn6aq0jm9neg5tsektyn9", + }, + { + "receivers": { + ORCHARD: unhexlify("56e84b1adc9423c3676c0463f7125df4836fd2816b024ee70efe09fb9a7b3863c6eacdf95e03894950692c"), + }, + "address": "u17j4lvw84jd238ev9ukr0lvqhv4z32v98pxcglctaj3aqfqj7rr2wwvh73247ekczw4smyrvm2wf2v5nfxvn3sl0ycc6w4455yg49yf2m", + }, + { + "receivers": { + SAPLING: unhexlify("3484fc3fce5048621e1e164acf29528ce2fa39c9ca97a8114fb3deba18068a68d487c5d063a4a3ce614ec2"), + ORCHARD: unhexlify("513d01f7b5d37f5433c336af84a342af9d833bc01635ceb7a2139d23510ae4f26e42fc79f4f6c467154115"), + }, + "address": "u1xnkv2m5x02kc5h4fqv3yk0s8z6k4ya7n92an98gsk5dc467ny0rkwevvq3nw7sjll4hvhr2u58xw4cryguzeq0h5yzu4xk3esxgl3glqqth5jqse0za8w5y2ym93ht3zv8wvyw7kvs7ejuvk6xkc375j77ukuzwxkznhuy3x6yvxeq5c", + }, + { + "receivers": { + P2SH: unhexlify("3dc166d56a1d62f5a8d7551db5fd9313e8c7203d"), + ORCHARD: unhexlify("1038c28960629162fdd46aca1eb067a1de41e4385d19f92b53341de8b3eeecb0afcbc130808bf42e8de128"), + }, + "address": "u1qld9m8deq56mpvma9gpvm5ths62f9fcqa9udwn7jvjts3msldpet509nnuguvl6zq0atz9s8t53wm207zq2xyhawanjg4xt2yzqvuwt6kzx2lzzmk9w45mlh6vmacmywmeh6c584t8u", + }, + { + "receivers": { + P2SH: unhexlify("362697b0e4e4c763ccb8f676495c222f7fba1e31"), + ORCHARD: unhexlify("3a6f91fb15f130f2778292577d9f32e137ab22c51fafc959b16d42578387b297a124a53bd9b32af59d7f1f"), + }, + "address": "u1rw3fjc02enpe09gx57ada7rdvy2xy8mxs7kalvq2uhyg2qqmt77ycealmp30wujndt02cdeffrrrgg6d858awwxadspg8yqwmf56w04tqagt0chhg9z7s6h7ndwn92yjnrwky7207zz", + }, + { + "receivers": { + P2SH: unhexlify("acd20b183e31d49f25c9a138f49b1a537edcf04b"), + SAPLING: unhexlify("24e657b33ec5d1679d072ba830c1c4412bc775c520e2a1ca9617680397c6708effd84b61fb950222e62516"), + ORCHARD: unhexlify("a391f728b786bbd102ddbfd1318ca4e03e6045c23c3c4826686696c7ba949216adf0e06e6b4bd16b28ac39"), + }, + "address": "u1j7zuzyc5n2mvp3tvgdztsx0gz3er4n5rl7jvre3hh6tgusnft5lpsme3ldndk482v9t3pcl9umkj2eucnua80vqk9jmelg7had8x5ryqlp2et48tmq9qu824xankrktyms8x6x4dnfyan98fgl7rr7wfxsmrsxqs2x7qwd57qltf5rlrdecevm4tjmz8m6kqkvjlqj3s5uywcr6za7f", + }, + { + "receivers": { + P2PKH: unhexlify("47f1e191e00c7a1d48af046827591e9733a97fa6"), + ORCHARD: unhexlify("6424f71a3ad197426498f4eccb6a5780204237987232bc098f89acc475c3f74bd69e2f35d44736f48f3c14"), + }, + "address": "u1kts2hcsm3nnzm290gh29ga32eg2euvkp9l20yhptwtzqlp0c6tskc59yeywq4e6y64uggzc2pzf9x8yuc805xdjfp0waezff8gzmaxc8rdmp9wr82r78ts2nwk24yrakua84sll8z9h", + }, + { + "receivers": { + P2PKH: unhexlify("f73476f21a482ec9378365c8f7393c94e2885315"), + ORCHARD: unhexlify("b4eed75afb7f6b02b626ebb20b0b211507fda98d12229395fdf0d67b6b40d8496ee159251933bbbd7b8228"), + }, + "address": "u1hn3fgwa6m4h3uulnrlz78x74khmfda20jej5495pxhcteadp8xrpl9z644e7pf3pk4a3wuld24fat7r2z33wxqfaeakhgl9nssnh0sg9ggyadr4696n4p6mdrfnzum5luxs42uc7x7m", + }, + { + "receivers": { + P2PKH: unhexlify("03880d780cb07fcfaabe3f1a84b27db59a4a153d"), + SAPLING: unhexlify("137419a9a2123e27dce5836ac3ecdcf9d628b25a744248eae8cf0223f5c444c55650df2301d1417ed3ca59"), + ORCHARD: unhexlify("db8c305524bc0deaa85d9704ea8c1320ffbbadfe96f0c6ff16b607111b5583bfb6f1ea45275ef2aa2d879b"), + }, + "address": "u1wg3qy8cw9zfs6axtg6204ftf2c6gkx2mz8xksz83paxfaqcc52jfedfz0m2sk50a7ujp6gtlvp7572csfvz4t4rtv9pzfxa0dk9d8van09dymf5s2q3a2ccsevrscm2z6hw9fqw3swjjlcqh3muq703mlrmf5rstd476ngzq9dr4y0dwys4hnjz58wx9erhz7ycmuv7ynnjt2sm2epy", + }, +] + +COIN = coininfo.by_name("Zcash") + + +@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin") +class TestZcashAddress(unittest.TestCase): + def test_encode_unified(self): + for tv in TESTVECTORS_UNIFIED: + ua = unified_addresses.encode(tv["receivers"], COIN) + self.assertEqual(ua, tv["address"]) + + def test_decode_unified(self): + for tv in TESTVECTORS_UNIFIED: + receivers = unified_addresses.decode(tv["address"], COIN) + self.assertEqual(receivers, tv["receivers"]) + + +if __name__ == '__main__': + unittest.main() diff --git a/python/src/trezorlib/messages.py b/python/src/trezorlib/messages.py index 76a8a4d38a0..66d45f73bd5 100644 --- a/python/src/trezorlib/messages.py +++ b/python/src/trezorlib/messages.py @@ -551,6 +551,13 @@ class TezosBallotType(IntEnum): Pass = 2 +class ZcashReceiverTypecode(IntEnum): + P2PKH = 0 + P2SH = 1 + SAPLING = 2 + ORCHARD = 3 + + class BinanceGetAddress(protobuf.MessageType): MESSAGE_WIRE_TYPE = 700 FIELDS = { diff --git a/tests/device_tests/zcash/test_sign_tx.py b/tests/device_tests/zcash/test_sign_tx.py index c23aa5e6017..3ab04a61b86 100644 --- a/tests/device_tests/zcash/test_sign_tx.py +++ b/tests/device_tests/zcash/test_sign_tx.py @@ -271,6 +271,63 @@ def test_one_two(client: Client): ) +@pytest.mark.skip_t1 +def test_unified_address(client: Client): + # identical to the test_one_two + # but receiver address is unified with a sapling address + inp1 = messages.TxInputType( + # tmQoJ3PTXgQLaRRZZYT6xk8XtjRbr2kCqwu + address_n=parse_path("m/44h/1h/0h/0/0"), + amount=4_134_720, + prev_hash=TXHASH_c5309b, + prev_index=0, + ) + + out1 = messages.TxOutputType( + # m/44h/1h/0h/0/8 + some Sapling address + address="utest1ehsqx89s6rm02dg3yw8yr9203d9gruazyuqkc4f8p78j9twyd8geum5h47j4fq7mjklgt2ynvkmlphg2yjne6quyqysuyy7286neetcsfm5rzvvwnmtc593k8qw6zrtr5ypz6qculv5", + amount=1_000_000, + script_type=messages.OutputScriptType.PAYTOADDRESS, + ) + + out2 = messages.TxOutputType( + address_n=parse_path("m/44h/1h/0h/0/0"), + amount=4_134_720 - 1_000_000 - 2_000, + script_type=messages.OutputScriptType.PAYTOADDRESS, + ) + + with client: + client.set_expected_responses( + [ + request_input(0), + request_output(0), + messages.ButtonRequest(code=B.ConfirmOutput), + request_output(1), + messages.ButtonRequest(code=B.SignTx), + request_input(0), + request_output(0), + request_output(1), + request_finished(), + ] + ) + + _, serialized_tx = btc.sign_tx( + client, + "Zcash Testnet", + [inp1], + [out1, out2], + version=5, + version_group_id=VERSION_GROUP_ID, + branch_id=BRANCH_ID, + ) + + # Accepted by network: txid = d8cfa377012ca0b8d856586693b530835bf2fa14add0380e24ec6755bed5b931 + assert ( + serialized_tx.hex() + == "050000800a27a726b4d0d6c2000000000000000001bf79ce8a0403de4775d3538c670de802af72e8961c8b9174f36b8fa1d69b30c5000000006b483045022100be78eccf801dda4dd33f9d4e04c2aae01022869d1d506d51669204ec269d71a90220394a51838faf40176058cf45fe7032be9c5c942e21aff35d7dbe4b96ab5e0a500121030e669acac1f280d1ddf441cd2ba5e97417bf2689e4bbec86df4f831bf9f7ffd0ffffffff0240420f00000000001976a9141efeae5c937bfc7f095a06aabdb5476a5d6d19db88ac30cd2f00000000001976a914a579388225827d9f2fe9014add644487808c695d88ac000000" + ) + + @pytest.mark.skip_t1 def test_external_presigned(client: Client): inp1 = messages.TxInputType( diff --git a/tests/ui_tests/fixtures.json b/tests/ui_tests/fixtures.json index 4a3fd0f77da..3172327e83d 100644 --- a/tests/ui_tests/fixtures.json +++ b/tests/ui_tests/fixtures.json @@ -1647,6 +1647,7 @@ "TT_zcash-test_sign_tx.py::test_spend_multisig": "1f7cfe70831cffb4c076d0b4778704a982b37be4cc114e2cd9de85ee3173430e", "TT_zcash-test_sign_tx.py::test_spend_v4_input": "235910c3aa36150bb7012d21d52acdca347a2a216bb09a0588910329e464c8f0", "TT_zcash-test_sign_tx.py::test_spend_v5_input": "dd9b4f0050836b7d881d9be798d2f4d972e39f460b8acc3e4e1c038ba754ae7c", +"TT_zcash-test_sign_tx.py::test_unified_address": "90c260abe850e4821f479a489e79efaa75b7cf9af3ac74752a3cf7099fef6b31", "TT_zcash-test_sign_tx.py::test_version_group_id_missing": "c09de07fbbf1e047442180e2facb5482d06a1a428891b875b7dd93c9e4704ae1", "TTui2_binance-test_get_address.py::test_binance_get_address[m-44h-714h-0h-0-0-bnb1hgm0p7khfk85zpz-68e2cb5a": "577553344793d14026b1d3c7f6185a4858378476dafee67a958319bd8d8f58ac", "TTui2_binance-test_get_address.py::test_binance_get_address[m-44h-714h-0h-0-1-bnb1egswqkszzfc2uq7-1adfb691": "9fedc9ad26c5573b264f28f70c83b8baf6503a71f2296de27be6c6c9166d5488",