From f544513600de5753fab32eb8fde3b6fba0d79ad0 Mon Sep 17 00:00:00 2001 From: Tomas Krnak Date: Tue, 20 Sep 2022 14:40:12 +0200 Subject: [PATCH] feat(core): add Zcash shielded payments --- common/protob/messages-binance.proto | 1 - common/protob/messages-bitcoin.proto | 22 +- common/protob/messages-zcash.proto | 100 ++++++ common/protob/messages.proto | 12 + core/Makefile | 3 +- core/SConscript.firmware | 2 + core/SConscript.unix | 2 + core/embed/firmware/memory_T.ld | 4 +- .../rust/src/zcash_primitives/pallas/mod.rs | 40 +-- .../src/zcash_primitives/pallas/scalar.rs | 13 +- core/mocks/generated/trezorpallas.pyi | 35 ++- core/src/all_modules.py | 48 +++ core/src/apps/bitcoin/sign_tx/__init__.py | 5 +- core/src/apps/bitcoin/sign_tx/approvers.py | 5 +- core/src/apps/bitcoin/sign_tx/helpers.py | 2 + core/src/apps/bitcoin/sign_tx/progress.py | 5 +- core/src/apps/workflow_handlers.py | 8 + core/src/apps/zcash/approver.py | 35 +++ core/src/apps/zcash/debug.py | 43 +++ core/src/apps/zcash/get_address.py | 82 +++++ core/src/apps/zcash/hasher.py | 87 +++-- core/src/apps/zcash/layout.py | 144 +++++++++ core/src/apps/zcash/orchard/__init__.py | 0 core/src/apps/zcash/orchard/accumulator.py | 34 ++ .../src/apps/zcash/orchard/crypto/__init__.py | 0 core/src/apps/zcash/orchard/crypto/address.py | 41 +++ core/src/apps/zcash/orchard/crypto/builder.py | 133 ++++++++ core/src/apps/zcash/orchard/crypto/ff1.py | 90 ++++++ .../apps/zcash/orchard/crypto/generators.py | 35 +++ core/src/apps/zcash/orchard/crypto/keys.py | 114 +++++++ core/src/apps/zcash/orchard/crypto/note.py | 96 ++++++ .../zcash/orchard/crypto/note_encryption.py | 118 +++++++ .../apps/zcash/orchard/crypto/redpallas.py | 44 +++ .../apps/zcash/orchard/crypto/sinsemilla.py | 59 ++++ core/src/apps/zcash/orchard/crypto/utils.py | 76 +++++ core/src/apps/zcash/orchard/get_fvk.py | 19 ++ core/src/apps/zcash/orchard/get_ivk.py | 19 ++ core/src/apps/zcash/orchard/keychain.py | 105 +++++++ core/src/apps/zcash/orchard/random.py | 74 +++++ core/src/apps/zcash/orchard/signer.py | 296 ++++++++++++++++++ core/src/apps/zcash/signer.py | 55 +++- core/src/trezor/crypto/curve.py | 73 +++++ core/src/trezor/crypto/mock_bip340.py | 68 ++++ core/src/trezor/enums/MessageType.py | 10 + core/src/trezor/enums/RequestType.py | 3 + core/src/trezor/enums/ZcashSignatureType.py | 6 + core/src/trezor/enums/__init__.py | 17 + core/src/trezor/messages.py | 167 ++++++++++ core/tests/common.py | 17 + core/tests/test_apps.zcash.f4jumble.py | 31 +- .../test_apps.zcash.orchard.crypto.ff1.py | 56 ++++ ...st_apps.zcash.orchard.crypto.generators.py | 39 +++ .../test_apps.zcash.orchard.crypto.keys.py | 70 +++++ ...ps.zcash.orchard.crypto.note_encryption.py | 45 +++ ...st_apps.zcash.orchard.crypto.sinsemilla.py | 68 ++++ .../test_apps.zcash.unified_addresses.py | 99 +++--- core/tests/test_apps.zcash.zip244.py | 246 ++++++++++++++- python/src/trezorlib/cli/trezorctl.py | 2 + python/src/trezorlib/cli/zcash.py | 90 ++++++ python/src/trezorlib/messages.py | 199 ++++++++++++ python/src/trezorlib/zcash.py | 200 ++++++++++++ 61 files changed, 3460 insertions(+), 152 deletions(-) create mode 100644 common/protob/messages-zcash.proto create mode 100644 core/src/apps/zcash/approver.py create mode 100644 core/src/apps/zcash/debug.py create mode 100644 core/src/apps/zcash/get_address.py create mode 100644 core/src/apps/zcash/layout.py create mode 100644 core/src/apps/zcash/orchard/__init__.py create mode 100644 core/src/apps/zcash/orchard/accumulator.py create mode 100644 core/src/apps/zcash/orchard/crypto/__init__.py create mode 100644 core/src/apps/zcash/orchard/crypto/address.py create mode 100644 core/src/apps/zcash/orchard/crypto/builder.py create mode 100644 core/src/apps/zcash/orchard/crypto/ff1.py create mode 100755 core/src/apps/zcash/orchard/crypto/generators.py create mode 100755 core/src/apps/zcash/orchard/crypto/keys.py create mode 100644 core/src/apps/zcash/orchard/crypto/note.py create mode 100755 core/src/apps/zcash/orchard/crypto/note_encryption.py create mode 100644 core/src/apps/zcash/orchard/crypto/redpallas.py create mode 100644 core/src/apps/zcash/orchard/crypto/sinsemilla.py create mode 100644 core/src/apps/zcash/orchard/crypto/utils.py create mode 100644 core/src/apps/zcash/orchard/get_fvk.py create mode 100644 core/src/apps/zcash/orchard/get_ivk.py create mode 100644 core/src/apps/zcash/orchard/keychain.py create mode 100644 core/src/apps/zcash/orchard/random.py create mode 100644 core/src/apps/zcash/orchard/signer.py create mode 100644 core/src/trezor/crypto/mock_bip340.py create mode 100644 core/src/trezor/enums/ZcashSignatureType.py create mode 100644 core/tests/test_apps.zcash.orchard.crypto.ff1.py create mode 100644 core/tests/test_apps.zcash.orchard.crypto.generators.py create mode 100644 core/tests/test_apps.zcash.orchard.crypto.keys.py create mode 100644 core/tests/test_apps.zcash.orchard.crypto.note_encryption.py create mode 100644 core/tests/test_apps.zcash.orchard.crypto.sinsemilla.py create mode 100644 python/src/trezorlib/cli/zcash.py create mode 100644 python/src/trezorlib/zcash.py diff --git a/common/protob/messages-binance.proto b/common/protob/messages-binance.proto index 531a2bea36c..76594714346 100644 --- a/common/protob/messages-binance.proto +++ b/common/protob/messages-binance.proto @@ -152,4 +152,3 @@ message BinanceSignedTx { required bytes signature = 1; required bytes public_key = 2; } - diff --git a/common/protob/messages-bitcoin.proto b/common/protob/messages-bitcoin.proto index 5c4b639df67..9a9bbe6eb6e 100644 --- a/common/protob/messages-bitcoin.proto +++ b/common/protob/messages-bitcoin.proto @@ -9,6 +9,7 @@ option (include_in_bitcoin_only) = true; import "messages.proto"; import "messages-common.proto"; +import "messages-zcash.proto"; /** * Type of script which will be used for transaction input @@ -197,6 +198,17 @@ message SignTx { optional uint32 branch_id = 10; // only for Zcash, BRANCH_ID optional AmountUnit amount_unit = 11 [default=BITCOIN]; // show amounts in optional bool decred_staking_ticket = 12 [default=false]; // only for Decred, this is signing a ticket purchase + optional ZcashOrchardBundleInfo orchard = 13; // only for Zcash + + /** + * Informations about shielded part of a Zcash transaction. + */ + message ZcashOrchardBundleInfo { + required uint32 inputs_count = 1; // number of Orchard inputs + required uint32 outputs_count = 2; // number of Orchard outputs + required bytes anchor = 3; // a root of Orchard Merkle tree + optional uint32 account = 7 [default = 0]; + } } /** @@ -211,6 +223,8 @@ message SignTx { * @next TxAckPrevOutput * @next TxAckPrevExtraData * @next TxAckPaymentRequest + * @next ZcashOrchardInput + * @next ZcashOrchardOutput */ message TxRequest { optional RequestType request_type = 1; // what should be filled in TxAck message? @@ -228,6 +242,9 @@ message TxRequest { TXORIGINPUT = 5; TXORIGOUTPUT = 6; TXPAYMENTREQ = 7; + TXORCHARDOUTPUT = 8; + TXORCHARDINPUT = 9; + NO_OP = 10; } /** * Structure representing request details @@ -245,6 +262,10 @@ message TxRequest { optional uint32 signature_index = 1; // 'signature' field contains signed input of this index optional bytes signature = 2; // signature of the signature_index input optional bytes serialized_tx = 3; // part of serialized and signed transaction + + optional uint32 signature_type = 4; // currently for Zcash only + + optional bytes zcash_shielding_seed = 8; // for Zcash only } } @@ -607,4 +628,3 @@ message AuthorizeCoinJoin { optional InputScriptType script_type = 7 [default=SPENDADDRESS]; // used to distinguish between various address formats (non-segwit, segwit, etc.) optional AmountUnit amount_unit = 8 [default=BITCOIN]; // show amounts in } - diff --git a/common/protob/messages-zcash.proto b/common/protob/messages-zcash.proto new file mode 100644 index 00000000000..0a5c04a7885 --- /dev/null +++ b/common/protob/messages-zcash.proto @@ -0,0 +1,100 @@ +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"; + + +enum ZcashSignatureType { + reserved 1, 2, 4; + TRANSPARENT = 0; + // SAPLING_SPEND_AUTH = 1; + // SAPLING_BINDING = 2; + ORCHARD_SPEND_AUTH = 3; + // ORCHARD_BINDING = 4; +} + +/** + * Request: Ask device for Orchard Full Viewing Key. + * @start + * @next Failure + * @next ZcashFullViewingKey + */ +message ZcashGetFullViewingKey { + optional string coin_name = 2 [default = "Zcash"]; + repeated uint32 z_address_n = 3; // z-address ZIP 32 path +} + +/** + * Response: Contains raw Orchard Full Viewing Key. + * @end + */ +message ZcashFullViewingKey { + required bytes fvk = 1; +} + +/** + * Request: Ask device for Orchard Incoming Viewing Key. + * @start + * @next Failure + * @next ZcashIncomingViewingKey + */ +message ZcashGetIncomingViewingKey { + optional string coin_name = 1 [default = "Zcash"]; + repeated uint32 z_address_n = 2; // z-address ZIP 32 path +} + +/** + * Response: Contains raw Orchard Incoming Viewing Key. + * @end + */ +message ZcashIncomingViewingKey { + required bytes ivk = 1; +} + +/** + * Request: Ask device for Unified Address. + * @start + * @next Failure + * @next ZcashAddress + */ +message ZcashGetAddress { + optional string coin_name = 1 [default = "Zcash"]; + repeated uint32 t_address_n = 2; // t-address BIP 32 path (P2PKH) + repeated uint32 z_address_n = 3; // z-address ZIP 32 path (Orchard) + optional uint64 diversifier_index = 4 [default = 0]; // z-address diversifier index + optional bool show_display = 5 [default = false]; // Optionally show on display before sending the result +} + +/** + * Response: Contains Zcash diversified payment address derived from device private seed + * @end + */ +message ZcashAddress { + optional string address = 1; +} + +/** + * Request: Specify transaction Orchard input. + * @next TxRequest + */ +message ZcashOrchardInput { + required bytes recipient = 1; + required uint64 value = 2; + required bytes rho = 3; + required bytes rseed = 4; +} + +/** + * Request: Specify transaction Orchard output. + * Let the `address` and `memo` fields empty for change outputs. + * @next TxRequest + */ +message ZcashOrchardOutput { + optional string address = 1; // for outgoing transfers + required uint64 amount = 2; + optional string memo = 3; // an optional message for a recepient +} + +message ZcashAck {} diff --git a/common/protob/messages.proto b/common/protob/messages.proto index af3991ca1c1..a0aed9a6416 100644 --- a/common/protob/messages.proto +++ b/common/protob/messages.proto @@ -357,4 +357,16 @@ enum MessageType { MessageType_WebAuthnCredentials = 801 [(wire_out) = true]; MessageType_WebAuthnAddResidentCredential = 802 [(wire_in) = true]; MessageType_WebAuthnRemoveResidentCredential = 803 [(wire_in) = true]; + + // Zcash + MessageType_ZcashGetFullViewingKey = 893 [(wire_in) = true]; + MessageType_ZcashFullViewingKey = 894 [(wire_out) = true]; + MessageType_ZcashGetIncomingViewingKey = 895 [(wire_in) = true]; + MessageType_ZcashIncomingViewingKey = 896[(wire_out) = true]; + MessageType_ZcashGetAddress = 897 [(wire_in) = true]; + MessageType_ZcashAddress = 898 [(wire_out) = true]; + MessageType_ZcashOrchardBundleInfo = 899 [(wire_in) = true]; + MessageType_ZcashOrchardInput = 900 [(wire_in) = true]; + MessageType_ZcashOrchardOutput = 901 [(wire_in) = true]; + MessageType_ZcashAck = 907 [(wire_in) = true]; } diff --git a/core/Makefile b/core/Makefile index 0c5c149f194..39ca0c1c385 100644 --- a/core/Makefile +++ b/core/Makefile @@ -46,7 +46,8 @@ FIRMWARE_P1_MAXSIZE = 786432 FIRMWARE_P2_MAXSIZE = 917504 FIRMWARE_MAXSIZE = 1703936 -CFLAGS += -DSCM_REVISION='\"$(shell git rev-parse HEAD | sed 's:\(..\):\\x\1:g')\"' +#CFLAGS += -DSCM_REVISION='\"$(shell git rev-parse HEAD | sed 's:\(..\):\\x\1:g')\"' +CFLAGS += -DSCM_REVISION='\"\x89\xfb\x6d\xa9\x11\x5e\x0b\x37\x4c\xea\x7f\x1c\xfa\xae\x2e\x20\x37\xdb\xdf\x92\"' TESTPATH = $(CURDIR)/../tests diff --git a/core/SConscript.firmware b/core/SConscript.firmware index 7eca682cb1d..534254b740d 100644 --- a/core/SConscript.firmware +++ b/core/SConscript.firmware @@ -677,6 +677,8 @@ if FROZEN: SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/enums/Tezos*.py')) SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/zcash/*.py')) + SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/zcash/*/*.py')) + SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/zcash/*/*/*.py')) SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/webauthn/*.py')) diff --git a/core/SConscript.unix b/core/SConscript.unix index 0a12a995c9e..ed3f361dc5e 100644 --- a/core/SConscript.unix +++ b/core/SConscript.unix @@ -633,6 +633,8 @@ if FROZEN: SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/enums/Tezos*.py')) SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/zcash/*.py')) + SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/zcash/*/*.py')) + SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/zcash/*/*/*.py')) SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/webauthn/*.py')) diff --git a/core/embed/firmware/memory_T.ld b/core/embed/firmware/memory_T.ld index e50df36cd04..b910d2672c7 100644 --- a/core/embed/firmware/memory_T.ld +++ b/core/embed/firmware/memory_T.ld @@ -45,12 +45,12 @@ SECTIONS { .flash2 : ALIGN(512) { build/firmware/frozen_mpy.o(.rodata*); + /* build/firmware/vendor/secp256k1-zkp/src/secp256k1.o(.rodata*); build/firmware/vendor/secp256k1-zkp/src/precomputed_ecmult.o(.rodata*); - build/firmware/vendor/secp256k1-zkp/src/precomputed_ecmult_gen.o(.rodata*); + build/firmware/vendor/secp256k1-zkp/src/precomputed_ecmult_gen.o(.rodata*); */ . = ALIGN(512); } >FLASH2 AT>FLASH2 - .flash : ALIGN(512) { KEEP(*(.vector_table)); . = ALIGN(4); diff --git a/core/embed/rust/src/zcash_primitives/pallas/mod.rs b/core/embed/rust/src/zcash_primitives/pallas/mod.rs index 3fb089771a7..be9b069df50 100644 --- a/core/embed/rust/src/zcash_primitives/pallas/mod.rs +++ b/core/embed/rust/src/zcash_primitives/pallas/mod.rs @@ -8,17 +8,14 @@ mod scalar; #[no_mangle] pub static mp_module_trezorpallas: Module = obj_module! { Qstr::MP_QSTR___name__ => Qstr::MP_QSTR_trezorpallas.to_obj(), - /// # https://zips.z.cash/protocol/protocol.pdf#orchardkeycomponents /// def to_base(x: bytes) -> Fp: - /// ... + /// """https://zips.z.cash/protocol/protocol.pdf#orchardkeycomponents""" Qstr::MP_QSTR_to_base => obj_fn_1!(fp::to_base).as_obj(), - /// # https://zips.z.cash/protocol/protocol.pdf#orchardkeycomponents /// def to_scalar(x: bytes) -> Scalar: - /// ... + /// """https://zips.z.cash/protocol/protocol.pdf#orchardkeycomponents""" Qstr::MP_QSTR_to_scalar => obj_fn_1!(scalar::to_scalar).as_obj(), - /// # https://zips.z.cash/protocol/protocol.pdf#concretegrouphashpallasandvesta /// def group_hash(domain: str, message: bytes) -> Point: - /// ... + /// """https://zips.z.cash/protocol/protocol.pdf#concretegrouphashpallasandvesta""" Qstr::MP_QSTR_group_hash => obj_fn_2!(point::group_hash).as_obj(), /// def scalar_from_i64(x: int) -> Scalar: /// """Converts integer to Scalar.""" @@ -32,8 +29,8 @@ pub static mp_module_trezorpallas: Module = obj_module! { /// def to_bytes(self) -> bytes: /// ... Qstr::MP_QSTR_Fp => (&fp::FP_TYPE).as_obj(), - /// class Scalar: - /// """Pallas scalar field.""" + /// class Point: + /// """Pallas point.""" /// /// def __init__(self, repr: bytes) -> None: /// ... @@ -41,20 +38,22 @@ pub static mp_module_trezorpallas: Module = obj_module! { /// def to_bytes(self) -> bytes: /// ... /// - /// def is_not_zero(self) -> bool: + /// def extract(self) -> Fp: /// ... /// - /// def __mul__(self, other: Point) -> Point: + /// def is_identity(self) -> bool: /// ... /// - /// def __add__(self, other: Scalar) -> Scalar: + /// def __add__(self, other: Point) -> Point: /// ... /// /// def __neg__(self) -> Point: /// ... - Qstr::MP_QSTR_Scalar => (&scalar::SCALAR_TYPE).as_obj(), - /// class Point: - /// """Pallas point.""" + Qstr::MP_QSTR_Point => (&point::POINT_TYPE).as_obj(), + /// PointOrScalar = TypeVar("PointOrScalar", Point, Scalar) + /// + /// class Scalar: + /// """Pallas scalar field.""" /// /// def __init__(self, repr: bytes) -> None: /// ... @@ -62,16 +61,19 @@ pub static mp_module_trezorpallas: Module = obj_module! { /// def to_bytes(self) -> bytes: /// ... /// - /// def extract(self) -> Fp: + /// def is_not_zero(self) -> bool: /// ... /// - /// def is_identity(self) -> bool: + /// def __mul__(self, other: PointOrScalar) -> PointOrScalar: /// ... + /// # if isinstance(other, Point) then isinstance(result, Point) + /// # if isinstance(other, Scalar) then isinstance(result, Scalar) + /// return other # just for typechecker! inner implementation differs /// - /// def __add__(self, other: Point) -> Point: + /// def __add__(self, other: Scalar) -> Scalar: /// ... /// - /// def __neg__(self) -> Point: + /// def __neg__(self) -> Scalar: /// ... - Qstr::MP_QSTR_Point => (&point::POINT_TYPE).as_obj(), + Qstr::MP_QSTR_Scalar => (&scalar::SCALAR_TYPE).as_obj(), }; diff --git a/core/embed/rust/src/zcash_primitives/pallas/scalar.rs b/core/embed/rust/src/zcash_primitives/pallas/scalar.rs index 8b312e7f7aa..ef9e0e2dddf 100644 --- a/core/embed/rust/src/zcash_primitives/pallas/scalar.rs +++ b/core/embed/rust/src/zcash_primitives/pallas/scalar.rs @@ -40,9 +40,16 @@ unsafe extern "C" fn scalar_binary_op(op: ffi::mp_binary_op_t, this: Obj, other: let this = this.deref().inner(); match op { ffi::mp_binary_op_t_MP_BINARY_OP_MULTIPLY => { - let other = Gc::>::try_from(other)?; - let other = other.deref().inner(); - (other * this).wrap() + let point = Gc::>::try_from(other); + if point.is_ok() { + let point = point.unwrap(); + let point = point.deref().inner(); + (point * this).wrap() + } else { + let scalar = Gc::>::try_from(other)?; + let scalar = scalar.deref().inner(); + (this * scalar).wrap() + } } ffi::mp_binary_op_t_MP_BINARY_OP_ADD => { let other = Gc::>::try_from(other)?; diff --git a/core/mocks/generated/trezorpallas.pyi b/core/mocks/generated/trezorpallas.pyi index 6540ac5dacd..9069cd1b11e 100644 --- a/core/mocks/generated/trezorpallas.pyi +++ b/core/mocks/generated/trezorpallas.pyi @@ -1,22 +1,19 @@ from typing import * -# https://zips.z.cash/protocol/protocol.pdf#orchardkeycomponents # rust/src/zcash_primitives/pallas/mod.rs def to_base(x: bytes) -> Fp: - ... -# https://zips.z.cash/protocol/protocol.pdf#orchardkeycomponents + """https://zips.z.cash/protocol/protocol.pdf#orchardkeycomponents""" # rust/src/zcash_primitives/pallas/mod.rs def to_scalar(x: bytes) -> Scalar: - ... -# https://zips.z.cash/protocol/protocol.pdf#concretegrouphashpallasandvesta + """https://zips.z.cash/protocol/protocol.pdf#orchardkeycomponents""" # rust/src/zcash_primitives/pallas/mod.rs def group_hash(domain: str, message: bytes) -> Point: - ... + """https://zips.z.cash/protocol/protocol.pdf#concretegrouphashpallasandvesta""" # rust/src/zcash_primitives/pallas/mod.rs @@ -34,34 +31,38 @@ class Fp: # rust/src/zcash_primitives/pallas/mod.rs -class Scalar: - """Pallas scalar field.""" +class Point: + """Pallas point.""" def __init__(self, repr: bytes) -> None: ... def to_bytes(self) -> bytes: ... - def is_not_zero(self) -> bool: + def extract(self) -> Fp: ... - def __mul__(self, other: Point) -> Point: + def is_identity(self) -> bool: ... - def __add__(self, other: Scalar) -> Scalar: + def __add__(self, other: Point) -> Point: ... def __neg__(self) -> Point: ... +PointOrScalar = TypeVar("PointOrScalar", Point, Scalar) # rust/src/zcash_primitives/pallas/mod.rs -class Point: - """Pallas point.""" +class Scalar: + """Pallas scalar field.""" def __init__(self, repr: bytes) -> None: ... def to_bytes(self) -> bytes: ... - def extract(self) -> Fp: + def is_not_zero(self) -> bool: ... - def is_identity(self) -> bool: + def __mul__(self, other: PointOrScalar) -> PointOrScalar: ... - def __add__(self, other: Point) -> Point: + # if isinstance(other, Point) then isinstance(result, Point) + # if isinstance(other, Scalar) then isinstance(result, Scalar) + return other # just for typechecker! inner implementation differs + def __add__(self, other: Scalar) -> Scalar: ... - def __neg__(self) -> Point: + def __neg__(self) -> Scalar: ... diff --git a/core/src/all_modules.py b/core/src/all_modules.py index 610c2f8a947..b6fb53c8bcf 100644 --- a/core/src/all_modules.py +++ b/core/src/all_modules.py @@ -81,6 +81,8 @@ import trezor.crypto.der trezor.crypto.hashlib import trezor.crypto.hashlib +trezor.crypto.mock_bip340 +import trezor.crypto.mock_bip340 trezor.crypto.pallas import trezor.crypto.pallas trezor.crypto.rlp @@ -461,6 +463,8 @@ import trezor.enums.TezosBallotType trezor.enums.TezosContractType import trezor.enums.TezosContractType + trezor.enums.ZcashSignatureType + import trezor.enums.ZcashSignatureType trezor.ui.components.common.webauthn import trezor.ui.components.common.webauthn trezor.ui.components.tt.webauthn @@ -777,10 +781,54 @@ import apps.webauthn.resident_credentials apps.zcash import apps.zcash + apps.zcash.approver + import apps.zcash.approver + apps.zcash.debug + import apps.zcash.debug apps.zcash.f4jumble import apps.zcash.f4jumble + apps.zcash.get_address + import apps.zcash.get_address apps.zcash.hasher import apps.zcash.hasher + apps.zcash.layout + import apps.zcash.layout + apps.zcash.orchard + import apps.zcash.orchard + apps.zcash.orchard.accumulator + import apps.zcash.orchard.accumulator + apps.zcash.orchard.crypto + import apps.zcash.orchard.crypto + apps.zcash.orchard.crypto.address + import apps.zcash.orchard.crypto.address + apps.zcash.orchard.crypto.builder + import apps.zcash.orchard.crypto.builder + apps.zcash.orchard.crypto.ff1 + import apps.zcash.orchard.crypto.ff1 + apps.zcash.orchard.crypto.generators + import apps.zcash.orchard.crypto.generators + apps.zcash.orchard.crypto.keys + import apps.zcash.orchard.crypto.keys + apps.zcash.orchard.crypto.note + import apps.zcash.orchard.crypto.note + apps.zcash.orchard.crypto.note_encryption + import apps.zcash.orchard.crypto.note_encryption + apps.zcash.orchard.crypto.redpallas + import apps.zcash.orchard.crypto.redpallas + apps.zcash.orchard.crypto.sinsemilla + import apps.zcash.orchard.crypto.sinsemilla + apps.zcash.orchard.crypto.utils + import apps.zcash.orchard.crypto.utils + apps.zcash.orchard.get_fvk + import apps.zcash.orchard.get_fvk + apps.zcash.orchard.get_ivk + import apps.zcash.orchard.get_ivk + apps.zcash.orchard.keychain + import apps.zcash.orchard.keychain + apps.zcash.orchard.random + import apps.zcash.orchard.random + apps.zcash.orchard.signer + import apps.zcash.orchard.signer apps.zcash.signer import apps.zcash.signer apps.zcash.unified_addresses diff --git a/core/src/apps/bitcoin/sign_tx/__init__.py b/core/src/apps/bitcoin/sign_tx/__init__.py index 3c94814921d..9b11fa9a200 100644 --- a/core/src/apps/bitcoin/sign_tx/__init__.py +++ b/core/src/apps/bitcoin/sign_tx/__init__.py @@ -11,6 +11,7 @@ if not utils.BITCOIN_ONLY: from . import bitcoinlike, decred, zcash_v4 from apps.zcash.signer import Zcash + from apps.zcash.orchard.keychain import OrchardKeychain if TYPE_CHECKING: from typing import Protocol @@ -65,6 +66,7 @@ async def sign_tx( if authorization: approver = approvers.CoinJoinApprover(msg, coin, authorization) + kwargs = dict() if utils.BITCOIN_ONLY or coin.coin_name in BITCOIN_NAMES: signer_class: type[SignerClass] = bitcoin.Bitcoin else: @@ -73,12 +75,13 @@ async def sign_tx( elif coin.overwintered: if msg.version == 5: signer_class = Zcash + kwargs["orchard_keychain"] = await OrchardKeychain.for_coin(ctx, coin) else: signer_class = zcash_v4.ZcashV4 else: signer_class = bitcoinlike.Bitcoinlike - signer = signer_class(msg, keychain, coin, approver).signer() + signer = signer_class(msg, keychain, coin, approver, **kwargs).signer() res: TxAckType | bool | None = None while True: diff --git a/core/src/apps/bitcoin/sign_tx/approvers.py b/core/src/apps/bitcoin/sign_tx/approvers.py index f3b0e336067..e26f73e2eab 100644 --- a/core/src/apps/bitcoin/sign_tx/approvers.py +++ b/core/src/apps/bitcoin/sign_tx/approvers.py @@ -202,7 +202,10 @@ async def add_external_output( elif txo.payment_req_index is None or self.show_payment_req_details: # Ask user to confirm output, unless it is part of a payment # request, which gets confirmed separately. - await helpers.confirm_output(txo, self.coin, self.amount_unit) + await self.confirm_output(txo) + + async def confirm_output(self, txo: TxOutput) -> None: + await helpers.confirm_output(txo, self.coin, self.amount_unit) async def add_payment_request( self, msg: TxAckPaymentRequest, keychain: Keychain diff --git a/core/src/apps/bitcoin/sign_tx/helpers.py b/core/src/apps/bitcoin/sign_tx/helpers.py index e1277d51119..e06be9b9436 100644 --- a/core/src/apps/bitcoin/sign_tx/helpers.py +++ b/core/src/apps/bitcoin/sign_tx/helpers.py @@ -371,6 +371,8 @@ def _clear_tx_request(tx_req: TxRequest) -> None: # typechecker thinks serialized_tx is `bytes`, which is immutable # we know that it is `bytearray` in reality tx_req.serialized.serialized_tx[:] = bytes() # type: ignore ["__setitem__" method not defined on type "bytes"] + tx_req.serialized.signature_type = None + tx_req.serialized.zcash_shielding_seed = None # Data sanitizers diff --git a/core/src/apps/bitcoin/sign_tx/progress.py b/core/src/apps/bitcoin/sign_tx/progress.py index 440eda463dd..da17d25d038 100644 --- a/core/src/apps/bitcoin/sign_tx/progress.py +++ b/core/src/apps/bitcoin/sign_tx/progress.py @@ -27,5 +27,8 @@ def report_init() -> None: def report() -> None: if utils.DISABLE_ANIMATION: return - p = 1000 * _progress // _steps + if _steps == 0: # Zcash transaction without transparent inputs and outputs + p = 1000 + else: + p = 1000 * _progress // _steps ui.display.loader(p, False, 18, ui.WHITE, ui.BG) diff --git a/core/src/apps/workflow_handlers.py b/core/src/apps/workflow_handlers.py index 564c4cb1cb6..440de338c36 100644 --- a/core/src/apps/workflow_handlers.py +++ b/core/src/apps/workflow_handlers.py @@ -181,6 +181,14 @@ def find_message_handler_module(msg_type: int) -> str: if msg_type == MessageType.BinanceSignTx: return "apps.binance.sign_tx" + # zcash + if msg_type == MessageType.ZcashGetFullViewingKey: + return "apps.zcash.orchard.get_fvk" + if msg_type == MessageType.ZcashGetIncomingViewingKey: + return "apps.zcash.orchard.get_ivk" + if msg_type == MessageType.ZcashGetAddress: + return "apps.zcash.get_address" + raise ValueError diff --git a/core/src/apps/zcash/approver.py b/core/src/apps/zcash/approver.py new file mode 100644 index 00000000000..767c594cd7c --- /dev/null +++ b/core/src/apps/zcash/approver.py @@ -0,0 +1,35 @@ +from typing import TYPE_CHECKING + +from apps.bitcoin.sign_tx import approvers + +from .layout import UiConfirmOrchardOutput, UiConfirmTransparentOutput + +if TYPE_CHECKING: + from typing import Awaitable + from trezor.messages import ZcashOrchardInput, ZcashOrchardOutput, TxOutput + + +class ZcashApprover(approvers.BasicApprover): + def __init__(self, *args, **kwargs): + self.orchard_balance = 0 + super().__init__(*args, **kwargs) + + def confirm_output(self, txo: TxOutput) -> Awaitable[None]: # type: ignore [awaitable-is-generator] + return (yield UiConfirmTransparentOutput(txo, self.coin)) + + def add_orchard_input(self, txi: ZcashOrchardInput) -> None: + self.total_in += txi.value + self.orchard_balance += txi.value + + def add_orchard_change_output(self, txo: ZcashOrchardOutput) -> None: + self.change_count += 1 + self.total_out += txo.amount + self.change_out += txo.amount + self.orchard_balance -= txo.amount + + def add_orchard_external_output( + self, txo: ZcashOrchardOutput + ) -> Awaitable[None]: # type: ignore [awaitable-is-generator] + self.total_out += txo.amount + self.orchard_balance -= txo.amount + return (yield UiConfirmOrchardOutput(txo, self.coin)) diff --git a/core/src/apps/zcash/debug.py b/core/src/apps/zcash/debug.py new file mode 100644 index 00000000000..d82ba7883c3 --- /dev/null +++ b/core/src/apps/zcash/debug.py @@ -0,0 +1,43 @@ +import gc + +if __debug__: + from trezor import log + + def log_gc(label=""): + gc.collect() + log.info( + __name__, + "GC[%s]: alloc: %d kb free: %d kb (%d/1000)", + label, + gc.mem_alloc() // 1000, + gc.mem_free() // 1000, + (1000 * gc.mem_free()) // (gc.mem_free() + gc.mem_alloc()), + ) + + +def watch_gc(func): + if __debug__: + + def wrapper(*args, **kwargs): + log_gc("before " + func.__name__) + res = func(*args, **kwargs) + log_gc("after " + func.__name__) + return res + + return wrapper + else: + return func + + +def watch_gc_async(func): + if __debug__: + + async def wrapper(*args, **kwargs): + log_gc("before " + func.__name__) + res = await func(*args, **kwargs) + log_gc("after " + func.__name__) + return res + + return wrapper + else: + return func diff --git a/core/src/apps/zcash/get_address.py b/core/src/apps/zcash/get_address.py new file mode 100644 index 00000000000..e5f66a41f7e --- /dev/null +++ b/core/src/apps/zcash/get_address.py @@ -0,0 +1,82 @@ +from typing import TYPE_CHECKING + +from trezor import wire +from trezor.crypto import base58 +from trezor.crypto.scripts import sha256_ripemd160 +from trezor.messages import ZcashAddress +from trezor.ui.layouts import show_address + +from apps.bitcoin import keychain as t_keychain +from apps.common import address_type +from apps.common.coininfo import CoinInfo, by_name +from apps.common.paths import HARDENED, address_n_to_str + +from .orchard import keychain as z_keychain +from .unified_addresses import Typecode, encode as encode_unified + +if TYPE_CHECKING: + from trezor.wire import Context + from trezor.messages import ZcashGetAddress + + +def encode_p2pkh(raw_address: bytes, coin: CoinInfo) -> str: + return base58.encode_check(address_type.tobytes(coin.address_type) + raw_address) + + +async def get_address(ctx: Context, msg: ZcashGetAddress) -> ZcashAddress: + has_t_addr = len(msg.t_address_n) != 0 + has_z_addr = len(msg.z_address_n) != 0 + + if not (has_t_addr or has_z_addr): + raise wire.DataError("t-address or z-address path expected") + + coin = by_name(msg.coin_name) + + if has_z_addr: + receivers = dict() + receivers[Typecode.ORCHARD] = await get_raw_orchard_address(ctx, msg) + + if has_t_addr: + if msg.t_address_n[2] != msg.z_address_n[2]: + raise wire.DataError("Receivers use different acount numbers.") + receivers[Typecode.P2PKH] = await get_raw_transparent_address( + ctx, coin, msg + ) + + title = "u/{coin_type}/{account}/{receivers}".format( + coin_type=msg.z_address_n[1] ^ HARDENED, + account=msg.z_address_n[2] ^ HARDENED, + receivers=",".join(map(str, receivers.keys())), + ) + + address = encode_unified(receivers, coin) + + else: # has only t-address + title = address_n_to_str(msg.t_address_n) + raw_address = await get_raw_transparent_address(ctx, coin, msg) + address = encode_p2pkh(raw_address, coin) + + if msg.show_display: + await show_address(ctx, address=address, address_qr=address, title=title) + + return ZcashAddress(address=address) + + +async def get_raw_transparent_address( + ctx: Context, + coin: CoinInfo, + msg: ZcashGetAddress, +) -> bytes: + """Returns Zcash raw P2PKH transparent address.""" + keychain = await t_keychain.get_keychain_for_coin(ctx, coin) + node = keychain.derive(msg.t_address_n) + return sha256_ripemd160(node.public_key()).digest() + + +@z_keychain.with_keychain +async def get_raw_orchard_address( + ctx: Context, msg: ZcashGetAddress, keychain: z_keychain.OrchardKeychain +) -> bytes: + """Returns raw Zcash Orchard address.""" + fvk = keychain.derive(msg.z_address_n).full_viewing_key() + return fvk.address(msg.diversifier_index).to_bytes() diff --git a/core/src/apps/zcash/hasher.py b/core/src/apps/zcash/hasher.py index fcfd47f5142..c81c06bcb8b 100644 --- a/core/src/apps/zcash/hasher.py +++ b/core/src/apps/zcash/hasher.py @@ -1,6 +1,6 @@ """ Implementation of Zcash txid and sighash algorithms -according to the ZIP-0244. +according to the ZIP-244. specification: https://zips.z.cash/zip-0244 """ @@ -11,6 +11,8 @@ from trezor.utils import HashWriter, empty_bytearray from apps.bitcoin.common import SigHashType +from apps.bitcoin.writers import write_uint32 # TODO: import from apps.common.writers +from apps.bitcoin.writers import write_uint64 # TODO: import from apps.common.writers from apps.bitcoin.writers import ( TX_HASH_SIZE, write_bytes_fixed, @@ -18,12 +20,10 @@ write_bytes_reversed, write_tx_output, write_uint8, - write_uint32, - write_uint64, ) if TYPE_CHECKING: - from trezor.messages import TxInput, TxOutput, SignTx, PrevTx + from trezor.messages import TxInput, TxOutput, SignTx, PrevTx, ZcashOrchardAction from trezor.utils import Writer from apps.common.coininfo import CoinInfo from typing import Sequence @@ -38,6 +38,13 @@ def write_prevout(w: Writer, txi: TxInput) -> None: write_uint32(w, txi.prev_index) +def write_sint64_le(w: Writer, x: int) -> None: + assert -0x8000_0000_0000_0000 < x <= 0x7FFF_FFFF_FFFF_FFFF + if x < 0: + x += 0x1_0000_0000_0000_0000 # 2**64 + write_uint64(w, x) + + class ZcashHasher: def __init__(self, tx: SignTx | PrevTx): self.header = HeaderHasher(tx) @@ -56,7 +63,6 @@ def __init__(self, tx: SignTx | PrevTx): def txid_digest(self) -> bytes: """ Returns the transaction identifier. - see: https://zips.z.cash/zip-0244#id4 """ h = HashWriter(blake2b(outlen=32, personal=self.tx_hash_person)) @@ -69,11 +75,10 @@ def txid_digest(self) -> bytes: return h.get_digest() def signature_digest( - self, txi: TxInput | None, script_pubkey: bytes | None + self, txi: TxInput | None = None, script_pubkey: bytes | None = None ) -> bytes: """ Returns the transaction signature digest. - see: https://zips.z.cash/zip-0244#id13 """ h = HashWriter(blake2b(outlen=32, personal=self.tx_hash_person)) @@ -139,7 +144,6 @@ def __init__(self, tx: SignTx | PrevTx): def digest(self) -> bytes: """ Returns `T.1: header_digest` field. - see: https://zips.z.cash/zip-0244#t-1-header-digest """ return self._digest @@ -167,30 +171,28 @@ def __init__(self) -> None: blake2b(outlen=32, personal=b"ZTxIdOutputsHash") ) # a hasher for fields T.2c & S.2f - self.empty = True # inputs_amount + outputs_amount == 0 + self.has_inputs = False + self.has_outputs = False def add_input(self, txi: TxInput, script_pubkey: bytes) -> None: - self.empty = False - + self.has_inputs = True write_prevout(self.prevouts, txi) write_uint64(self.amounts, txi.amount) write_bytes_prefixed(self.scriptpubkeys, script_pubkey) write_uint32(self.sequence, txi.sequence) def add_output(self, txo: TxOutput, script_pubkey: bytes) -> None: - self.empty = False - + self.has_outputs = True write_tx_output(self.outputs, txo, script_pubkey) def digest(self) -> bytes: """ Returns `T.2: transparent_digest` field for txid computation. - see: https://zips.z.cash/zip-0244#t-2-transparent-digest """ h = HashWriter(blake2b(outlen=32, personal=b"ZTxIdTranspaHash")) - if not self.empty: + if self.has_inputs or self.has_outputs: write_hash(h, self.prevouts.get_digest()) # T.2a write_hash(h, self.sequence.get_digest()) # T.2b write_hash(h, self.outputs.get_digest()) # T.2c @@ -205,11 +207,10 @@ def sig_digest( """ Returns `S.2: transparent_sig_digest` field for signature digest computation. - see: https://zips.z.cash/zip-0244#s-2-transparent-sig-digest """ - if self.empty: + if not self.has_inputs: assert txi is None assert script_pubkey is None return self.digest() @@ -234,7 +235,6 @@ def _txin_sig_digest( ) -> bytes: """ Returns `S.2g: txin_sig_digest` field for signature digest computation. - see: https://zips.z.cash/zip-0244#s-2g-txin-sig-digest """ @@ -252,28 +252,61 @@ def _txin_sig_digest( class SaplingHasher: - """ - Empty Sapling bundle hasher. - """ + """Empty Sapling bundle hasher.""" def digest(self) -> bytes: """ Returns `T.3: sapling_digest` field. - see: https://zips.z.cash/zip-0244#t-3-sapling-digest """ return blake2b(outlen=32, personal=b"ZTxIdSaplingHash").digest() +EMPTY = object() +ADDING_ACTIONS = object() +FINISHED = object() + + class OrchardHasher: - """ - Empty Orchard bundle hasher. - """ + def __init__(self) -> None: + self.h = HashWriter(blake2b(outlen=32, personal=b"ZTxIdOrchardHash")) + self.ch = HashWriter(blake2b(outlen=32, personal=b"ZTxIdOrcActCHash")) + self.mh = HashWriter(blake2b(outlen=32, personal=b"ZTxIdOrcActMHash")) + self.nh = HashWriter(blake2b(outlen=32, personal=b"ZTxIdOrcActNHash")) + self.state = EMPTY + + def add_action(self, action: ZcashOrchardAction) -> None: + assert self.state in (EMPTY, ADDING_ACTIONS) + self.state = ADDING_ACTIONS + encrypted = action.encrypted_note + write_bytes_fixed(self.ch, action.nf, 32) # T.4a.i + write_bytes_fixed(self.ch, action.cmx, 32) # T.4a.ii + write_bytes_fixed(self.ch, encrypted.epk_bytes, 32) # T.4a.iii + write_bytes_fixed(self.ch, encrypted.enc_ciphertext[:52], 52) # T.4a.iv + + write_bytes_fixed(self.mh, encrypted.enc_ciphertext[52:564], 512) # T.4b.i + + write_bytes_fixed(self.nh, action.cv, 32) # T.4c.i + write_bytes_fixed(self.nh, action.rk, 32) # T.4c.ii + write_bytes_fixed(self.nh, encrypted.enc_ciphertext[564:], 16) # T.4c.iii + write_bytes_fixed(self.nh, encrypted.out_ciphertext, 80) # T.4c.iv + + def finalize(self, flags: int, value_balance: int, anchor: bytes) -> None: + assert self.state == ADDING_ACTIONS + + write_bytes_fixed(self.h, self.ch.get_digest(), 32) # T.4a + write_bytes_fixed(self.h, self.mh.get_digest(), 32) # T.4b + write_bytes_fixed(self.h, self.nh.get_digest(), 32) # T.4c + write_uint8(self.h, flags) # T.4d + write_sint64_le(self.h, value_balance) # T.4e + write_bytes_fixed(self.h, anchor, 32) # T.4f + + self.state = FINISHED def digest(self) -> bytes: """ Returns `T.4: orchard_digest` field. - see: https://zips.z.cash/zip-0244#t-4-orchard-digest """ - return blake2b(outlen=32, personal=b"ZTxIdOrchardHash").digest() + assert self.state in (EMPTY, FINISHED) + return self.h.get_digest() diff --git a/core/src/apps/zcash/layout.py b/core/src/apps/zcash/layout.py new file mode 100644 index 00000000000..c3e2b84a047 --- /dev/null +++ b/core/src/apps/zcash/layout.py @@ -0,0 +1,144 @@ +from typing import TYPE_CHECKING + +from trezor import strings, ui +from trezor.enums import ButtonRequestType +from trezor.ui.components.common.confirm import CONFIRMED, SHOW_PAGINATED +from trezor.ui.components.tt.scroll import AskPaginated, Paginated, paginate_paragraphs +from trezor.ui.components.tt.text import Text +from trezor.ui.constants.tt import MONO_ADDR_PER_LINE +from trezor.ui.layouts import confirm_action, confirm_address +from trezor.ui.layouts.tt import Confirm, interact, raise_if_cancelled +from trezor.utils import chunks, chunks_intersperse, ensure + +from apps.bitcoin.sign_tx import helpers + +if TYPE_CHECKING: + from typing import Awaitable, Any + from apps.common.coininfo import CoinInfo + from trezor.wire import Context + from trezor.messages import ZcashOrchardOutput, TxOutput + from trezor.ui import Component + from trezor.ui.layouts.common import LayoutType + + +def _format_amount(value: int, coin: CoinInfo) -> str: + return "%s %s" % (strings.format_amount(value, 8), coin.coin_shortcut) + + +class UiConfirmTransparentOutput(helpers.UiConfirm): + def __init__(self, txo: TxOutput, coin: CoinInfo) -> None: + self.txo = txo + self.coin = coin + + def confirm_dialog(self, ctx: Context) -> Awaitable[Any]: + content = Confirm(get_pay_page(self.txo, self.coin, "t")) + assert self.txo.address is not None # typing + return maybe_show_full_address(ctx, content, self.txo.address) + + +class UiConfirmOrchardOutput(helpers.UiConfirm): + def __init__(self, txo: ZcashOrchardOutput, coin: CoinInfo) -> None: + self.txo = txo + self.coin = coin + + def confirm_dialog(self, ctx: Context) -> Awaitable[Any]: + pages = [] + pages.append(get_pay_page(self.txo, self.coin, "z")) + pages.extend(get_memo_pages(self.txo.memo)) + + pages[-1] = Confirm(pages[-1]) + + assert len(pages) >= 2 # pay page + memo page + content = Paginated(pages) + assert self.txo.address is not None # typing + return maybe_show_full_address(ctx, content, self.txo.address) + + +def get_pay_page( + txo: TxOutput | ZcashOrchardOutput, coin: CoinInfo, transfer_type: str +) -> Component: + assert transfer_type in ("t", "z") + title = "Confirm %s-sending" % transfer_type + text = Text(title, ui.ICON_SEND, ui.GREEN, new_lines=False) + text.bold(_format_amount(txo.amount, coin)) + text.normal(" to\n") + + assert txo.address is not None # typing + if txo.address.startswith("t"): # transparent address + ensure(len(txo.address) == 35) + text.mono(*chunks_intersperse(txo.address, MONO_ADDR_PER_LINE)) + return text + elif txo.address.startswith("u"): # unified address + address_lines = chunks(txo.address, MONO_ADDR_PER_LINE) + text.mono(next(address_lines) + "\n") + text.mono(next(address_lines)[:-3] + "...") + return AskPaginated(text, "show full address") + else: + raise ValueError("Unexpected address prefix.") + + +def get_memo_pages(memo: str | None) -> list[Component]: + if memo is None: + return [Text("without memo", ui.ICON_SEND, ui.GREEN)] + + paginated = paginate_paragraphs( + [(ui.NORMAL, memo)], + "with memo", + header_icon=ui.ICON_SEND, + icon_color=ui.GREEN, + ) + + if isinstance(paginated, Confirm): + return [paginated.content] + else: + assert isinstance(paginated, Paginated) + return paginated.pages + + +async def maybe_show_full_address( + ctx: Context, content: LayoutType, full_address: str +) -> None: + """Lets user to toggle between output-confirmation-dialog + and see-full-address-dialog before he confirms an output.""" + while True: + result = await raise_if_cancelled( + interact( + ctx, + content, + "confirm_output", + ) + ) + if result is SHOW_PAGINATED: + await confirm_address( + ctx, + "Confirm address", + full_address, + description=None, + ) + else: + assert result is CONFIRMED + break + + +async def require_confirm_export_fvk(ctx: Context) -> None: + await confirm_action( + ctx, + "export_full_viewing_key", + "Confirm export", + description="Do you really want to export Full Viewing Key?", + icon=ui.ICON_SEND, + icon_color=ui.GREEN, + br_code=ButtonRequestType.SignTx, + ) + + +async def require_confirm_export_ivk(ctx: Context) -> None: + await confirm_action( + ctx, + "export_incoming_viewing_key", + "Confirm export", + description="Do you really want to export Incoming Viewing Key?", + icon=ui.ICON_SEND, + icon_color=ui.GREEN, + br_code=ButtonRequestType.SignTx, + ) diff --git a/core/src/apps/zcash/orchard/__init__.py b/core/src/apps/zcash/orchard/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/core/src/apps/zcash/orchard/accumulator.py b/core/src/apps/zcash/orchard/accumulator.py new file mode 100644 index 00000000000..b57725e0e0e --- /dev/null +++ b/core/src/apps/zcash/orchard/accumulator.py @@ -0,0 +1,34 @@ +from typing import TYPE_CHECKING + +from trezor import protobuf +from trezor.crypto import hmac +from trezor.wire import ProcessError + +if TYPE_CHECKING: + from trezor.protobuf import MessageType + + pass + +EMPTY = 32 * b"\x00" + + +def xor(x: bytes, y: bytes) -> bytes: + return bytes([a ^ b for a, b in zip(x, y)]) + + +class MessageAccumulator: + def __init__(self, secret: bytes) -> None: + self.key = secret + self.state = EMPTY + + def xor_message(self, msg: MessageType, index: int) -> None: + mac = hmac(hmac.SHA256, self.key) + assert msg.MESSAGE_WIRE_TYPE is not None + mac.update(msg.MESSAGE_WIRE_TYPE.to_bytes(2, "big")) + mac.update(index.to_bytes(4, "little")) + mac.update(protobuf.dump_message_buffer(msg)) + self.state = xor(self.state, mac.digest()) + + def check(self) -> None: + if self.state != EMPTY: + raise ProcessError("Orchard input or output changed") diff --git a/core/src/apps/zcash/orchard/crypto/__init__.py b/core/src/apps/zcash/orchard/crypto/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/core/src/apps/zcash/orchard/crypto/address.py b/core/src/apps/zcash/orchard/crypto/address.py new file mode 100644 index 00000000000..7122c7749ac --- /dev/null +++ b/core/src/apps/zcash/orchard/crypto/address.py @@ -0,0 +1,41 @@ +from typing import TYPE_CHECKING + +from trezor.crypto.pallas import Point, group_hash + +if TYPE_CHECKING: + from trezor.crypto.pallas import Scalar + + pass # address.i + + +# https://zips.z.cash/protocol/protocol.pdf#concretediversifyhash +def diversify_hash(d: bytes) -> Point: + P = group_hash("z.cash:Orchard-gd", d) + if P.is_identity(): + P = group_hash("z.cash:Orchard-gd", b"") + return P + + +class Address: + def __init__(self, d: bytes, pk_d: Point) -> None: + assert len(d) == 11 + self.d = d + self.pk_d = pk_d + + @staticmethod + def from_bytes(data: bytes) -> "Address": + assert len(data) == 43 + return Address(data[:11], Point(data[11:])) + + @staticmethod + def from_ivk(d: bytes, ivk: Scalar): + g_d = diversify_hash(d) + pk_d = ivk * g_d + return Address(d, pk_d) + + # https://zips.z.cash/protocol/nu5.pdf#orchardpaymentaddrencoding + def to_bytes(self) -> bytes: + return self.d + self.pk_d.to_bytes() + + def g_d(self) -> Point: + return diversify_hash(self.d) diff --git a/core/src/apps/zcash/orchard/crypto/builder.py b/core/src/apps/zcash/orchard/crypto/builder.py new file mode 100644 index 00000000000..b77a62820dd --- /dev/null +++ b/core/src/apps/zcash/orchard/crypto/builder.py @@ -0,0 +1,133 @@ +from typing import TYPE_CHECKING + +from trezor.crypto.pallas import Point, Scalar, scalar_from_i64 + +from ...debug import log_gc +from .generators import ( + SPENDING_KEY_BASE, + VALUE_COMMITMENT_RANDOMNESS_BASE, + VALUE_COMMITMENT_VALUE_BASE, +) +from .keys import FullViewingKey, sk_to_ask +from .note import Note +from .note_encryption import encrypt_note + +if TYPE_CHECKING: + from .note_encryption import TransmittedNoteCiphertext + from ..random import ActionShieldingRng + + +# https://zips.z.cash/protocol/nu5.pdf#concretehomomorphiccommit +def commit_value(rcv: Scalar, v: int): + V = scalar_from_i64(v) * VALUE_COMMITMENT_VALUE_BASE + R = rcv * VALUE_COMMITMENT_RANDOMNESS_BASE + return V + R + + +class Action: + def __init__( + self, + cv: bytes, + nf: bytes, + rk: bytes, + cmx: bytes, + encrypted_note: TransmittedNoteCiphertext, + ) -> None: + self.cv = cv + self.nf = nf + self.rk = rk + self.cmx = cmx + self.encrypted_note = encrypted_note + + +class InputInfo: + def __init__( + self, note: Note, fvk: FullViewingKey, dummy_ask: Scalar | None = None + ): + self.note = note + self.fvk = fvk + self.dummy_ask = dummy_ask # for dummy notes + + @staticmethod + def dummy(rng: ActionShieldingRng) -> "InputInfo": + dummy_sk = rng.dummy_sk() + fvk = FullViewingKey.from_spending_key(dummy_sk) + note = Note( + recipient=fvk.address(0), + value=0, + rho=rng.rho(), + rseed=rng.rseed_old(), + ) + dummy_ask = sk_to_ask(dummy_sk) + return InputInfo(note, fvk, dummy_ask) + + +class OutputInfo: + def __init__(self, ovk, address, value, memo): + self.ovk = ovk + self.address = address + self.value = value + self.memo = memo + + @staticmethod + def dummy(rng: ActionShieldingRng) -> "OutputInfo": + return OutputInfo(None, rng.recipient(), 0, None) + + +def build_action( + input: InputInfo, + output: OutputInfo, + rng: ActionShieldingRng, +) -> Action: + log_gc("build 1") + # nullifier + nf_old = input.note.nullifier(input.fvk.nk) + + # verification key + alpha = rng.alpha() + akP = Point(input.fvk.ak.to_bytes()) + rk = akP + alpha * SPENDING_KEY_BASE + + # note commitment + note = Note( + recipient=output.address, + value=output.value, + rho=nf_old, + rseed=rng.rseed_new(), + ) + cm_new = note.commitment() + + # logging + from trezor import log + + log.warning(__name__, "new Note:") + log.warning(__name__, "recipient: %s", str(note.recipient.to_bytes())) + log.warning(__name__, "value: %s", str(note.value)) + log.warning(__name__, "rho: %s", str(note.rho.to_bytes())) + log.warning(__name__, "rseed: %s", str(note.rseed)) + log.warning(__name__, "cmx: %s", str(cm_new.extract().to_bytes())) + + # value commitment + v_net = input.note.value - output.value + rcv = rng.rcv() + cv_net = commit_value(rcv, v_net) + + log_gc("build 2") + # note encryption + encrypted_note = encrypt_note( + note, + output.memo, + cv_net, + cm_new, + output.ovk, + rng, + ) + + log_gc("build 3") + return Action( + cv=cv_net.to_bytes(), + nf=nf_old.to_bytes(), + rk=rk.to_bytes(), + cmx=cm_new.extract().to_bytes(), + encrypted_note=encrypted_note, + ) diff --git a/core/src/apps/zcash/orchard/crypto/ff1.py b/core/src/apps/zcash/orchard/crypto/ff1.py new file mode 100644 index 00000000000..627ea722d42 --- /dev/null +++ b/core/src/apps/zcash/orchard/crypto/ff1.py @@ -0,0 +1,90 @@ +""" +Slightly optimized implementation of FF1 algorithm +specified by Morris Dworkin in: + +NIST Special Publication 800-38G +Recommendation for Block Cipher Modes of Operation: Methods for Format-Preserving Encryption + + +Input message length is fixed to 88 bits. +Radix is fixed to 2. +""" + + +from typing import TYPE_CHECKING + +from trezor.crypto import aes + +from .utils import chain, lebs2ip, take + +if TYPE_CHECKING: + from typing import Iterable + + pass # ff1.i + + +# big-endian bits to integer +def bebs2ip(bits: Iterable[int]) -> int: + acc = 0 + for bit in bits: + acc <<= 1 + acc += bit + return acc + + +# integer to big endian bits +def i2bebsp(l: int, x: int) -> Iterable[int]: + assert 0 <= x and x < (1 << l) + for i in range(l): + yield (x >> (l - 1 - i)) & 1 + return + + +def ff1_aes256_encrypt(key: bytes, tweak: bytes, x: Iterable[int]): + n = 88 # n = len(x) + t = len(tweak) + assert t <= 255 + u, v = 44, 44 # u = n//2; v = n-u + A = bebs2ip(take(u, x)) + B = bebs2ip(take(v, x)) + radix = 2 + b = 6 # b = cldiv(v, 8) + d = 12 # d = 4*cldiv(b, 4) + 4 + P = bytes([1, 2, 1, 0, 0, radix, 10, u % 256, 0, 0, 0, n, 0, 0, 0, t]) + for i in range(10): + Q = tweak + b"\x00" * ((-t - b - 1) % 16) + bytes([i]) + B.to_bytes(b, "big") + y = int.from_bytes(aes_cbcmac(key, P + Q)[:d], "big") + C = (A + y) & 0x0000_0FFF_FFFF_FFFF # 44-bit mask + A, B = B, C + + return chain(i2bebsp(u, A), i2bebsp(v, B)) + + +def aes_cbcmac(key, input): + cipher = aes(aes.CBC, key, b"\x00" * 16) + mac = cipher.encrypt(input)[-16:] + del cipher + return mac + + +def to_radix(message: bytes) -> Iterable[int]: + for n in message: + for _ in range(8): + yield n & 1 + n >>= 1 + return + + +def from_radix(bits: Iterable[int]) -> bytes: + data = [] + for _ in range(11): + byte = take(8, bits) + data.append(lebs2ip(byte)) + return bytes(data) + + +# https://zips.z.cash/protocol/protocol.pdf#concreteprps +def encrypt_diversifier_index(dk: bytes, index: int) -> bytes: + index_bits = to_radix(index.to_bytes(11, "little")) + diversifier_bits = ff1_aes256_encrypt(dk, b"", index_bits) + return from_radix(diversifier_bits) diff --git a/core/src/apps/zcash/orchard/crypto/generators.py b/core/src/apps/zcash/orchard/crypto/generators.py new file mode 100755 index 00000000000..8a5c1d79ebb --- /dev/null +++ b/core/src/apps/zcash/orchard/crypto/generators.py @@ -0,0 +1,35 @@ +"""Precomputed Orchard generators.""" + +from trezor.crypto.pallas import Point + +# https://zips.z.cash/protocol/nu5.pdf#concretespendauthsig +SPENDING_KEY_BASE = Point( + b"\x63\xc9\x75\xb8\x84\x72\x1a\x8d\x0c\xa1\x70\x7b\xe3\x0c\x7f\x0c\x5f\x44\x5f\x3e\x7c\x18\x8d\x3b\x06\xd6\xf1\x28\xb3\x23\x55\xb7" +) + +# https://zips.z.cash/protocol/nu5.pdf#commitmentsandnullifiers +NULLIFIER_K_BASE = Point( + b"\x75\xca\x47\xe4\xa7\x6a\x6f\xd3\x9b\xdb\xb5\xcc\x92\xb1\x7e\x5e\xcf\xc9\xf4\xfa\x71\x55\x37\x2e\x8d\x19\xa8\x9c\x16\xaa\xe7\x25" +) + +# https://zips.z.cash/protocol/nu5.pdf#concretehomomorphiccommit +VALUE_COMMITMENT_VALUE_BASE = Point( + b"\x67\x43\xf9\x3a\x6e\xbd\xa7\x2a\x8c\x7c\x5a\x2b\x7f\xa3\x04\xfe\x32\xb2\x9b\x4f\x70\x6a\xa8\xf7\x42\x0f\x3d\x8e\x7a\x59\x70\x2f" +) +VALUE_COMMITMENT_RANDOMNESS_BASE = Point( + b"\x91\x5a\x3c\x88\x68\xc6\xc3\x0e\x2f\x80\x90\xee\x45\xd7\x6e\x40\x48\x20\x8d\xea\x5b\x23\x66\x4f\xbb\x09\xa4\x0f\x55\x44\xf4\x07" +) + +# https://zips.z.cash/protocol/protocol.pdf#concretesinsemillacommit +NOTE_COMMITMENT_BASE = Point( + b"\x13\x6e\xfc\x0f\x48\x2c\x02\x2c\x7c\xa4\x14\xfc\x5c\xc5\x9e\x23\xf2\x3d\x6f\x93\xab\x9f\x23\xcd\x33\x45\xa9\x28\xc3\x06\xb2\xa6" +) +NOTE_COMMITMENT_Q = Point( + b"\x5d\x74\xa8\x40\x09\xba\x0e\x32\x2a\xdd\x46\xfd\x5a\x0f\x96\xc5\x5d\xed\xb0\x79\xb4\xf2\x9f\xf7\x0d\xcd\xfb\x56\xa0\x07\x80\x97" +) +IVK_COMMITMENT_BASE = Point( + b"\x18\xa1\xf8\x5f\x6e\x48\x23\x98\xc7\xed\x1a\xd3\xe2\x7f\x95\x02\x48\x89\x80\x40\x0a\x29\x34\x16\x4e\x13\x70\x50\xcd\x2c\xa2\xa5" +) +IVK_COMMITMENT_Q = Point( + b"\xf2\x82\x0f\x79\x92\x2f\xcb\x6b\x32\xa2\x28\x51\x24\xcc\x1b\x42\xfa\x41\xa2\x5a\xb8\x81\xcc\x7d\x11\xc8\xa9\x4a\xf1\x0c\xbc\x05" +) diff --git a/core/src/apps/zcash/orchard/crypto/keys.py b/core/src/apps/zcash/orchard/crypto/keys.py new file mode 100755 index 00000000000..ba29c72d637 --- /dev/null +++ b/core/src/apps/zcash/orchard/crypto/keys.py @@ -0,0 +1,114 @@ +# https://zips.z.cash/protocol/protocol.pdf#orchardkeycomponents + +from trezor.crypto.pallas import Fp, Scalar, to_base, to_scalar +from trezor.utils import ensure + +from . import ff1 +from .address import Address +from .generators import IVK_COMMITMENT_BASE, IVK_COMMITMENT_Q, SPENDING_KEY_BASE +from .sinsemilla import Sinsemilla +from .utils import i2lebsp, prf_expand + + +def sk_to_ask(sk: bytes) -> Scalar: + """Derives Spend Authorizing Key from Spending Key.""" + ask = to_scalar(prf_expand(sk, b"\x06")) + akP = ask * SPENDING_KEY_BASE + if akP.to_bytes()[-1] & 0x80 != 0: + ask = -ask + ensure(ask.is_not_zero()) + return ask + + +class FullViewingKey: + def __init__(self, ak: Fp, nk: Fp, rivk: Scalar): + self.ak = ak + self.nk = nk + self.rivk = rivk + + self._ivk: Scalar | None = None + self._ovk: bytes | None = None + self._dk: bytes | None = None + + @property + def ivk(self) -> Scalar: + if self._ivk is None: + self._derive_ivk() + assert self._ivk is not None + return self._ivk + + @property + def dk(self) -> bytes: + if self._dk is None: + self._derive_dk_and_ovk() + assert self._dk is not None # typing + return self._dk + + @property + def ovk(self) -> bytes: + if self._ovk is None: + self._derive_dk_and_ovk() + assert self._ovk is not None # typing + return self._ovk + + @staticmethod + def from_spending_key(sk: bytes) -> "FullViewingKey": + ask = to_scalar(prf_expand(sk, b"\x06")) + nk = to_base(prf_expand(sk, b"\x07")) + rivk = to_scalar(prf_expand(sk, b"\x08")) + ensure(ask.is_not_zero()) + ak = (ask * SPENDING_KEY_BASE).extract() + return FullViewingKey(ak, nk, rivk) + + # https://zips.z.cash/protocol/protocol.pdf#orchardfullviewingkeyencoding + def to_bytes(self) -> bytes: + return self.ak.to_bytes() + self.nk.to_bytes() + self.rivk.to_bytes() + + # https://zips.z.cash/protocol/protocol.pdf#orchardinviewingkeyencoding + def incoming_viewing_key(self) -> bytes: + if self.ivk is None: + self._derive_ivk() + if self.dk is None: + self._derive_dk_and_ovk() + return self.dk + self.ivk.to_bytes() + + def outgoing_viewing_key(self) -> bytes: + if self.ovk is None: + self._derive_dk_and_ovk() + return self.ovk + + def _derive_ivk(self) -> None: + ivk_base = commit_ivk(self.rivk, self.ak, self.nk) + assert ivk_base is not None + # Now convert Fp to Scalar. + # This requires no modular reduction because + # Pallas' base field is smaller than its scalar field. + self._ivk = Scalar(ivk_base.to_bytes()) + ensure(self._ivk.is_not_zero()) + + def _derive_dk_and_ovk(self) -> None: + K = self.rivk.to_bytes() + R = prf_expand(K, b"\x82" + self.ak.to_bytes() + self.nk.to_bytes()) + self._dk = R[:32] + self._ovk = R[32:] + + def address(self, index: int = 0) -> Address: + """Derives a diversified shielded address.""" + d = ff1.encrypt_diversifier_index(self.dk, index) + return Address.from_ivk(d, self.ivk) + + def internal(self) -> "FullViewingKey": + K = self.rivk.to_bytes() + rivk_internal = to_scalar( + prf_expand(K, b"\x83" + self.ak.to_bytes() + self.nk.to_bytes()) + ) + return FullViewingKey(self.ak, self.nk, rivk_internal) + + +# https://zips.z.cash/protocol/nu5.pdf#concreteNotecommit +def commit_ivk(rivk: Scalar, ak: Fp, nk: Fp) -> Fp: + h = Sinsemilla(IVK_COMMITMENT_Q) + h.update(i2lebsp(255, ak)) + h.update(i2lebsp(255, nk)) + commitment = h.finalize() + rivk * IVK_COMMITMENT_BASE + return commitment.extract() diff --git a/core/src/apps/zcash/orchard/crypto/note.py b/core/src/apps/zcash/orchard/crypto/note.py new file mode 100644 index 00000000000..f4ea1e5722e --- /dev/null +++ b/core/src/apps/zcash/orchard/crypto/note.py @@ -0,0 +1,96 @@ +from typing import TYPE_CHECKING + +from trezor.crypto.hashlib import poseidon +from trezor.crypto.pallas import Fp, Point, Scalar, to_base, to_scalar +from trezor.messages import ZcashOrchardInput + +from apps.common.writers import ( + write_bytes_fixed, + write_bytes_unchecked, + write_uint8, + write_uint64_le, +) + +from .address import Address +from .generators import NOTE_COMMITMENT_BASE, NOTE_COMMITMENT_Q, NULLIFIER_K_BASE +from .sinsemilla import Sinsemilla +from .utils import i2lebsp, leos2bsp, prf_expand + +if TYPE_CHECKING: + from trezor.utils import Writer + + +class Note: + def __init__(self, recipient: Address, value: int, rho: Fp, rseed: bytes) -> None: + self.recipient = recipient + self.value = value + self.rho = rho + self.rseed = rseed + + @staticmethod + def from_message(msg: ZcashOrchardInput) -> "Note": + return Note( + Address.from_bytes(msg.recipient), + msg.value, + Fp(msg.rho), + msg.rseed, + ) + + # `esk`, `rcm` and `psi` derivation defined in + # https://zips.z.cash/protocol/protocol.pdf#orchardsend + def _expand(self, domain: bytes) -> bytes: + return prf_expand(self.rseed, domain + self.rho.to_bytes()) + + def esk(self) -> Scalar: + return to_scalar(self._expand(b"\x04")) + + def rcm(self) -> Scalar: + return to_scalar(self._expand(b"\x05")) + + def psi(self) -> Fp: + return to_base(self._expand(b"\x09")) + + # https://zips.z.cash/protocol/nu5.pdf#concreteNotecommit + def commitment(self) -> Point: + h = Sinsemilla(NOTE_COMMITMENT_Q) + h.update(leos2bsp(self.recipient.g_d().to_bytes())) + h.update(leos2bsp(self.recipient.pk_d.to_bytes())) + h.update(i2lebsp(64, self.value)) + h.update(i2lebsp(255, self.rho)) + h.update(i2lebsp(255, self.psi())) + return h.finalize() + self.rcm() * NOTE_COMMITMENT_BASE + + # https://zips.z.cash/protocol/protocol.pdf#commitmentsandnullifiers + def nullifier(self, nk: Fp) -> Fp: + base = poseidon(nk, self.rho) + self.psi() + scalar = Scalar(base.to_bytes()) + point = scalar * NULLIFIER_K_BASE + self.commitment() + return point.extract() + + # https://zips.z.cash/protocol/nu5.pdf#notept + def write_plaintext(self, w: Writer, memo: str | bytes | None) -> None: + write_uint8(w, 0x02) + write_bytes_fixed(w, self.recipient.d, 11) + write_uint64_le(w, self.value) + write_bytes_fixed(w, self.rseed, 32) + write_memo(w, memo) + + +# https://zips.z.cash/zip-0302 +def write_memo(w: Writer, memo: str | bytes | None) -> None: + """Encodes a memo according to the ZIP-302 Standardized Memo Field Format""" + if memo is None: + write_uint8(w, 0xF6) + padding_length = 511 + elif isinstance(memo, str): + encoded = memo.encode() + if len(encoded) > 512: + raise ValueError("Memo is too long.") + write_bytes_unchecked(w, encoded) + padding_length = 512 - len(encoded) + else: + assert 0xF7 <= memo[0] <= 0xFF + write_bytes_fixed(w, memo, 512) + padding_length = 0 + + write_bytes_unchecked(w, padding_length * b"\x00") diff --git a/core/src/apps/zcash/orchard/crypto/note_encryption.py b/core/src/apps/zcash/orchard/crypto/note_encryption.py new file mode 100755 index 00000000000..c2277442169 --- /dev/null +++ b/core/src/apps/zcash/orchard/crypto/note_encryption.py @@ -0,0 +1,118 @@ +from micropython import const +from typing import TYPE_CHECKING + +from trezor.crypto import chacha20poly1305 +from trezor.crypto.hashlib import blake2b +from trezor.crypto.pallas import Point +from trezor.utils import empty_bytearray, ensure + +from apps.common.writers import write_bytes_fixed + +from .note import Note + +if TYPE_CHECKING: + from typing import Iterable + from ..random import ActionShieldingRng + +BLOCK_SIZE = const(64) +ENC_CIPHERTEXT_SIZE = const(580) +OUT_CIPHERTEXT_SIZE = const(80) + + +# https://zips.z.cash/protocol/nu5.pdf#concreteorchardkdf +def kdf_orchard(shared_secret: Point, ephemeral_key: bytes) -> bytes: + digest = blake2b(outlen=32, personal=b"Zcash_OrchardKDF") + digest.update(shared_secret.to_bytes()) + digest.update(ephemeral_key) + return digest.digest() + + +# https://zips.z.cash/protocol/nu5.pdf#concreteprfs +def prf_ock_orchard( + ovk: bytes, + cv: bytes, + cmx: bytes, + ephemeral_key: bytes, +) -> bytes: + digest = blake2b(outlen=32, personal=b"Zcash_Orchardock") + digest.update(ovk) + digest.update(cv) + digest.update(cmx) + digest.update(ephemeral_key) + return digest.digest() + + +def chunks(size: int, length: int) -> Iterable[tuple[int, int]]: + offset = 0 + while offset + size <= length: + yield offset, offset + size + offset += size + if offset < length: + yield offset, length + return + + +# https://zips.z.cash/protocol/nu5.pdf#concretesym +def sym_encrypt(key: bytes, buffer: bytearray) -> None: + nonce = 12 * b"\x00" + cipher = chacha20poly1305(key, nonce) + for i, j in chunks(BLOCK_SIZE, len(buffer)): + buffer[i:j] = cipher.encrypt(buffer[i:j]) + buffer.extend(cipher.finish()) # append tag + + +class TransmittedNoteCiphertext: + def __init__( + self, + epk_bytes: bytes, + enc_ciphertext: bytes, + out_ciphertext: bytes, + ): + self.epk_bytes = epk_bytes + self.enc_ciphertext = enc_ciphertext + self.out_ciphertext = out_ciphertext + + +# https://zips.z.cash/protocol/nu5.pdf#saplingandorchardencrypt +def encrypt_note( + note: Note, + memo: str | bytes | None, + cv_new: Point, + cm_new: Point, + ovk: bytes | None, + rng: ActionShieldingRng, +) -> TransmittedNoteCiphertext: + np = empty_bytearray(ENC_CIPHERTEXT_SIZE) + note.write_plaintext(np, memo) + + esk = note.esk() + ensure(esk.is_not_zero()) + g_d = note.recipient.g_d() + pk_d = note.recipient.pk_d + + # https://zips.z.cash/protocol/nu5.pdf#concreteorchardkeyagreement + epk = esk * g_d # KA.DerivePublic + shared_secret = esk * pk_d # KA.Agree + + ephemeral_key = epk.to_bytes() + k_enc = kdf_orchard(shared_secret, ephemeral_key) + sym_encrypt(k_enc, np) + + op = empty_bytearray(OUT_CIPHERTEXT_SIZE) + if ovk is None: + ock = rng.ock() + write_bytes_fixed(op, rng.op(), 64) + else: + cv = cv_new.to_bytes() + cmx = cm_new.extract().to_bytes() + ock = prf_ock_orchard(ovk, cv, cmx, ephemeral_key) + write_bytes_fixed(op, pk_d.to_bytes(), 32) + write_bytes_fixed(op, esk.to_bytes(), 32) + + sym_encrypt(ock, op) + + return TransmittedNoteCiphertext( + epk_bytes=ephemeral_key, + enc_ciphertext=bytes(np), + out_ciphertext=bytes(op), + ) diff --git a/core/src/apps/zcash/orchard/crypto/redpallas.py b/core/src/apps/zcash/orchard/crypto/redpallas.py new file mode 100644 index 00000000000..f215407fee8 --- /dev/null +++ b/core/src/apps/zcash/orchard/crypto/redpallas.py @@ -0,0 +1,44 @@ +# https://zips.z.cash/protocol/protocol.pdf#concretereddsa + +from typing import TYPE_CHECKING + +from trezor.crypto.hashlib import blake2b +from trezor.crypto.pallas import to_scalar + +from .generators import SPENDING_KEY_BASE + +if TYPE_CHECKING: + from trezor.crypto.pallas import Scalar + + pass + + +def randomize(sk: Scalar, randomizer: Scalar) -> Scalar: + return sk + randomizer + + +def H_star(x: bytes) -> Scalar: + digest = blake2b(personal=b"Zcash_RedPallasH", data=x).digest() + return to_scalar(digest) + + +def sign_spend_auth(sk: Scalar, message: bytes) -> bytes: + # According to the Redpallas specification, `T` should be uniformly random + # sequence of 32 bytes. Since Trezor output must be deterministic (to prevent + # secret leakage caused by mallicious hw randomness generator), we set + T = sk.to_bytes() + # We use the same technique for BIP-340 Bitcoin signatures. + # The only differences between these two schemes are: + # 1. in BIP-340 we set `T = sk xor some_constant` + # 2. BIP-340 uses secp256k1 curve + # 3. BIP-340 additionally requires y coordinate of `R` to be non negative + # According to our security analysis, setting `T = bytes(sk)` does harm + # security the RedPallas signature scheme. Also notice that this change does + # not require changes in RedPallas verification algorithm. + + vk: bytes = (sk * SPENDING_KEY_BASE).to_bytes() + r: Scalar = H_star(T + vk + message) + R: bytes = (r * SPENDING_KEY_BASE).to_bytes() + e: Scalar = H_star(R + vk + message) + S: bytes = (r + e * sk).to_bytes() + return R + S diff --git a/core/src/apps/zcash/orchard/crypto/sinsemilla.py b/core/src/apps/zcash/orchard/crypto/sinsemilla.py new file mode 100644 index 00000000000..2fc46d0c89d --- /dev/null +++ b/core/src/apps/zcash/orchard/crypto/sinsemilla.py @@ -0,0 +1,59 @@ +""" +Implementation of Sinsemilla hash function. +see: https://zips.z.cash/protocol/protocol.pdf#concretesinsemillahash +""" + +from micropython import const +from typing import TYPE_CHECKING + +from trezor.crypto.pallas import group_hash +from trezor.utils import ensure + +from .utils import i2leosp, lebs2ip + +if TYPE_CHECKING: + from trezor.crypto.pallas import Point + + pass # sinsemilla.i + + +K = const(10) + + +def iadd(a: Point, b: Point) -> Point: + """Incomplete addition.""" + ensure(not a.is_identity()) + ensure(not b.is_identity()) + ensure(a != b) + ensure(a != -b) + return a + b + + +class Sinsemilla: + def __init__(self, acc): + self.buffer = [] + self.acc = acc + + @staticmethod + def personalized_by(personal: bytes): + Q = group_hash("z.cash:SinsemillaQ", personal) + return Sinsemilla(Q) + + def update(self, bits): + for bit in bits: + self.buffer.append(bit) + if len(self.buffer) == K: + self.digest_buffer() + + def digest_buffer(self): + index = lebs2ip(self.buffer) + S = group_hash("z.cash:SinsemillaS", i2leosp(32, index)) + acc = self.acc + self.acc = iadd(iadd(acc, S), acc) + self.buffer = [] + + def finalize(self): + if len(self.buffer) > 0: + self.buffer.extend([0] * (K - len(self.buffer))) # padding + self.digest_buffer() + return self.acc diff --git a/core/src/apps/zcash/orchard/crypto/utils.py b/core/src/apps/zcash/orchard/crypto/utils.py new file mode 100644 index 00000000000..5fa005dcb78 --- /dev/null +++ b/core/src/apps/zcash/orchard/crypto/utils.py @@ -0,0 +1,76 @@ +from typing import TYPE_CHECKING + +from trezor.crypto import random +from trezor.crypto.hashlib import blake2b +from trezor.crypto.pallas import Fp, Scalar + +if TYPE_CHECKING: + from typing import Iterable + + pass # utils.i + + +# https://zips.z.cash/protocol/protocol.pdf#concreteprfs +def prf_expand(sk: bytes, t: bytes) -> bytes: + digest = blake2b(personal=b"Zcash_ExpandSeed") + digest.update(sk) + digest.update(t) + return digest.digest() + + +# ceil div (div rounded up) +def cldiv(n, divisor): + return (n + (divisor - 1)) // divisor + + +# integer to little-endian bits +def i2lebsp(l: int, x: Fp | Scalar | int) -> Iterable[int]: + if isinstance(x, Fp) or isinstance(x, Scalar): + gen = leos2bsp(x.to_bytes()) + for _ in range(l): + yield next(gen) + return + elif isinstance(x, int): + for i in range(l): + yield (x >> i) & 1 + return + else: + raise ValueError() + + +# integer to little-endian bytes +def i2leosp(l, x): + return x.to_bytes(cldiv(l, 8), "little") + + +# little endian bits to interger +def lebs2ip(bits: Iterable[int]) -> int: + acc = 0 + for i, bit in enumerate(bits): + acc += bit << i + return acc + + +# little-endian bytes to little endian- bits +def leos2bsp(buf): + for byte in buf: + for i in range(8): + yield (byte >> i) & 1 + return + + +def take(i, gen): + """Creates a new generator, which returns `i` elements + of the original generator.""" + if isinstance(gen, list): + gen = iter(gen) + for _ in range(i): + yield next(gen) + return + + +def chain(gen_a, gen_b): + """Chains two generators into one.""" + yield from gen_a + yield from gen_b + return diff --git a/core/src/apps/zcash/orchard/get_fvk.py b/core/src/apps/zcash/orchard/get_fvk.py new file mode 100644 index 00000000000..feefd3770cb --- /dev/null +++ b/core/src/apps/zcash/orchard/get_fvk.py @@ -0,0 +1,19 @@ +from typing import TYPE_CHECKING + +from trezor.messages import ZcashFullViewingKey, ZcashGetFullViewingKey + +from .. import layout +from .keychain import with_keychain + +if TYPE_CHECKING: + from trezor.wire import Context + from .keychain import OrchardKeychain + + +@with_keychain +async def get_fvk( + ctx: Context, msg: ZcashGetFullViewingKey, keychain: OrchardKeychain +) -> ZcashFullViewingKey: + await layout.require_confirm_export_fvk(ctx) + fvk = keychain.derive(msg.z_address_n).full_viewing_key() + return ZcashFullViewingKey(fvk=fvk.to_bytes()) diff --git a/core/src/apps/zcash/orchard/get_ivk.py b/core/src/apps/zcash/orchard/get_ivk.py new file mode 100644 index 00000000000..d332658a499 --- /dev/null +++ b/core/src/apps/zcash/orchard/get_ivk.py @@ -0,0 +1,19 @@ +from typing import TYPE_CHECKING + +from trezor.messages import ZcashGetIncomingViewingKey, ZcashIncomingViewingKey + +from .. import layout +from .keychain import with_keychain + +if TYPE_CHECKING: + from trezor.wire import Context + from .keychain import OrchardKeychain + + +@with_keychain +async def get_ivk( + ctx: Context, msg: ZcashGetIncomingViewingKey, keychain: OrchardKeychain +) -> ZcashIncomingViewingKey: + await layout.require_confirm_export_ivk(ctx) + fvk = keychain.derive(msg.z_address_n).full_viewing_key() + return ZcashIncomingViewingKey(ivk=fvk.incoming_viewing_key()) diff --git a/core/src/apps/zcash/orchard/keychain.py b/core/src/apps/zcash/orchard/keychain.py new file mode 100644 index 00000000000..b7fdddf1145 --- /dev/null +++ b/core/src/apps/zcash/orchard/keychain.py @@ -0,0 +1,105 @@ +""" +Implementation of Orchard key derivation scheme +for deterministic wallets according to the ZIP-32. + +see: https://zips.z.cash/zip-0032 +""" + +from typing import TYPE_CHECKING + +from trezor.crypto.hashlib import blake2b + +from apps.bitcoin.keychain import get_coin_by_name +from apps.common.keychain import Keychain +from apps.common.paths import PathSchema +from apps.common.seed import get_seed + +from .crypto.keys import FullViewingKey, sk_to_ask +from .crypto.utils import i2leosp, prf_expand + +if TYPE_CHECKING: + from apps.common.coininfo import CoinInfo + from apps.common.paths import Bip32Path + from trezor.wire import Context + from trezor.crypto.pallas import Scalar + from typing import Callable, TypeVar, Awaitable + from typing_extensions import Protocol + + class MsgWithCoinNameType(Protocol): + coin_name: str + + MsgIn = TypeVar("MsgIn", bound=MsgWithCoinNameType) + Result = TypeVar("Result") + + +PATTERN_ZIP32 = "m/32'/coin_type'/account'" + + +class ExtendedSpendingKey: + def __init__(self, sk: bytes, c: bytes) -> None: + self.sk = sk # spending key + self.c = c # chain code + + def spending_key(self) -> bytes: + return self.sk + + def full_viewing_key(self) -> FullViewingKey: + return FullViewingKey.from_spending_key(self.sk) + + def spend_authorizing_key(self) -> Scalar: + return sk_to_ask(self.sk) + + @staticmethod + def get_master(seed: bytes) -> "ExtendedSpendingKey": + """Generates the Orchard master ExtendedSpendingKey from `seed`.""" + I = blake2b(personal=b"ZcashIP32Orchard", data=seed).digest() + return ExtendedSpendingKey(sk=I[:32], c=I[32:]) + + # apps.common.keychain.NodeProtocol methods: + + def derive_path(self, path: Bip32Path) -> None: + """Derives a descendant ExtendedSpendingKey according to the `path`.""" + for i in path: + assert i >= 1 << 31 + I = prf_expand(self.c, bytes([0x81]) + self.sk + i2leosp(32, i)) + self.sk, self.c = I[:32], I[32:] + + def clone(self) -> "ExtendedSpendingKey": + return ExtendedSpendingKey(self.sk, self.c) + + def __del__(self): + del self.sk + del self.c + + +class OrchardKeychain(Keychain): + def __init__(self, seed: bytes, coin: CoinInfo) -> None: + schema = PathSchema.parse(PATTERN_ZIP32, (coin.slip44,)) + super().__init__(seed, "pallas", [schema], [[b"Zcash Orchard"]]) + + @staticmethod + async def for_coin(ctx: Context, coin: CoinInfo) -> "OrchardKeychain": + seed = await get_seed(ctx) + return OrchardKeychain(seed, coin) + + def derive(self, path: Bip32Path) -> ExtendedSpendingKey: + self.verify_path(path) + return self._derive_with_cache( + prefix_len=3, + path=path, + new_root=lambda: ExtendedSpendingKey.get_master(self.seed), + ) + + def root_fingerprint(self) -> int: + raise NotImplementedError + + +def with_keychain( + func: Callable[[Context, MsgIn, OrchardKeychain], Awaitable[Result]] +) -> Callable[[Context, MsgIn], Awaitable[Result]]: + async def wrapper(ctx: Context, msg: MsgIn): + coin = get_coin_by_name(msg.coin_name) + keychain = await OrchardKeychain.for_coin(ctx, coin) + return await func(ctx, msg, keychain) + + return wrapper diff --git a/core/src/apps/zcash/orchard/random.py b/core/src/apps/zcash/orchard/random.py new file mode 100644 index 00000000000..f29eaf9640c --- /dev/null +++ b/core/src/apps/zcash/orchard/random.py @@ -0,0 +1,74 @@ +from typing import TYPE_CHECKING + +from trezor.crypto.hashlib import blake2b +from trezor.crypto.pallas import to_base, to_scalar + +from .crypto.address import Address +from .crypto.keys import sk_to_ask + +if TYPE_CHECKING: + from typing import Any + from trezor.crypto.pallas import Scalar, Fp + + +class BundleShieldingRng: + def __init__(self, seed: bytes) -> None: + self.seed = seed + + def for_action(self, i: int) -> "ActionShieldingRng": + h = blake2b(personal=b"TrezorActionSeed", outlen=32) + h.update(self.seed) + h.update(i.to_bytes(4, "little")) + return ActionShieldingRng(h.digest()) + + def _shuffle(self, x: list[Any], personal: bytes) -> None: + pass # Suite shuffles inputs + + def shuffle_outputs(self, outputs: list[int | None]) -> None: + self._shuffle(outputs, personal=b"TrezorSpendsPerm") + + def shuffle_inputs(self, inputs: list[int | None]) -> None: + self._shuffle(inputs, personal=b"TrezorInputsPerm") + + +class ActionShieldingRng: + def __init__(self, seed: bytes) -> None: + self.seed = seed + + def random(self, dst: bytes, outlen: int = 64) -> bytes: + h = blake2b(personal=b"TrezorExpandSeed", outlen=outlen) + h.update(self.seed) + h.update(dst) + return h.digest() + + def alpha(self) -> Scalar: + return to_scalar(self.random(b"alpha")) + + def rcv(self) -> Scalar: + return to_scalar(self.random(b"rcv")) + + def recipient(self) -> Address: + d = self.random(b"d", 11) + ivk = to_scalar(self.random(b"ivk")) + return Address.from_ivk(d, ivk) + + def ock(self) -> bytes: + return self.random(b"ock", 32) + + def op(self) -> bytes: + return self.random(b"op", 64) + + def rseed_old(self) -> bytes: + return self.random(b"rseed_old", 32) + + def rseed_new(self) -> bytes: + return self.random(b"rseed_new", 32) + + def dummy_sk(self) -> bytes: + return self.random(b"dummy_sk", 32) + + def dummy_ask(self) -> Scalar: + return sk_to_ask(self.dummy_sk()) + + def rho(self) -> Fp: + return to_base(self.random(b"rho")) diff --git a/core/src/apps/zcash/orchard/signer.py b/core/src/apps/zcash/orchard/signer.py new file mode 100644 index 00000000000..afa8701a80d --- /dev/null +++ b/core/src/apps/zcash/orchard/signer.py @@ -0,0 +1,296 @@ +import gc +from micropython import const +from typing import TYPE_CHECKING + +from trezor import log +from trezor.crypto.hashlib import blake2b +from trezor.enums import RequestType, ZcashSignatureType +from trezor.messages import TxRequest, ZcashAck, ZcashOrchardInput, ZcashOrchardOutput +from trezor.wire import DataError + +from apps.bitcoin.sign_tx import helpers +from apps.common.paths import HARDENED + +from .. import unified_addresses +from ..debug import watch_gc, watch_gc_async +from ..hasher import ZcashHasher +from .accumulator import MessageAccumulator +from .crypto import builder, redpallas +from .crypto.address import Address +from .crypto.note import Note +from .random import BundleShieldingRng + +if TYPE_CHECKING: + from typing import Awaitable, List + from apps.common.coininfo import CoinInfo + from apps.bitcoin.sign_tx.tx_info import TxInfo + from .keychain import OrchardKeychain + from .crypto.keys import FullViewingKey + from ..approver import ZcashApprover + from .random import ActionShieldingRng + + +OVERWINTERED = const(0x8000_0000) +FLAGS = const(0b0000_0011) # spends enbled and output enabled + + +def skip_if_empty(func): + """ + A function decorated by this will not be evaluated, + if the Orchard bundle is impty. + """ + + async def wrapper(self): + if self.actions_count == 0: + return + else: + await func(self) + + return wrapper + + +class OrchardSigner: + @watch_gc + def __init__( + self, + tx_info: TxInfo, + keychain: OrchardKeychain, + approver: ZcashApprover, + coin: CoinInfo, + tx_req: TxRequest, + ) -> None: + assert tx_req.serialized is not None # typing + + if tx_info.tx.orchard is None: + self.actions_count = 0 + self.inputs_count = 0 + self.outputs_count = 0 + else: + self.inputs_count = tx_info.tx.orchard.inputs_count + self.outputs_count = tx_info.tx.orchard.outputs_count + + if self.inputs_count + self.outputs_count > 0: + self.actions_count = max( + 2, # minimal required amount of actions + self.inputs_count, + self.outputs_count, + ) + else: + self.actions_count = 0 + + if self.actions_count == 0: + return # no need to initialize other attributes + + self.tx_info = tx_info + self.keychain = keychain + self.approver = approver + self.coin = coin + self.tx_req = tx_req + assert isinstance(tx_info.sig_hasher, ZcashHasher) + self.sig_hasher: ZcashHasher = tx_info.sig_hasher + + assert tx_info.tx.orchard is not None + account = tx_info.tx.orchard.account + assert account is not None # typing + key_path = [ + 32 | HARDENED, # ZIP-32 constant + coin.slip44 | HARDENED, # purpose + account | HARDENED, # account + ] + self.key_node = keychain.derive(key_path) + + msg_acc_key = keychain.derive_slip21( + [b"Zcash Orchard", b"Message Accumulator Key"], + ).key() + self.msg_acc = MessageAccumulator(secret=msg_acc_key) + + self.rng = None + + @skip_if_empty + @watch_gc_async + async def process_inputs(self) -> None: + for i in range(self.inputs_count): + txi = await self.get_input(i) + self.msg_acc.xor_message(txi, i) # add message to accumulator + self.approver.add_orchard_input(txi) + + @skip_if_empty + @watch_gc_async + async def approve_outputs(self) -> None: + for i in range(self.outputs_count): + txo = await self.get_output(i) + self.msg_acc.xor_message(txo, i) # add message to accumulator + if output_is_internal(txo): + self.approver.add_orchard_change_output(txo) + else: + await self.approver.add_orchard_external_output(txo) + + @skip_if_empty + @watch_gc_async + async def compute_digest(self) -> None: + # shielding seed + shielding_seed = self.derive_shielding_seed() + self.rng = BundleShieldingRng(seed=shielding_seed) + + # send shielded_seed to the host + assert self.tx_req.serialized is not None # typing + self.tx_req.serialized.zcash_shielding_seed = shielding_seed + await self.release_serialized() + + # shuffle inputs + inputs: List[int | None] = list(range(self.inputs_count)) + assert inputs is not None # typing + pad(inputs, self.actions_count) + self.rng.shuffle_inputs(inputs) + self.shuffled_inputs = inputs + + # shuffle_outputs + outputs: List[int | None] = list(range(self.outputs_count)) + assert outputs is not None # typing + pad(outputs, self.actions_count) + self.rng.shuffle_outputs(outputs) + self.shuffled_outputs = outputs + + # precompute Full Viewing Key + fvk = self.key_node.full_viewing_key() + + # shield and hash actions + log.info(__name__, "=== start shielding ===") + for i, (j, k) in enumerate( + zip( + self.shuffled_inputs, + self.shuffled_outputs, + ) + ): + gc.collect() + log.info(__name__, "=== action %d (io: %s %s) ===", i, str(j), str(k)) + rng_i = self.rng.for_action(i) + input_info = await self.build_input_info(j, fvk, rng_i) + output_info = await self.build_output_info(k, fvk, rng_i) + + action = builder.build_action(input_info, output_info, rng_i) + self.sig_hasher.orchard.add_action(action) + + gc.collect() + log.info(__name__, "=== end shielding ===") + + # check that message accumulator is empty + self.msg_acc.check() + + # hash orchard footer + assert self.tx_info.tx.orchard is not None # typing + self.sig_hasher.orchard.finalize( + flags=FLAGS, + value_balance=self.approver.orchard_balance, + anchor=self.tx_info.tx.orchard.anchor, + ) + + def derive_shielding_seed(self) -> bytes: + ss_slip21 = self.keychain.derive_slip21( + [b"Zcash Orchard", b"bundle_shielding_seed"], + ).key() + ss_hasher = blake2b(personal=b"TrezorShieldSeed", outlen=32) + ss_hasher.update(self.sig_hasher.header.digest()) + ss_hasher.update(self.sig_hasher.transparent.digest()) + ss_hasher.update(self.msg_acc.state) + ss_hasher.update(self.tx_info.tx.orchard.anchor) + ss_hasher.update(ss_slip21) + return ss_hasher.digest() + + @watch_gc_async + async def build_input_info( + self, + index: int | None, + fvk: FullViewingKey, + rng: ActionShieldingRng, + ) -> builder.InputInfo: + if index is None: + return builder.InputInfo.dummy(rng) + + txi = await self.get_input(index) + self.msg_acc.xor_message(txi, index) # remove message from accumulator + + note = Note.from_message(txi) + return builder.InputInfo(note, fvk) + + @watch_gc_async + async def build_output_info( + self, + index: int | None, + fvk: FullViewingKey, + rng: ActionShieldingRng, + ) -> builder.OutputInfo: + if index is None: + return builder.OutputInfo.dummy(rng) + + txo = await self.get_output(index) + self.msg_acc.xor_message(txo, index) # remove message from accumulator + + if output_is_internal(txo): + fvk = fvk.internal() + address = fvk.address(0) + else: + assert txo.address is not None # typing + receivers = unified_addresses.decode(txo.address, self.coin) + address = receivers.get(unified_addresses.Typecode.ORCHARD) + if address is None: + raise DataError("Address has not an Orchard receiver.") + address = Address.from_bytes(address) + + ovk = fvk.outgoing_viewing_key() + return builder.OutputInfo(ovk, address, txo.amount, txo.memo) + + @skip_if_empty + @watch_gc_async + async def sign_inputs(self) -> None: + sighash = self.sig_hasher.signature_digest() + sig_type = ZcashSignatureType.ORCHARD_SPEND_AUTH + ask = self.key_node.spend_authorizing_key() + assert self.rng is not None + for i, j in enumerate(self.shuffled_inputs): + rng = self.rng.for_action(i) + ask = rng.dummy_ask() if j is None else ask + rsk = redpallas.randomize(ask, rng.alpha()) + signature = redpallas.sign_spend_auth(rsk, sighash) + await self.set_serialized_signature(i, signature, sig_type) + + async def set_serialized_signature( + self, i: int, signature: bytes, sig_type: ZcashSignatureType + ) -> None: + assert self.tx_req.serialized is not None + s = self.tx_req.serialized + if s.signature_index is not None: + await self.release_serialized() + s.signature_index = i + s.signature = signature + s.signature_type = sig_type + + def get_input(self, i) -> Awaitable[ZcashOrchardInput]: # type: ignore [awaitable-is-generator] + self.tx_req.request_type = RequestType.TXORCHARDINPUT + assert self.tx_req.details # typing + self.tx_req.details.request_index = i + txi = yield ZcashOrchardInput, self.tx_req + helpers._clear_tx_request(self.tx_req) + return txi + + def get_output(self, i: int) -> Awaitable[ZcashOrchardOutput]: # type: ignore [awaitable-is-generator] + self.tx_req.request_type = RequestType.TXORCHARDOUTPUT + assert self.tx_req.details is not None # typing + self.tx_req.details.request_index = i + txo = yield ZcashOrchardOutput, self.tx_req + helpers._clear_tx_request(self.tx_req) + return txo + + def release_serialized(self) -> Awaitable[None]: # type: ignore [awaitable-is-generator] + self.tx_req.request_type = RequestType.NO_OP + res = yield ZcashAck, self.tx_req + helpers._clear_tx_request(self.tx_req) + return res + + +def pad(items: list[int | None], target_length: int) -> None: + items.extend((target_length - len(items)) * [None]) + + +def output_is_internal(txo: ZcashOrchardOutput) -> bool: + return txo.address is None diff --git a/core/src/apps/zcash/signer.py b/core/src/apps/zcash/signer.py index 66136e0a5f6..9c2296062ba 100644 --- a/core/src/apps/zcash/signer.py +++ b/core/src/apps/zcash/signer.py @@ -1,7 +1,7 @@ from micropython import const from typing import TYPE_CHECKING -from trezor.enums import OutputScriptType +from trezor.enums import OutputScriptType, ZcashSignatureType from trezor.messages import SignTx from trezor.utils import ensure from trezor.wire import DataError, ProcessError @@ -12,7 +12,9 @@ from apps.common.writers import write_compact_size, write_uint32_le from . import unified_addresses +from .approver import ZcashApprover from .hasher import ZcashHasher +from .orchard.signer import OrchardSigner from .unified_addresses import Typecode if TYPE_CHECKING: @@ -28,6 +30,7 @@ TxOutput, ) from apps.bitcoin.keychain import Keychain + from .orchard.keychain import OrchardKeychain OVERWINTERED = const(0x8000_0000) @@ -39,12 +42,23 @@ def __init__( keychain: Keychain, coin: CoinInfo, approver: Approver | None, + orchard_keychain: OrchardKeychain, ) -> None: ensure(coin.overwintered) if tx.version != 5: raise DataError("Expected transaction version 5.") + assert approver is None + approver = ZcashApprover(tx, coin) + super().__init__(tx, keychain, coin, approver) + self.orchard = OrchardSigner( + self.tx_info, + orchard_keychain, + approver, + coin, + self.tx_req, + ) def create_sig_hasher(self, tx: SignTx | PrevTx) -> ZcashHasher: return ZcashHasher(tx) @@ -54,6 +68,14 @@ def create_hash_writer(self) -> HashWriter: # so this should never be called. raise NotImplementedError + async def step1_process_inputs(self): + await super().step1_process_inputs() + await self.orchard.process_inputs() + + async def step2_approve_outputs(self): + await super().step2_approve_outputs() + await self.orchard.approve_outputs() + async def step3_verify_inputs(self) -> None: # Replacement transactions are not supported. @@ -64,9 +86,27 @@ async def step3_verify_inputs(self) -> None: await super().step3_verify_inputs() self.taproot_only = False # turn off taproot behavior - async def step5_serialize_outputs(self) -> None: + async def step4_serialize_inputs(self): + # shield actions first to get a sighash + await self.orchard.compute_digest() + + await super().step4_serialize_inputs() + + async def step5_serialize_outputs(self): + # transparent await super().step5_serialize_outputs() + # Sapling + write_compact_size(self.serialized_tx, 0) # nSpendsSapling + write_compact_size(self.serialized_tx, 0) # nOutputsSapling + + # nActionsOrchard + write_compact_size(self.serialized_tx, self.orchard.actions_count) + + async def step6_sign_segwit_inputs(self): + # transparent inputs were signed in step 4 + await self.orchard.sign_inputs() + async def sign_nonsegwit_input(self, i_sign: int) -> None: await self.sign_nonsegwit_bip143_input(i_sign) @@ -110,11 +150,7 @@ def write_tx_header( write_uint32_le(w, tx.expiry) # expiryHeight def write_tx_footer(self, w: Writer, tx: SignTx | PrevTx) -> None: - # serialize Sapling bundle - write_compact_size(w, 0) # nSpendsSapling - write_compact_size(w, 0) # nOutputsSapling - # serialize Orchard bundle - write_compact_size(w, 0) # nActionsOrchard + pass # there is no footer for v5 Zcash transactions def output_derive_script(self, txo: TxOutput) -> bytes: # unified addresses @@ -132,3 +168,8 @@ def output_derive_script(self, txo: TxOutput) -> bytes: # transparent addresses return super().output_derive_script(txo) + + def set_serialized_signature(self, i: int, signature: bytes) -> None: + super().set_serialized_signature(i, signature) + assert self.tx_req.serialized is not None + self.tx_req.serialized.signature_type = ZcashSignatureType.TRANSPARENT diff --git a/core/src/trezor/crypto/curve.py b/core/src/trezor/crypto/curve.py index e66077e639d..c2b8daf25c5 100644 --- a/core/src/trezor/crypto/curve.py +++ b/core/src/trezor/crypto/curve.py @@ -1 +1,74 @@ from trezorcrypto import bip340, curve25519, ed25519, nist256p1, secp256k1 # noqa: F401 + +# from trezorcrypto import curve25519, ed25519, nist256p1, secp256k1 +# import mock_bip340 as bip340 # noqa: F401 + + +class bip340____: # noqa: F401 + BYTES = 32 * b"\x00" + # extmod/modtrezorcrypto/modtrezorcrypto-bip340.h + @staticmethod + def generate_secret() -> bytes: + """ + Generate secret key. + """ + return BYTES + + # extmod/modtrezorcrypto/modtrezorcrypto-bip340.h + @staticmethod + def publickey(secret_key: bytes) -> bytes: + """ + Computes public key from secret key. + """ + return BYTES + + # extmod/modtrezorcrypto/modtrezorcrypto-bip340.h + @staticmethod + def sign( + secret_key: bytes, + digest: bytes, + ) -> bytes: + """ + Uses secret key to produce the signature of the digest. + """ + return BYTES + BYTES + + # extmod/modtrezorcrypto/modtrezorcrypto-bip340.h + @staticmethod + def verify_publickey(public_key: bytes) -> bool: + """ + Verifies whether the public key is valid. + Returns True on success. + """ + return True + + # extmod/modtrezorcrypto/modtrezorcrypto-bip340.h + @staticmethod + def verify(public_key: bytes, signature: bytes, digest: bytes) -> bool: + """ + Uses public key to verify the signature of the digest. + Returns True on success. + """ + return True + + # extmod/modtrezorcrypto/modtrezorcrypto-bip340.h + @staticmethod + def tweak_public_key( + public_key: bytes, + root_hash: bytes | None = None, + ) -> bytes: + """ + Tweaks the public key with the specified root_hash. + """ + return BYTES + + # extmod/modtrezorcrypto/modtrezorcrypto-bip340.h + @staticmethod + def tweak_secret_key( + secret_key: bytes, + root_hash: bytes | None = None, + ) -> bytes: + """ + Tweaks the secret key with the specified root_hash. + """ + return BYTES diff --git a/core/src/trezor/crypto/mock_bip340.py b/core/src/trezor/crypto/mock_bip340.py new file mode 100644 index 00000000000..da2c72e78c9 --- /dev/null +++ b/core/src/trezor/crypto/mock_bip340.py @@ -0,0 +1,68 @@ +from typing import * + +BYTES = 32 * b"\x00" +# extmod/modtrezorcrypto/modtrezorcrypto-bip340.h +def generate_secret() -> bytes: + """ + Generate secret key. + """ + return BYTES + + +# extmod/modtrezorcrypto/modtrezorcrypto-bip340.h +def publickey(secret_key: bytes) -> bytes: + """ + Computes public key from secret key. + """ + return BYTES + + +# extmod/modtrezorcrypto/modtrezorcrypto-bip340.h +def sign( + secret_key: bytes, + digest: bytes, +) -> bytes: + """ + Uses secret key to produce the signature of the digest. + """ + return BYTES + BYTES + + +# extmod/modtrezorcrypto/modtrezorcrypto-bip340.h +def verify_publickey(public_key: bytes) -> bool: + """ + Verifies whether the public key is valid. + Returns True on success. + """ + return True + + +# extmod/modtrezorcrypto/modtrezorcrypto-bip340.h +def verify(public_key: bytes, signature: bytes, digest: bytes) -> bool: + """ + Uses public key to verify the signature of the digest. + Returns True on success. + """ + return True + + +# extmod/modtrezorcrypto/modtrezorcrypto-bip340.h +def tweak_public_key( + public_key: bytes, + root_hash: bytes | None = None, +) -> bytes: + """ + Tweaks the public key with the specified root_hash. + """ + return BYTES + + +# extmod/modtrezorcrypto/modtrezorcrypto-bip340.h +def tweak_secret_key( + secret_key: bytes, + root_hash: bytes | None = None, +) -> bytes: + """ + Tweaks the secret key with the specified root_hash. + """ + return BYTES diff --git a/core/src/trezor/enums/MessageType.py b/core/src/trezor/enums/MessageType.py index c245a903b27..aac035a7f29 100644 --- a/core/src/trezor/enums/MessageType.py +++ b/core/src/trezor/enums/MessageType.py @@ -231,3 +231,13 @@ WebAuthnCredentials = 801 WebAuthnAddResidentCredential = 802 WebAuthnRemoveResidentCredential = 803 + ZcashGetFullViewingKey = 893 + ZcashFullViewingKey = 894 + ZcashGetIncomingViewingKey = 895 + ZcashIncomingViewingKey = 896 + ZcashGetAddress = 897 + ZcashAddress = 898 + ZcashOrchardBundleInfo = 899 + ZcashOrchardInput = 900 + ZcashOrchardOutput = 901 + ZcashAck = 907 diff --git a/core/src/trezor/enums/RequestType.py b/core/src/trezor/enums/RequestType.py index 29bf1af21e9..aad1bc1577c 100644 --- a/core/src/trezor/enums/RequestType.py +++ b/core/src/trezor/enums/RequestType.py @@ -10,3 +10,6 @@ TXORIGINPUT = 5 TXORIGOUTPUT = 6 TXPAYMENTREQ = 7 +TXORCHARDOUTPUT = 8 +TXORCHARDINPUT = 9 +NO_OP = 10 diff --git a/core/src/trezor/enums/ZcashSignatureType.py b/core/src/trezor/enums/ZcashSignatureType.py new file mode 100644 index 00000000000..1d854055545 --- /dev/null +++ b/core/src/trezor/enums/ZcashSignatureType.py @@ -0,0 +1,6 @@ +# Automatically generated by pb2py +# fmt: off +# isort:skip_file + +TRANSPARENT = 0 +ORCHARD_SPEND_AUTH = 3 diff --git a/core/src/trezor/enums/__init__.py b/core/src/trezor/enums/__init__.py index def49dd9910..e8b51b63ab8 100644 --- a/core/src/trezor/enums/__init__.py +++ b/core/src/trezor/enums/__init__.py @@ -249,6 +249,16 @@ class MessageType(IntEnum): WebAuthnCredentials = 801 WebAuthnAddResidentCredential = 802 WebAuthnRemoveResidentCredential = 803 + ZcashGetFullViewingKey = 893 + ZcashFullViewingKey = 894 + ZcashGetIncomingViewingKey = 895 + ZcashIncomingViewingKey = 896 + ZcashGetAddress = 897 + ZcashAddress = 898 + ZcashOrchardBundleInfo = 899 + ZcashOrchardInput = 900 + ZcashOrchardOutput = 901 + ZcashAck = 907 class FailureType(IntEnum): UnexpectedMessage = 1 @@ -296,6 +306,10 @@ class PinMatrixRequestType(IntEnum): WipeCodeFirst = 4 WipeCodeSecond = 5 + class ZcashSignatureType(IntEnum): + TRANSPARENT = 0 + ORCHARD_SPEND_AUTH = 3 + class InputScriptType(IntEnum): SPENDADDRESS = 0 SPENDMULTISIG = 1 @@ -332,6 +346,9 @@ class RequestType(IntEnum): TXORIGINPUT = 5 TXORIGOUTPUT = 6 TXPAYMENTREQ = 7 + TXORCHARDOUTPUT = 8 + TXORCHARDINPUT = 9 + NO_OP = 10 class CardanoDerivationType(IntEnum): LEDGER = 0 diff --git a/core/src/trezor/messages.py b/core/src/trezor/messages.py index 484c17c0927..9829ba4e0e0 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 ZcashSignatureType # noqa: F401 class BinanceGetAddress(protobuf.MessageType): address_n: "list[int]" @@ -386,6 +387,146 @@ def __init__( def is_type_of(cls, msg: Any) -> TypeGuard["HDNodeType"]: return isinstance(msg, cls) + class ZcashGetFullViewingKey(protobuf.MessageType): + coin_name: "str" + z_address_n: "list[int]" + + def __init__( + self, + *, + z_address_n: "list[int] | None" = None, + coin_name: "str | None" = None, + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["ZcashGetFullViewingKey"]: + return isinstance(msg, cls) + + class ZcashFullViewingKey(protobuf.MessageType): + fvk: "bytes" + + def __init__( + self, + *, + fvk: "bytes", + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["ZcashFullViewingKey"]: + return isinstance(msg, cls) + + class ZcashGetIncomingViewingKey(protobuf.MessageType): + coin_name: "str" + z_address_n: "list[int]" + + def __init__( + self, + *, + z_address_n: "list[int] | None" = None, + coin_name: "str | None" = None, + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["ZcashGetIncomingViewingKey"]: + return isinstance(msg, cls) + + class ZcashIncomingViewingKey(protobuf.MessageType): + ivk: "bytes" + + def __init__( + self, + *, + ivk: "bytes", + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["ZcashIncomingViewingKey"]: + return isinstance(msg, cls) + + class ZcashGetAddress(protobuf.MessageType): + coin_name: "str" + t_address_n: "list[int]" + z_address_n: "list[int]" + diversifier_index: "int" + show_display: "bool" + + def __init__( + self, + *, + t_address_n: "list[int] | None" = None, + z_address_n: "list[int] | None" = None, + coin_name: "str | None" = None, + diversifier_index: "int | None" = None, + show_display: "bool | None" = None, + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["ZcashGetAddress"]: + return isinstance(msg, cls) + + class ZcashAddress(protobuf.MessageType): + address: "str | None" + + def __init__( + self, + *, + address: "str | None" = None, + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["ZcashAddress"]: + return isinstance(msg, cls) + + class ZcashOrchardInput(protobuf.MessageType): + recipient: "bytes" + value: "int" + rho: "bytes" + rseed: "bytes" + + def __init__( + self, + *, + recipient: "bytes", + value: "int", + rho: "bytes", + rseed: "bytes", + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["ZcashOrchardInput"]: + return isinstance(msg, cls) + + class ZcashOrchardOutput(protobuf.MessageType): + address: "str | None" + amount: "int" + memo: "str | None" + + def __init__( + self, + *, + amount: "int", + address: "str | None" = None, + memo: "str | None" = None, + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["ZcashOrchardOutput"]: + return isinstance(msg, cls) + + class ZcashAck(protobuf.MessageType): + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["ZcashAck"]: + return isinstance(msg, cls) + class MultisigRedeemScriptType(protobuf.MessageType): pubkeys: "list[HDNodePathType]" signatures: "list[bytes]" @@ -594,6 +735,7 @@ class SignTx(protobuf.MessageType): branch_id: "int | None" amount_unit: "AmountUnit" decred_staking_ticket: "bool" + orchard: "ZcashOrchardBundleInfo | None" def __init__( self, @@ -609,6 +751,7 @@ def __init__( branch_id: "int | None" = None, amount_unit: "AmountUnit | None" = None, decred_staking_ticket: "bool | None" = None, + orchard: "ZcashOrchardBundleInfo | None" = None, ) -> None: pass @@ -970,6 +1113,26 @@ def __init__( def is_type_of(cls, msg: Any) -> TypeGuard["HDNodePathType"]: return isinstance(msg, cls) + class ZcashOrchardBundleInfo(protobuf.MessageType): + inputs_count: "int" + outputs_count: "int" + anchor: "bytes" + account: "int" + + def __init__( + self, + *, + inputs_count: "int", + outputs_count: "int", + anchor: "bytes", + account: "int | None" = None, + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["ZcashOrchardBundleInfo"]: + return isinstance(msg, cls) + class TxRequestDetailsType(protobuf.MessageType): request_index: "int | None" tx_hash: "bytes | None" @@ -994,6 +1157,8 @@ class TxRequestSerializedType(protobuf.MessageType): signature_index: "int | None" signature: "bytes | None" serialized_tx: "bytes | None" + signature_type: "int | None" + zcash_shielding_seed: "bytes | None" def __init__( self, @@ -1001,6 +1166,8 @@ def __init__( signature_index: "int | None" = None, signature: "bytes | None" = None, serialized_tx: "bytes | None" = None, + signature_type: "int | None" = None, + zcash_shielding_seed: "bytes | None" = None, ) -> None: pass diff --git a/core/tests/common.py b/core/tests/common.py index 44512d3eb5b..70389fbb8e9 100644 --- a/core/tests/common.py +++ b/core/tests/common.py @@ -26,3 +26,20 @@ def await_result(task: Awaitable) -> Any: value = await_result(result) else: value = None + + +def zcash_parse(data): + """Parse Zcash test vectors format.""" + attributes = data[1][0].split(", ") + class TestVector: + def __init__(self, inner): + self.inner = inner + + def __getattr__(self, name): + index = attributes.index(name) + value = self.inner[index] + if isinstance(value, str): + value = unhexlify(value) + return value + + return map(TestVector, data[2:]) diff --git a/core/tests/test_apps.zcash.f4jumble.py b/core/tests/test_apps.zcash.f4jumble.py index 2c502731a7e..3c7ff63fbf1 100644 --- a/core/tests/test_apps.zcash.f4jumble.py +++ b/core/tests/test_apps.zcash.f4jumble.py @@ -4,27 +4,24 @@ @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')} + def test_zcash_f4jumble(self): + ZCASH_TEST_VECTORS = [ + ["From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/f4jumble.py"], + ["normal, jumbled"], + ["5d7a8f739a2d9e945b0ce152a8049e294c4d6e66b164939daffa2ef6ee6921481cdd86b3cc4318d9614fc820905d042b", "0304d029141b995da5387c125970673504d6c764d91ea6c082123770c7139ccd88ee27368cd0c0921a0444c8e5858d22"], + ["b1ef9ca3f24988c7b3534201cfb1cd8dbf69b8250c18ef41294ca97993db546c1fe01f7e9c8e36d6a5e29d4e30a73594bf5098421c69378af1e40f64e125946f", "5271fa3321f3adbcfb075196883d542b438ec6339176537daf859841fe6a56222bff76d1662b5509a9e1079e446eeedd2e683c31aae3ee1851d7954328526be1"], + ["62c2fa7b2fecbcb64b6968912a6381ce3dc166d56a1d62f5a8d7551db5fd9313e8c7203d996af7d477083756d59af80d06a745f44ab023752cb5b406ed8985e18130ab33362697b0e4e4c763ccb8f676495c222f7fba1e31defa3d5a57efc2e1e9b01a035587d5fb1a38e01d94903d3c3e0ad3360c1d3710acd20b183e31d49f", "498cf1b1ba6f4577effe64151d67469adc30acc325e326207e7d78487085b4162669f82f02f9774c0cc26ae6e1a76f1e266c6a9a8a2f4ffe8d2d676b1ed71cc47195a3f19208998f7d8cdfc0b74d2a96364d733a62b4273c77d9828aa1fa061588a7c4c88dd3d3dde02239557acfaad35c55854f4541e1a1b3bc8c17076e7316"], + ["25c9a138f49b1a537edcf04be34a9851a7af9db6990ed83dd64af3597c04323ea51b0052ad8084a8b9da948d320dadd64f5431e61ddf658d24ae67c22c8d1309131fc00fe7f235734276d38d47f1e191e00c7a1d48af046827591e9733a97fa6b679f3dc601d008285edcbdae69ce8fc1be4aac00ff2711ebd931de518856878f7", "7508a3a146714f229db91b543e240633ed57853f6451c9db6d64c6e86af1b88b28704f608582c53c51ce7d5b8548827a971d2b98d41b7f6258655902440cd66ee11e84dbfac7d2a43696fd0468810a3d9637c3fa58e7d2d341ef250fa09b9fb71a78a41d389370138a55ea58fcde779d714a04e0d30e61dc2d8be0da61cd684509"], + ["3476f21a482ec9378365c8f7393c94e2885315eb4671098b79535e790fe53e29fef2b3766697ac32b4f473f468a008e72389fc03880d780cb07fcfaabe3f1a84b27db59a4a153d882d2b2103596555ed9494c6ac893c49723833ec8926c1039586a7afcf4a0d9c731e985d99589c8bb838e8aaf745533ed9e8ae3a1cd074a51a20da8aba18d1dbebbc862ded42435e92476930d069896cff30eb414f727b89e001afa2fb8dc3436d75a4a6f26572504b192232ecb9f0c02411e52596bc5e9045", "5139912fe8b95492c12731995a0f4478dbeb81ec36653a21bc80d673f3c6a0feef70b6c566f9d34bb726c098648382d105afb19b2b8486b73cbd47a17a0d2d1fd593b14bb9826c5d114b850c6f0cf3083a6f61e38e42713a37ef7997ebd2b376c8a410d797b3932e5a6e39e726b2894ce79604b4ae3c00acaea3be2c1dfe697fa644755102cf9ad78794d0594585494fe38ab56fa6ef3271a68a33481015adf3944c115311421a7dc3ce73ef2abf47e18a6aca7f9dd25a85ce8dbd6f1ad89c8d"], + ["7e745939ffedbd12863ce71a02af117d417adb3d15cc54dcb1fce467500c6b8fb86b12b56da9c382857deecc40a98d5f2935395ee4762dd21afdbb5d47fa9a6dd984d567db2857b927b7fae2db587105415d4642789d38f50b8dbcc129cab3d17d19f3355bcf73cecb8cb8a5da01307152f13936a270572670dc82d39026c6cb4cd4b0f7f5aa2a4f5a5341ec5dd715406f2fdd2afa733f5f641c8c21862a1bafce2609d9eecfa158cfb5cd79f88008e315dc7d8388e76c1782fd2795d18a763624", "1a52585e652da6ea46994954905cb79f55fca58171a4d7f773a57d23ed9ddec0c745ef0f4588fa7b2b68d69cdd25e5eb0e08c20523a3957171f1730ab0636faee75da2dc9e89562f0653d4e9422179286ae8305f01371f47ab16eed692c3895ce2fd655e4b19651c35d83c81894f687055b581114440646508e39a49b0d5a99004560af7367cc2738344d4e797a995ed66df72228e3d3746674337104700144c73b6db27d238c9e1770662feb0957d5028b5086f3839aacf275022dd7e7e983b6d"], ] - for tv in TEST_VECTORS: - message = memoryview(bytearray(tv["normal"])) + for tv in zcash_parse(ZCASH_TEST_VECTORS): + message = memoryview(bytearray(tv.normal)) f4jumble(message) - self.assertEqual(bytes(message), tv["jumbled"]) + self.assertEqual(bytes(message), tv.jumbled) f4unjumble(message) - self.assertEqual(bytes(message), tv["normal"]) + self.assertEqual(bytes(message), tv.normal) if __name__ == '__main__': unittest.main() diff --git a/core/tests/test_apps.zcash.orchard.crypto.ff1.py b/core/tests/test_apps.zcash.orchard.crypto.ff1.py new file mode 100644 index 00000000000..2ab059a8457 --- /dev/null +++ b/core/tests/test_apps.zcash.orchard.crypto.ff1.py @@ -0,0 +1,56 @@ +""" +source: https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/zcash_test_vectors/ff1.py +""" + +from common import * + +if not utils.BITCOIN_ONLY: + from apps.zcash.orchard.crypto.ff1 import ff1_aes256_encrypt, aes_cbcmac + +@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin") +class TestZcashFF1(unittest.TestCase): + def test_ff1_aes(self): + KEY = unhexlify("0000000000000000000000000000000000000000000000000000000000000000") + PLAINTEXT = unhexlify("80000000000000000000000000000000") + CIPHERTEXT = unhexlify("ddc6bf790c15760d8d9aeb6f9a75fd4e") + self.assertEqual(aes_cbcmac(KEY, PLAINTEXT), CIPHERTEXT) + + key = unhexlify("f9e8389f5b80712e3886cc1fa2d28a3b8c9cd88a2d4a54c6aa86ce0fef944be0") + acc = unhexlify("b379777f9050e2a818f2940cbbd9aba4") + ct = unhexlify("6893ebaf0a1fccc704326529fdfb60db") + for i in range(1000): + acc = aes_cbcmac(key, acc) + self.assertEqual(acc, ct) + + def test_ff1_aes256_encrypt(self): + key = unhexlify("2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F7F036D6F04FC6A94") + + test_vectors = [ + { + "tweak": b'', + "pt": [0]*88, + "ct": list(map(int, "0000100100110101011101111111110011000001101100111110011101110101011010100100010011001111")), + }, + { + "tweak": b'', + "pt": list(map(int, "0000100100110101011101111111110011000001101100111110011101110101011010100100010011001111")), + "ct": list(map(int, "1101101011010001100011110000010011001111110110011101010110100001111001000101011111011000")), + }, + { + "tweak": b'', + "pt": [0, 1]*44, + "ct": list(map(int, "0000111101000001111011010111011111110001100101000000001101101110100010010111001100100110")), + }, + { + "tweak": bytes(range(255)), + "pt": [0, 1]*44, + "ct": list(map(int, "0111110110001000000111010110000100010101101000000011100111100100100010101101111010100011")), + }, + ] + + for tv in test_vectors: + ct = ff1_aes256_encrypt(key, tv["tweak"], iter(tv["pt"])) + self.assertEqual(list(ct), tv["ct"]) + +if __name__ == "__main__": + unittest.main() diff --git a/core/tests/test_apps.zcash.orchard.crypto.generators.py b/core/tests/test_apps.zcash.orchard.crypto.generators.py new file mode 100644 index 00000000000..42b21135598 --- /dev/null +++ b/core/tests/test_apps.zcash.orchard.crypto.generators.py @@ -0,0 +1,39 @@ +from common import * +from trezor.crypto.pallas import group_hash, Point +from apps.zcash.orchard.crypto import generators as gen + + +@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin") +class TestOrchardGenerators(unittest.TestCase): + def test_zcash_recompute_orchard_generators(self): + self.assertEqual(gen.SPENDING_KEY_BASE, group_hash('z.cash:Orchard', b'G')) + self.assertEqual(gen.NULLIFIER_K_BASE, group_hash('z.cash:Orchard', b'K')) + self.assertEqual(gen.VALUE_COMMITMENT_VALUE_BASE, group_hash('z.cash:Orchard-cv', b'v')) + self.assertEqual(gen.VALUE_COMMITMENT_RANDOMNESS_BASE, group_hash('z.cash:Orchard-cv', b'r')) + self.assertEqual(gen.NOTE_COMMITMENT_BASE, group_hash('z.cash:Orchard-NoteCommit-r', b'')) + self.assertEqual(gen.NOTE_COMMITMENT_Q, group_hash('z.cash:SinsemillaQ', b'z.cash:Orchard-NoteCommit-M')) + self.assertEqual(gen.IVK_COMMITMENT_BASE, group_hash('z.cash:Orchard-CommitIvk-r', b'')) + self.assertEqual(gen.IVK_COMMITMENT_Q, group_hash('z.cash:SinsemillaQ', b'z.cash:Orchard-CommitIvk-M')) + + def test_zcash_orchard_generators(self): + ZCASH_TEST_VECTORS = zcash_parse([ + ["From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/orchard_generators.py"], + ["skb, nkb, vcvb, vcrb, cmb, cmq, ivkb, ivkq, mcq"], + ["63c975b884721a8d0ca1707be30c7f0c5f445f3e7c188d3b06d6f128b32355b7", "75ca47e4a76a6fd39bdbb5cc92b17e5ecfc9f4fa7155372e8d19a89c16aae725", "6743f93a6ebda72a8c7c5a2b7fa304fe32b29b4f706aa8f7420f3d8e7a59702f", "915a3c8868c6c30e2f8090ee45d76e4048208dea5b23664fbb09a40f5544f407", "136efc0f482c022c7ca414fc5cc59e23f23d6f93ab9f23cd3345a928c306b2a6", "5d74a84009ba0e322add46fd5a0f96c55dedb079b4f29ff70dcdfb56a0078097", "18a1f85f6e482398c7ed1ad3e27f9502488980400a2934164e137050cd2ca2a5", "f2820f79922fcb6b32a2285124cc1b42fa41a25ab881cc7d11c8a94af10cbc05", "a0c6297ff9c7b9f870108dc055b9bec9990e89ef5a360fa0b918a86396d21616"] + ]) + tv = list(ZCASH_TEST_VECTORS)[0] + self.assertEqual(gen.SPENDING_KEY_BASE, Point(tv.skb)) + self.assertEqual(gen.NULLIFIER_K_BASE, Point(tv.nkb)) + self.assertEqual(gen.VALUE_COMMITMENT_VALUE_BASE, Point(tv.vcvb)) + self.assertEqual(gen.VALUE_COMMITMENT_RANDOMNESS_BASE, Point(tv.vcrb)) + self.assertEqual(gen.NOTE_COMMITMENT_BASE, Point(tv.cmb)) + self.assertEqual(gen.NOTE_COMMITMENT_Q, Point(tv.cmq)) + self.assertEqual(gen.IVK_COMMITMENT_BASE, Point(tv.ivkb)) + self.assertEqual(gen.IVK_COMMITMENT_Q, Point(tv.ivkq)) + # tv.mcq not tested + # tv.mcq = merkle tree commitment Q personalization + # which is not needed in Trezor + + +if __name__ == "__main__": + unittest.main() diff --git a/core/tests/test_apps.zcash.orchard.crypto.keys.py b/core/tests/test_apps.zcash.orchard.crypto.keys.py new file mode 100644 index 00000000000..83575ee65f4 --- /dev/null +++ b/core/tests/test_apps.zcash.orchard.crypto.keys.py @@ -0,0 +1,70 @@ +from common import * +from apps.zcash.orchard.crypto.keys import FullViewingKey, sk_to_ask +from apps.zcash.orchard.crypto.address import Address +from apps.zcash.orchard.crypto.note import Note +from trezor.crypto.pallas import Fp + +ZCASH_TEST_VECTORS = [ + ["From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/orchard_key_components.py"], + ["sk, ask, ak, nk, rivk, ivk, ovk, dk, default_d, default_pk_d, internal_rivk, internal_ivk, internal_ovk, internal_dk, note_v, note_rho, note_rseed, note_cmx, note_nf"], + ["5d7a8f739a2d9e945b0ce152a8049e294c4d6e66b164939daffa2ef6ee692148", "8eb8c401c287a6c13a2c345ad82172d86be4a8853525db602d14f630f4e61c17", "740bbe5d0580b2cad430180d02cc128b9a140d5e07c151721dc16d25d4e20f15", "9f2f826738945ad01f47f70db0c367c246c20c61ff5583948c39dea968fefd1b", "021ccf89604f5f7cc6e034b32d338908b819fbe325fee6458b56b4ca71a7e43d", "85c8b5cd1ac3ec3ad7092132f97f0178b075c81a139fd460bbe0dfcd75514724", "bcc7065e59910b35993f59505be209b14bf02488750bbc8b1acdcf108c362004", "31d6a685be570f9faf3ca8b052e887840b2c9f8d67224ca82aefb9e2ee5bedaf", "8ff3386971cb64b8e77899", "08dd8ebd7de92a68e586a34db8fea999efd2016fae76750afae7ee941646bcb9", "901a30b99ae1570cb80bb616aeef3bb916c640c4cc620f9b4b4499c74332eb2a", "906e2d20d00dc0bf7c520687d9df3ce9814d30ee05c215f8764a32c362f9262f", "d7268bebbee692286252ac60bd4df405ea499d697c454773c5c43cb170930123", "6d61a03f746ba93b932402ac1071fc2759d4f4d684b2c5056d5b177af0fa8aa9", 15643327852135767324, "2cb5b406ed8985e18130ab33362697b0e4e4c763ccb8f676495c222f7fba1e31", "defa3d5a57efc2e1e9b01a035587d5fb1a38e01d94903d3c3e0ad3360c1d3710", "4502e339901e397717839167cbb4037e0ecf6813b51c81fe085a7b782f124228", "1b32edbbe4d18f28876de262518ad31122701f8c0a52e98047a337876e7eea19"], + ["acd20b183e31d49f25c9a138f49b1a537edcf04be34a9851a7af9db6990ed83d", "41d47cc96313b4821dfc129651c3137f44d9cad16b3dc08133c3d2df0d0c5320", "6de1349830d66d7b97fe231fc7b02ad64323629cfed1e3aa24ef052f56e4002a", "a8b73d979b6eaada8924bcbdc63a9ef4e87346f230aba6bbe1e2b43c5bea6b22", "dacb2f2a9ced363171821aaf5d8cd902bc5e3a5a41fb51ae61a9f02dc89d1d12", "563a6db60c74c2db08492cbae3bb083f1aeabffbcf42551d0ac64f2690536711", "71cd30640fdb63f8d1305029e940e53fd5ec04a8ccad419578c242fec05b9af7", "9d9bd44525e7ae06b03ae6d4aecde6ae0927a7c667d5d9f8176b544695dfec11", "7807ca650858814d5022a8", "3d3de4d52c77fd0b630a40dc38212487b2ff6eeef56d8c6a6163e854aff04189", "8a22a7f5a1e91a92ad394b18eb7338b592470dd42be8ef84c93e7cd845ecfa32", "121183cb3b8d06f599bb38b37322851e5fc95ad0c9707ee85fb65e21f1a30d13", "93252b24b491d9c9c99765c84d4ac7c2bff054cd9cadcd3e01b26f21e2840909", "6eea18fd0d50707f90df002cbf309eca3c00d398aede1fdc2abffc88353859af", 4481649511318637270, "a51b0052ad8084a8b9da948d320dadd64f5431e61ddf658d24ae67c22c8d1309", "131fc00fe7f235734276d38d47f1e191e00c7a1d48af046827591e9733a97fa6", "c7ad794c563e32cad47d47dcda7884692848dce29ba4febd93202b7305f90300", "2cf067bc21d66320e51b9fbdc8ae031c2c96373db43b7b1a45056c00c65d4320"], + ["b679f3dc601d008285edcbdae69ce8fc1be4aac00ff2711ebd931de518856878", "ce8b65a7236511b2eaf19f72a3d6db7d062b66f516307d198706e5f6928e1615", "efa5f1debeead0940a619ce0017bedb426657b2d07406664d895312ea1c3b334", "04514ea048b94363dea7cb3be8d62582ac52922e0865f662743b05eae8715f17", "2a328f994f6e5ad29ca811ed344968ea2cfc3fd231030e37bbd56db42640231c", "609ecbc3d8cee3be2b2a2362951f58b74482adfaeee1c40f94030440f558aa30", "dfd30f62aa319c6f53e24c1f48c1de961b9001cb988b80b3eda244fcfeb25f83", "236bc3f3d02f960280eedede108d3685049f239aa67c48558f7c01d3fd469ecd", "6424f71a3ad197426498f4", "eccb6a5780204237987232bc098f89acc475c3f74bd69e2f35d44736f48f3c14", "0aa9aaaa2cf18490ddf9a7e521071407ea9bfffe843429bc94a288e8a606a710", "a06abd29d5a199e1c21025b0337e941f6d4d84eb7cc35a397f9e753fdaed810d", "f82eb24906e294ff6571ac7d8368ea8280d422f3477ce72aef5f9b9eca48468f", "3656b545a50a6b26287476641b2b68c63c36f332e74557e916050f0b9111179b", 14496603531126387959, "32b4f473f468a008e72389fc03880d780cb07fcfaabe3f1a84b27db59a4a153d", "882d2b2103596555ed9494c6ac893c49723833ec8926c1039586a7afcf4a0d9c", "03ce20cea194b7559a8a90471d28a3c053c3720ad49f40d27c2dcce335005616", "16fa2c3497fc09ad90dd349202a24b69892dc80629b2d1bfebaf41708f0fb10c"], + ["731e985d99589c8bb838e8aaf745533ed9e8ae3a1cd074a51a20da8aba18d1db", "426a7844f305b9d4e07ea52a39001c9b336cfc0d6fa15ef3d11c3d7b74f08c2d", "b1e0acbc69bf377b85abf0f5a10be72c3b640006ff08505280e4f00fadf76328", "cf36ad6a066cd213e1d767ab071dc1167885c4168bc2e2175448563ad13f333d", "c41bbad35105a80314b79624b675241220b331f12592617bdb705bfcce72ae38", "f79fe802e4d24307a6aaf85d19f5e0833740bae598dc7c880ac609631de15819", "f96366bc6eabd232549ebb43b4ed6fd81d330373c5b566904e9af11a6bab8d77", "803e348573022bf8932f23ee7a325ea283879c652412b8606be3198c4b782c47", "db8c305524bc0deaa85d97", "04ea8c1320ffbbadfe96f0c6ff16b607111b5583bfb6f1ea45275ef2aa2d879b", "9e452ab72c6c8eccf2e439a0cec0a0ac394a1aa121ac6032a7ebc29db4856226", "3ba93b0fc3f27ab217635d03f90d0b842d99a12cdc37a81c181ec018e5f44c11", "e3c7f86c1b2383b3bd41ad1a8f11efa2554a410a98c89207aeb4319b1abd7879", "d71a68cfd6c768f43073f698189ac75ee421b4204bb6f3c5d0fc432849aa7161", 6792346249443327211, "4b192232ecb9f0c02411e52596bc5e90457e745939ffedbd12863ce71a02af11", "7d417adb3d15cc54dcb1fce467500c6b8fb86b12b56da9c382857deecc40a98d", "a9b11baf3034b65c6424841bfe023f8eda1313c30aa27de92e21a108316e8219", "72d6308960351f7b26fa64603fe4dfd867bd5eb367ba2b7ca491c923c0ead222"], + ["5f2935395ee4762dd21afdbb5d47fa9a6dd984d567db2857b927b7fae2db5871", "118073285164e6557358fbc41a8135cb062f8676cb61f9aa52d19a09fac55802", "0d262de3609433fe5b7c862bc48ef56d832009f7242e1f7c770a12241dfa2807", "51baf333cff1f2d0c7e3cff4d301299dc1efe98300314a541938029b45cc1521", "228feb79219873c7a7606e52973c85f460465a6059083919ed73eb805c118301", "76f49cf8a3192185616a9a0da0c76ec2c2756159bce186a1862b6e6e59442d11", "eb72b6c31e837fd837aacb61fabace75a19dd9dd5b4b3a3ee723c14da77b4be8", "ee19f8ddd9da0634245143c4b43afc7d78c549c82054a9d84007b56217dbfdd6", "aae36e094de07bc16f898e", "b6533dcbfff0f6c1ceefa84799bda3de7334326ccd65f7ce92ff3d9e6e1f140b", "254406723b0667af27e51cb3ce8fa1388164d94376c850bddb39e9bea5fa9605", "bad4837ba78822b8b165b0a16e1104c705c3c0e382d3f13c195c0ef311bb8004", "b9113a952dcc1e15c34d136603a2ef254a38755a557fa9f88c143bd3076441b0", "02b52c6ed9ad49fb38e4447c69b570ebd055e4c7fd91c020ff43461d14e02f29", 4079549063511228677, "2670dc82d39026c6cb4cd4b0f7f5aa2a4f5a5341ec5dd715406f2fdd2afa733f", "5f641c8c21862a1bafce2609d9eecfa158cfb5cd79f88008e315dc7d8388e76c", "0ffbca1d5921fa0a8c5116ae137e37f2c118d52125628d8a3f412ce0e6530e04", "e62b8ed83540146cd23cac74eed7d773d80224a5aa30d68e35572ee883d1b704"], + ["1782fd2795d18a763624c25fa959cc97489ce75745824b77868c53239cfbdf73", "f6ef328d24761d6d3ccd25d47196e8109c038fe17c59a7f05b98d66bebc64124", "d11787ca582f948e450718b36998df28bb0f1021ea843f867f8a170f5c33901f", "9e997d9d269787268e092a7c85417da530ea42fac668a749af55dfb71cdbbe09", "136c6fe2e2b79c5156db5047d8d5e795dfc0bdc0880853a44adb7392c02f941b", "028b640564b24905de9292ba5b9810addd86bed0fb3b2d6b37f26dd238a7db13", "98d6a4bf6801d8ba0d0b67ea7b805207abc0348fc562005a59a27a8a46fa6add", "d0baef6012d308efbb769a99cca2928cede8db277645a777eaf1722cd08450b3", "cc7ce734b075a01b92aaca", "3da5273a5667c766b8231206180f158ac02af3f06ecca6ec7c38c75d33600320", "88d7b19699f394a550bc9cdc6bf3fc71f610c30656376153a6961fcd5b97fa19", "0a2dc96661b927250d7e3cd2c7e06d5174c62cb12e07167f194f4ce64e689502", "cc7965f33ac01c606851b129bdc9b6abd5ca5b9d241dbd5c18b2469b7c8cc89f", "daa242d20dfdce8fc10f4d99397da22c491dc09e1b120f6693d686ecd4030a00", 5706402952489856202, "a1df0e5b87b5bece477a709649e950060591394812951e1fe3895b8cc3d14d2c", "f6556df6ed4b4ddd3d9a69f53357d7767f4f5ccbdbc596631277f8fecd08cb05", "63cee37e3c7b4e6cc939a2e63ada74f85ea48ba07a4f92ccbd34faa42dfd4916", "4c99bfa8c20dba59bb7347da16c43b73c88794c9ebcd0dd2b25ee7bb836f9520"], + ["6b95e3025b9792fff7f244fc716269b926d62e9596fa825c6bf21aff9e68625a", "757d158d07356b3bc2c9e51c558a9b316bddbc360b8beb6e2ae3b0618f062d2e", "449a90d2e8d1a037642a97096c916543462a137ffea37baf41ef286bb732be2c", "fd3164c632bec94ce9fb2f302263b884abb9c10e55e448647f6798495c9d083f", "c0b36b56070fff2fdf38eba11a7424957195014cba43a56bd1b1658e66a39d00", "976a8788191b87e4c13f2c6d23b4f3595e0228e245e96eef1d24b293296a191c", "1ed0eda5a4086131261a2ed4429261e4276a26d42859fabda31aa96709874371", "5e5b60c05b53d0bcd2da46a1312912515cc7cf2d974c117c8ddea9fab620c668", "99af6bf3f475bde889aaca", "acdcd348ca45ee583278303846ca078459d5be5c5dcf347e3b9a34cba124b4a3", "941a17e1202a6271a44a01666553b581bf25ef99e8e95f132ace381d96018432", "a27629ac1c62c9f4dad57c9530ab2a59800d2ef455cd17446f3fc6081a581e3b", "e9898ed6b669c8d9d590b759d0295fcfaf95e2daf7da991c2757dcefe1626e0e", "610cbd9a577979e1f71da8100f6fe6b8f6d10a747fed2a1c91cbe142475c3082", 2558469029534639129, "722db041a3ef66fa483afd3c2e19e59444a64add6df1d963f5dd5b5010d3d025", "f0287c4cf19c75f33d51ddddba5d657b43ee8da645443814cc7329f3e9b4e54c", "1e619e46bb62b61d4e1cf3622ea70a908de7f076ecf87f541e0b7b48ad4a2601", "3b948db21608e9acb22a5417b98c0dedd527a96487814e6420cbff6e4eee4e31"], + ["236c29af3923101756d9fa4bd0f7d2ddaacb6b0f86a2658e0a07a05ac5b95005", "b4ded90d62117f18f3dd5fdb22238a35ca37c40feec845ce5fc27fe8bca5ef0f", "4efd5a2ef1ffa99a0ff62b767d44b3651ffa1c696915ac00a25ea3ac7dff9901", "02ab995ce98f63025fb62428a0fbf52f2522e6a27261078a9f4d6a36a1c05d39", "d9840d0bd89520abbca7f10be6eba366f86ec3b78dbdf1ebfe20d99512af1515", "58f5bb5c3231152529423b67fa432879112635cda0da2ec2419c6fe91ea48d24", "78f5d348672e8d209c41b783f8ca14a77b3ea3e6004ca4e0c25aa44563981dcb", "5d7fe396bbfd2267aca711ab5b3e1f024f4911f3a181732f1322a1592f9e0ebe", "2fbe4b4b1edff33123ce65", "eb2c6fee341eade07d7487997aa723697d05e62960df379c9e4a8d476dfac5bf", "663b67d3ac159927f06e6c8dab80a58967c545daac3d98729a0bcc41fd536d2b", "aa6acc8a7aa9a8052004ff93833f4abb153b45797fd907e305c8927bb0378220", "bfd1096727b6d5a2e17acbc5b24680cb88db34cf53b6b7466cef676fb3f72229", "47bdf9271ecc50e705c521cd0dbbaf1c4e6a962fc9141348b8bd7b35c4001e62", 15425828902564319772, "736c23357c85f45791e1708029d9824d90704607f387a03e49bf983657443134", "5a7877efaa8a08e73081ef8d62cb780ab6883a50a0d470190dfba10a857f8284", "c8528f722cd3e47dc99e1e388056370815a9d037973d85cac7ea38b5a716fa3b", "acc2ed2c7e3b197e5cdb4a576357d5f135391626c7a825d10aa260ae0b958128"], + ["2d3825b3d6da0573d316eb160dc0b716c48fbd467f75b780149ae8808f4e68f5", "2d6e973e1754d41787934c34558cfe993844199972d9a6348b7a3dadfcb6772a", "762159a414f574b539750f22c8863b02d25cc10c9071fc0219e97f9392d0670c", "2591edf7ef4cf2184c34be93fcf612915042f15ab5084b14e166795b09cea133", "758fb250dd2950e5d2b2eed7ffcf94ae67cde125b95b479e2377813a85a03d2f", "6ea4363cb2df62b10da1308a0b9679bd0f7495ffe7d4e2618f54df9b670c3316", "a63cbcd31ba136d83b8f1e88efb60055ef6f98252ddbd75f625f44dcb6632c72", "02f07408f33e8712e4c9ec42de5604200109861724d33eb6368b70f65e0a1621", "08df1d4b45c673a459ff58", "268cc24b38a62880b6ee3cbcb85a712fa686cffca6db2feec5f3c3566f84218f", "0057377461f2191a7eca2b02edfd9c9b44845d2fdb8a99c76120527e53dd0917", "8162973509470c44241911c06d04029f5f1f0e9851e32ba69b18e58105dd4e2b", "6947910ea3e7331d15a71a64b2a8c16a6da08e6f3429db26f937ab9dd133b5fd", "327f76cc4244ce0a9148a35a7ea6228d441c4c7b05bd02657ceaabb609bc3c52", 12606128263924155660, "12f6b02fe806b94569cd4059f396bf29b99d0a40e5e1711ca944f72d436a102f", "ca4b97693da0b086fe9d2e7162470d02e0f05d4bec9512bfb3f38327296efaa7", "6a1195aa0536f60ecfaecbdf5374e494ea072a2b867b5f694340c96fc370a910", "b0f1602a2b1af2fc55f15950a6838385e5e39fecfd05ccec799b75c65c8da235"], + ["4328b118c27402c70c3a90b49ad4bbc68e37c0aa7d9b3fe17799d73b841e7517", "28dc45f11544425c1bef8661da11155fdbb7e3bcfc0f0d49e6f131e7c09d352f", "0d211a9060fbaa664e41a734ad1d8d4b025f8cc160e1f4e95f0a853ebc416a2b", "3e88f2071fd9a2bb26cda2ea856aa0fb3a80a87d2fb6136fab85e36c5b38d824", "2c373882c408cd5fd482a0c9816fc32203a10fbfce0e200ccfd9ee307c5e1224", "bb9e20b2991c996da21e3ecd39fb7b3aa2babc6bde186f7dd8a875d10c51a430", "9321838a2db7f168f0ce77c45b211ffbb9b365e85e6731d909700553de492b28", "3df583361b3338bb6815f85872e39f04df5008524884af0f8c559716fcb14958", "4c4064c47a5ca6e75d4644", "f517174be258923278cf458908c0735649f1899db99c3ba9003f4ba30ab0d210", "d809a2a3d36ef96dc563f8a7b413908bfdffc06d51064849ef886b6a1d1d7c3f", "ae18a9a42512387f92eec134bde528b62b61e9956f9fb3c7d65e1945da34f309", "67a6d84a8166326cf34cedffd4298a13b801cb122d5f3329a1599f31eadf5b17", "a0073addfb89c9cc349ead5a92b7d417fe0e61f4a7e56669c907d41746c072b9", 625536973899669523, "03fd69442eb7681ec2a05600054e92eed555028f21b6a155268a2dd6640a6930", "1a52a38d4d9f9f957ae35af7167118141ce4c9be0a6a492fe79f1581a155fa3a", "f70ebf0f5ee5da6c6cdeff8fec2f8eed65c88e6755daf114d554af1967a7f40a", "95649728465e682ac057ad876294d700c27feba2f750922f955185706261c30c"] +] + +@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin") +class TestZcashOrchardKeyComponents(unittest.TestCase): + def test_zcash_orchard_key_components(self): + for tv in zcash_parse(ZCASH_TEST_VECTORS): + ask = sk_to_ask(tv.sk) + self.assertEqual(ask, tv.ask) + + fvk = FullViewingKey.from_spending_key(tv.sk) + self.assertEqual(fvk.ak.to_bytes(), tv.ak) + self.assertEqual(fvk.nk.to_bytes(), tv.nk) + self.assertEqual(fvk.rivk.to_bytes(), tv.rivk) + ifvk = fvk.internal() + self.assertEqual(ifvk.rivk.to_bytes(), tv.internal_rivk) + + ivk = fvk.incoming_viewing_key() + self.assertEqual(ivk[0:32], tv.dk) + self.assertEqual(ivk[32:64], tv.ivk) + iivk = ifvk.incoming_viewing_key() + self.assertEqual(iivk[0:32], tv.internal_dk) + self.assertEqual(iivk[32:64], tv.internal_ivk) + + ovk = fvk.outgoing_viewing_key() + self.assertEqual(ovk, tv.ovk) + iovk = ifvk.outgoing_viewing_key() + self.assertEqual(iovk, tv.internal_ovk) + + address = fvk.address(0).to_bytes() + self.assertEqual(address[0:11], tv.default_d) + self.assertEqual(address[11:43], tv.default_pk_d) + + def test_zcash_orchard_note_commitments(self): + for tv in zcash_parse(ZCASH_TEST_VECTORS): + address = Address.from_bytes(tv.default_d + tv.default_pk_d) + note = Note( + address, + tv.note_v, + Fp(tv.note_rho), + tv.note_rseed, + ) + self.assertEqual( + note.commitment().extract().to_bytes(), + tv.note_cmx, + ) + nk = Fp(tv.nk) + self.assertEqual(note.nullifier(nk).to_bytes(), tv.note_nf) + + +if __name__ == '__main__': + unittest.main() diff --git a/core/tests/test_apps.zcash.orchard.crypto.note_encryption.py b/core/tests/test_apps.zcash.orchard.crypto.note_encryption.py new file mode 100644 index 00000000000..fa4fb70748d --- /dev/null +++ b/core/tests/test_apps.zcash.orchard.crypto.note_encryption.py @@ -0,0 +1,45 @@ +from common import * + +from apps.zcash.orchard.crypto.note_encryption import encrypt_note +from apps.zcash.orchard.crypto.note import Note +from trezor.crypto.pallas import * +from apps.zcash.orchard.random import ActionShieldingRng +from apps.zcash.orchard.crypto.address import diversify_hash, Address + +ZCASH_TEST_VECTORS = [ + ["From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/orchard_note_encryption.py"], + ["incoming_viewing_key, ovk, default_d, default_pk_d, v, rseed, memo, cv_net, rho, cmx, esk, ephemeral_key, shared_secret, k_enc, p_enc, c_enc, ock, op, c_out"], + ["1039d8e64a80902e105947817df3bdfb7df7030e68739f9c533a36bf5a6a807243106de9a7ec54dd36dfa70bdbd9072dbddab5e066aaeffcf9bba320d4fff712", "5d7a8f739a2d9e945b0ce152a8049e294c4d6e66b164939daffa2ef6ee692148", "56e84b1adc9423c3676c04", "63f7125df4836fd2816b024ee70efe09fb9a7b3863c6eacdf95e03894950692c", 8567075990963576717, "bf69b8250c18ef41294ca97993db546c1fe01f7e9c8e36d6a5e29d4e30a73594", "ffbf5098421c69378af1e40f64e125946f62c2fa7b2fecbcb64b6968912a6381ce3dc166d56a1d62f5a8d7551db5fd9313e8c7203d996af7d477083756d59af80d06a745f44ab023752cb5b406ed8985e18130ab33362697b0e4e4c763ccb8f676495c222f7fba1e31defa3d5a57efc2e1e9b01a035587d5fb1a38e01d94903d3c3e0ad3360c1d3710acd20b183e31d49f25c9a138f49b1a537edcf04be34a9851a7af9db6990ed83dd64af3597c04323ea51b0052ad8084a8b9da948d320dadd64f5431e61ddf658d24ae67c22c8d1309131fc00fe7f235734276d38d47f1e191e00c7a1d48af046827591e9733a97fa6b679f3dc601d008285edcbdae69ce8fc1be4aac00ff2711ebd931de518856878f73476f21a482ec9378365c8f7393c94e2885315eb4671098b79535e790fe53e29fef2b3766697ac32b4f473f468a008e72389fc03880d780cb07fcfaabe3f1a84b27db59a4a153d882d2b2103596555ed9494c6ac893c49723833ec8926c1039586a7afcf4a0d9c731e985d99589c8bb838e8aaf745533ed9e8ae3a1cd074a51a20da8aba18d1dbebbc862ded42435e92476930d069896cff30eb414f727b89e001afa2fb8dc3436d75a4a6f26572504b192232ecb9f0c02411e52596bc5e90457e745939ffedbd12863ce71a02af117d417adb3d15cc54dcb1fce467500c6b8fb86b12b56da9c382857deecc40a9", "ddba24f39f708ed7a7485713711142c238513815302df0f4830421a6c13e7101", "ca1feb30ca111776c0417466bd69b3d213882eef55e60b6d9e2a98e705eef327", "23757c515821cbc1843c9a457b7e6ae601add2ea10b9c86d6b317ce2f17bd921", "5bfe469c33e447ba456b8bfe9b385b3931b4baeb8f7023fe8e33354ffff1bd1a", "8a5e132c3a0704f2456fbd777a13d6ec57655671db072a7d276ad969f5ec4517", "36d54cabc67f6cc726a730f3a0ceed5853f08cd38146c8342598987c215048a5", "82c43265337f1ab37b18df277548618263b8024d9b145a05ade2eb5479180320", "0256e84b1adc9423c3676c048d5f2935395ee476bf69b8250c18ef41294ca97993db546c1fe01f7e9c8e36d6a5e29d4e30a73594ffbf5098421c69378af1e40f64e125946f62c2fa7b2fecbcb64b6968912a6381ce3dc166d56a1d62f5a8d7551db5fd9313e8c7203d996af7d477083756d59af80d06a745f44ab023752cb5b406ed8985e18130ab33362697b0e4e4c763ccb8f676495c222f7fba1e31defa3d5a57efc2e1e9b01a035587d5fb1a38e01d94903d3c3e0ad3360c1d3710acd20b183e31d49f25c9a138f49b1a537edcf04be34a9851a7af9db6990ed83dd64af3597c04323ea51b0052ad8084a8b9da948d320dadd64f5431e61ddf658d24ae67c22c8d1309131fc00fe7f235734276d38d47f1e191e00c7a1d48af046827591e9733a97fa6b679f3dc601d008285edcbdae69ce8fc1be4aac00ff2711ebd931de518856878f73476f21a482ec9378365c8f7393c94e2885315eb4671098b79535e790fe53e29fef2b3766697ac32b4f473f468a008e72389fc03880d780cb07fcfaabe3f1a84b27db59a4a153d882d2b2103596555ed9494c6ac893c49723833ec8926c1039586a7afcf4a0d9c731e985d99589c8bb838e8aaf745533ed9e8ae3a1cd074a51a20da8aba18d1dbebbc862ded42435e92476930d069896cff30eb414f727b89e001afa2fb8dc3436d75a4a6f26572504b192232ecb9f0c02411e52596bc5e90457e745939ffedbd12863ce71a02af117d417adb3d15cc54dcb1fce467500c6b8fb86b12b56da9c382857deecc40a9", "93e04874b5837c261daf1a27b783ec4865d3bb728eb161daedb8446ab38f078ea8662e4d2e9d00a39527dcde517ac3dbf9d27e3c79fa881abb48b70dbc28ddf4af81aeed2a298600510848edbdc42e88954870d5d601cdf290181b539105b9f61386cb07846bc8e319dfab8e109766a28c1e0bbf913202cecd1b4817a2282fc29ed44d9b04049de55acf5499e5f565d48b8f1972c043847796230dc68f3257c08529148c8e0c327b25b459877cded98ff78e81fa692e14f8fda1fe524ff150181f736ed3a88ec789dc15954a02639a8a20ca38d899bfd1c573b041ee7bf22b9675bda8c4b058a05a493303b11f3581c19d2da9966a71066ec17dccd348207eb314f6cfc9d06a6214c6721097a52e2776667c6be9c8862b173db0e804b12caae9d9fa09f3f48caf4bf756a278950a254ec4147677aaca214296081a2f624a9278946e689dd914029092e7fa8fbc8a04467d60edff5d97cb6509a0c72ced77aca871308e7de2beb1520a3417d7213a9abd47358c4f329f0f64419210a99db2de6e6d8921b0f4f99fd645fae0d629ce2211905f25f40d120b63279375b543c31e3b557e57a7a87c6179ebd34f6dbb920ec5e05d6a77ecdf36b457bab4566c408fb57dfcdddaa42c5134af3e978dbfd0dfb0ca4ffaf1650abee1625f7f4bf825060100645b54c0041fbfbdeff7b93804e9cc0ccd6f27be40016c32d42fe366faaa8687c2d192619f565b0c70ea6a3f79d53a5241e69c3ca687a112fb16c25cc08317dba423970c32dfb4bd6922e336abf2fde2c3aa5db293ef2747876c8bd86ea187cb601af7", "b325ebe57a2c40a8b211cfdf72a1a244f15342859888a364523efd2ac66a1ad6", "63f7125df4836fd2816b024ee70efe09fb9a7b3863c6eacdf95e03894950692c5bfe469c33e447ba456b8bfe9b385b3931b4baeb8f7023fe8e33354ffff1bd1a", "55b8907c6d454b83634f1b9a1aa3c3c98adc77d96c2f6249ec66dbae4d0cc940d726bcd1ec91189fd3049a33f2ea7d8b74aac17cda3883802db5969d8d2f3225919ce38826415cc6b338944b4899548b"], + ["fd9e9a1f381cbe75cd8d6ae12fca872e9400f00272b029652e656c8f3c4bf037eeef96421b2fab2fb3ad1e0ad8502d74e6f08f0dd518f8fa822a65be2740c021", "e73081ef8d62cb780ab6883a50a0d470190dfba10a857f82842d3825b3d6da05", "556e5e1bf51bc6a61158f7", "b4cac56f062bfb2e2715eaf9c8fcdbc20c86793f2357ddd04aad39f94ad7c784", 9072946746592546880, "aeab016b6bc1ec144b4e553acfd670f77e755fc88e0677e31ba459b44e307768", "ff958fe3789d41c2b1ff434cb30e15914f01bc6bc2307b488d2556d7b7380ea4ffd712f6b02fe806b94569cd4059f396bf29b99d0a40e5e1711ca944f72d436a102fca4b97693da0b086fe9d2e7162470d02e0f05d4bec9512bfb3f38327296efaa74328b118c27402c70c3a90b49ad4bbc68e37c0aa7d9b3fe17799d73b841e751713a02943905aae0803fd69442eb7681ec2a05600054e92eed555028f21b6a155268a2dd6640a69301a52a38d4d9f9f957ae35af7167118141ce4c9be0a6a492fe79f1581a155fa3a2b9dafd82e650b386ad3a08cb6b83131ac300b0846354a7eef9c410e4b62c47c5426907dfc6685c5c99b7141ac626ab4761fd3f41e728e1a28f89db89ffdeca364dd2f0f0739f0534556483199c71f189341ac9b78a269164206a0ea1ce73bfb2a942e7370b247c046f8e75ef8e3f8bd821cf577491864e20e6d08fd2e32b555c92c661f19588b72a89599710a88061253ca285b6304b37da2b5294f5cb354a894322848ccbdc7c2545b7da568afac87ffa005c312241c2d57f4b45d6419f0d2e2c5af33ae243785b325cdab95404fc7aed70525cddb41872cfcc214b13232edc78609753dbff930eb0dc156612b9cb434bc4b693392deb87c530435312edcedc6a961133338d786c4a3e103f60110a16b1337129704bf4754ff6ba9fbe65951e610620f71cda8fc877625f2c5bb04cbe1228b1e886f", "1549707e1ed2b2eb6615650bec45a21764104a23eaf6ba496cb9b8e8257ad8b3", "c1e1595b8de7559766e5a6725f5be5742f43bf40623b7149cae2675c4db2c731", "59b6f3d403223d6ce43dedaee235fca95cc8b249941ccdb66f3f611cc5e9f90f", "10874a74227ac7995edddd734d0e00dcc9f48a01dd5c4cb122c061e0bdc9ce14", "d29e0d001ee71e0599086504d862c7f52b0860770d8a4b42a86811ac3169858c", "11a0ac799a29b0ed195ed87b138322263bbb9c31008c2959af2fc636687ed9b0", "4bbf80e7a1703ac14ad7b5448a2e8e79493049d19a6a513167d55bdd586ac0d9", "02556e5e1bf51bc6a61158f74050afd8fe94e97daeab016b6bc1ec144b4e553acfd670f77e755fc88e0677e31ba459b44e307768ff958fe3789d41c2b1ff434cb30e15914f01bc6bc2307b488d2556d7b7380ea4ffd712f6b02fe806b94569cd4059f396bf29b99d0a40e5e1711ca944f72d436a102fca4b97693da0b086fe9d2e7162470d02e0f05d4bec9512bfb3f38327296efaa74328b118c27402c70c3a90b49ad4bbc68e37c0aa7d9b3fe17799d73b841e751713a02943905aae0803fd69442eb7681ec2a05600054e92eed555028f21b6a155268a2dd6640a69301a52a38d4d9f9f957ae35af7167118141ce4c9be0a6a492fe79f1581a155fa3a2b9dafd82e650b386ad3a08cb6b83131ac300b0846354a7eef9c410e4b62c47c5426907dfc6685c5c99b7141ac626ab4761fd3f41e728e1a28f89db89ffdeca364dd2f0f0739f0534556483199c71f189341ac9b78a269164206a0ea1ce73bfb2a942e7370b247c046f8e75ef8e3f8bd821cf577491864e20e6d08fd2e32b555c92c661f19588b72a89599710a88061253ca285b6304b37da2b5294f5cb354a894322848ccbdc7c2545b7da568afac87ffa005c312241c2d57f4b45d6419f0d2e2c5af33ae243785b325cdab95404fc7aed70525cddb41872cfcc214b13232edc78609753dbff930eb0dc156612b9cb434bc4b693392deb87c530435312edcedc6a961133338d786c4a3e103f60110a16b1337129704bf4754ff6ba9fbe65951e610620f71cda8fc877625f2c5bb04cbe1228b1e886f", "1b423480bf3767f5ebfc40b8c89cc534f165c35d19c8da6c3210e952cad823a7846021c3de4a8693b71e287f4686ac0addced94eba810a998b823a4ad241aa9f4a3ae4825de995dd5873566244bbd875d01bf328e822cafdb83ed7753a8885d7aef2455a152e23dfa2d699b35c33d361072ae5c512434d346f6c56fb5f11b0b647cbcafe02d88455a630a350862b3cd1513b6d6e4117c75ec4b12fd75a90f82dcea1c771fdda24ecf0a3e5b2e8a224236ef09a93ab59e59bdfb872860cc2d91134caf2139848e39aa64ba2e6d7252054f37ad55c2ce5f81b33ccb68a947371243a77e84367d9d35b11681410ea798b0387b8f10b1f89c68ad1cca9a3e032f3499879c89ae6382f389722011f4925143ea85073e4ff0ccf6d779bc3bf4c1b95fc7cf7f991a2162ab94541f3998ef6bc3fe80254aba41f15231503451b15e10852f85bd2d115935314cd80c123be0b530faad6b5074968221da04b546d962163299d52cef41e296da59cb076dbe899704b61730c19bd221ad2bd2981ea951be02c9f5bdf92d9870746b2a58c3d18a7d3e5e2c63ac2615837be1c6fe003656c1b3d71505f5e2188104e98911b6a5e3f5282fac0c8fa1ba36ffc07dc7a409df2eba8c75f70bd59a6f0651dc1b1b596de6acec778e2e32f1ed46df7a9aef51dfe5aa52436ea07f505d339f203458661c83a9a5a27aa48b5ec47f8d60d2a41001fce30ff753a8a8ce492efcd1f753b7f4ad736626447d1b6f07a617d4bfcdb48afef082dae1d76544e8b63adcbb60e1496693260c720e6721e0020efa3f8d88d15b5aa48a1b22c", "abd0c24697e45b8bc4830fb146532ea0ac845581ca3539d34124735409d015ac", "b4cac56f062bfb2e2715eaf9c8fcdbc20c86793f2357ddd04aad39f94ad7c78410874a74227ac7995edddd734d0e00dcc9f48a01dd5c4cb122c061e0bdc9ce14", "eadf7eeb102db1885854c29eb7b05c7c96bbb890002c4ed114ed62f5f9ccb4416b5eddd9adb55ce9c7a0d8442bbc8afa5c77b990ad6d46124dde70494872b2208a7c5802dfe9bd1ca19bef4b37c613b2"], + ["91ee205448c98b69a33ebf2935095d79c253029e5e5dc02df58a1003d1d85c27f2def5b110fd43d715e8d59ec4ad0f41020ec660cd9733e779b51a7ac2d5a631", "182f207b3175961f6411a493bffd048e7d0d87d82fe6f990a2b0a25f5aa0111a", "08ab2ee99d4d9b983ddd22", "82fef643dbf42dca5156fb51d4c4ee008a72f0dbc3f31efab075f2751537140d", 14400879385556610631, "d507cdfe6fbdaa86163e9cf5de3100fbca7e8da047b090db9f37952fbfee76af", "ff61668190bd52ed490e677b515d014384af07219c7c0ee7fc7bfc79f325644e4df4c0d7db08e9f0bd024943c705abff8994bfa605cfbc7ed746a7d3f7c37d9e8bdc433b7d79e08a12f738a8f0dbddfef2f2657ef3e47d1b0fd11e6a13311fb799c79c641d9da43b33e7ad012e28255398789262275f1175be8462c01491c4d842406d0ec4282c9526174a09878fe8fdde33a29604e5e5e7b2a025d6650b97dbb52befb59b1d30a57433b0a351474444099daa371046613260cf3354cfcdada663ece824ffd7e44393886a86165ddddf2b4c41773554c86995269408b11e6737a4c447586f69173446d8e48bf84cbc000a807899973eb93c5e819aad669413f8387933ad1584aa35e43f4ecd1e2d0407c0b1b89920ffdfdb9bea51ac95b557af71b89f903f5d9848f14fcbeb1837570f544d6359eb23faf38a0822da36ce426c4a2fbeffeb0a8a2e297a9d19ba15024590e3329d9fa9261f9938a4032dd34606c9cf9f3dd33e576f05cd1dd6811c6298757d77d9e810abdb226afcaa4346a6560f8932b3181fd355d5d391976183f8d99388839632d6354f666d09d3e5629ea19737388613d38a34fd0f6e50ee5a0cc9677177f50028c141378187bd2819403fc534f80076e9380cb4964d3b6b45819d3b8e9caf54f051852d671bf8c1ffde2d1510756418cb4810936aa57e6965d6fb656a760b7f19adf96c173488552193b1", "c394685d9295597e21557f219f3c9d5e640719bca5c8ed49999734e6c5b3733e", "c88d008484c5d79820ab68c67d083672b07f727d44d0cd14738800f825b9ff16", "0b7459616fc69395e64436cf4ae9441d374b29049e4c86223a0383f4e0246905", "c49242cee7e0868f2a75a1c412bc44d54c9709f659ded3269572929359e04c3a", "0e04d8525dd68f7ae868ca811e8833a7f47d7aadd37603ace607ee6c866bce23", "4a7a54ac00419598b0760153e26accd215052416651713eea18919f3e262d3b6", "30626d92eb620fd4a928b43fd550697471767de4496cfdadb1da18fc0cdd5aa6", "0208ab2ee99d4d9b983ddd2247ee58858033dac7d507cdfe6fbdaa86163e9cf5de3100fbca7e8da047b090db9f37952fbfee76afff61668190bd52ed490e677b515d014384af07219c7c0ee7fc7bfc79f325644e4df4c0d7db08e9f0bd024943c705abff8994bfa605cfbc7ed746a7d3f7c37d9e8bdc433b7d79e08a12f738a8f0dbddfef2f2657ef3e47d1b0fd11e6a13311fb799c79c641d9da43b33e7ad012e28255398789262275f1175be8462c01491c4d842406d0ec4282c9526174a09878fe8fdde33a29604e5e5e7b2a025d6650b97dbb52befb59b1d30a57433b0a351474444099daa371046613260cf3354cfcdada663ece824ffd7e44393886a86165ddddf2b4c41773554c86995269408b11e6737a4c447586f69173446d8e48bf84cbc000a807899973eb93c5e819aad669413f8387933ad1584aa35e43f4ecd1e2d0407c0b1b89920ffdfdb9bea51ac95b557af71b89f903f5d9848f14fcbeb1837570f544d6359eb23faf38a0822da36ce426c4a2fbeffeb0a8a2e297a9d19ba15024590e3329d9fa9261f9938a4032dd34606c9cf9f3dd33e576f05cd1dd6811c6298757d77d9e810abdb226afcaa4346a6560f8932b3181fd355d5d391976183f8d99388839632d6354f666d09d3e5629ea19737388613d38a34fd0f6e50ee5a0cc9677177f50028c141378187bd2819403fc534f80076e9380cb4964d3b6b45819d3b8e9caf54f051852d671bf8c1ffde2d1510756418cb4810936aa57e6965d6fb656a760b7f19adf96c173488552193b1", "81562dbef7bb353a62e7c81ebe68156cb75c5c7e3d96bbcd7daff50cb0957d33dd99779f7d3d72b18deb7a697510e0135b8df483a4d71d1ab108096e760891d53107f03dea4ae8e4d3febd9877f8570aa309d097d423bb763fb3e7e9be3c8fa034c01d664f47a0e7133ca11a48cd0eea4635fa77250a17bdf7b732c8984651574fd4f99f7aa0db28c2973152bf426ee9a4d841a91d5d335718eecbc9c8b2a2001570fe8b779143df229598a5be2548cf35842518cc1dbc78cc2f0fc8ea357ce6c17eb97c6138d53e6c8e00f07f800125182b25a5e875c5377209527222371f72bfbd462844ab06f3b3a1eba34423b69abf5de664ba83cd43b6a8e9d5b7c52adb8615041b90d908831a6ff92db48a14ac4dfa67d02c72e0c863157d98f8f54537929743c969bc91c2c1375204983c9999975ffa5ee5fe1f697199405f0966e31f34e1523844381844982b2c3b49a209ffa3cee979a85b19b850f41dccc463e22e24a3049d37b1fb370debddf4de0546245e4f02a98498af532e27acae5c7ed143e6e9ccfa743516021657acb25e4447845c5f9c5964607c4a78721d981a7ff2fdf6c033628bffd6f0b8de0cd635ec22f8b50ed637fe4e00f9d3c3d4f1810b09b75c96e2fcf11185317edfa39d1925ded814dde0ef00a3fb47af5d812094af13d01c98569ff7735787fa9bd01fa06928275fdd1038965fb06fb35edb7380dd3c42419e0c0ede4c486a9db4953886aec6ad307028eb26a37ef471567ad4bd4eaab7a82cb0d6b5f05e894e5325821d92bed2b86fb24337d579288f6df734771d9ef8358ba91a", "b636c39a6bad2263b2441ed5bbdb013588fb462701e6f876646c8c17fa2efde8", "82fef643dbf42dca5156fb51d4c4ee008a72f0dbc3f31efab075f2751537140dc49242cee7e0868f2a75a1c412bc44d54c9709f659ded3269572929359e04c3a", "46ba14f83ff5ab760f1420ebded986fd937827bc05692ecadb652ebbc8f6d9b52ec39787d8ebdd506ca1a85dc3d5ba4c5b415261b0753ac10e01864532a3572c68afe40ac3c0957b7afc23fd5e0517aa"], + ["f19042b9d10cc480a08c04322db6ec4e412eaa84c971828cccd733a11f253eda8ac30ba31fbc895d60b983062a5f453390793226ffd921bd64ac390703856a0b", "dadc966c8a5466b61fc998c31f1070d9a5c9a6d268d304fe6b8fd3b401034861", "aa14929c57898585ce665a", "78a4e33988d71d718e595555284c249a62b7128806a54c3b36a3aa5714931636", 17936016275122962426, "49950afcb0ef462a2ae024b0f0224dfd73684b88c7fbe92d02b68f759c475266", "ff3cd7b97a14943649305521326bde085630864629291bae25ff8822a14c4b666a9259ad0dc42a8290ac7bc7f53a16f379f758e5de750f04fd7cad47701c8597f97888bea6fa0bf2999956fbfd0ee68ec36e4688809ae231eb8bc4369f5fe1573f57e099d9c09901bf39caac48dc11956a8ae905ead86954547c448ae43d315e669c4242da565938f417bf43ce7b2b30b1cd4018388e1a910f0fc41fb0877a5925e466819d375b0a912d4fe843b76ef6f223f0f7c894f38f7ab780dfd75f669c8c06cffa43eb47565a50e3b1fa45ad61ce9a1c4727b7aaa53562f523e73952bbf33d8a4104078ade3eaaa49699a69fdf1c5ac7732146ee5e1d6b6ca9b9180f964cc9d0878ae1373524d7d510e58227df6de9d30d271867640177b0f1856e28d5c8afb095ef6184fed651589022eeaea4c0ce1fa6f085092b04979489172b3ef8194a798df5724d6b05f1ae000013a08d612bca8a8c31443c10346dbf61de8475c0bbec5104b47556af3d514458e2321d146071789d2335934a680614e83562f82dfd405b54a45eb32c165448d4d5d61ca2859585369f53f1a137e9e82b67b8fdaf01bda54a317311896ae10280a032440c420a421e944d1e952b70d5826cd3b08b7db9630fe4fd5f22125de840fcc40b98038af11d55be25432597b4b65b9ec1c7a8bbfd052cbf7e1c1785314934b262d5853754f1f17771cfb7503072655753", "d451b46289ba998c0cced1cc15b3fade94fa0b46e3b1a573349934e232b50e96", "a90a9b8ab1359dc96bdae90e5274788cb0c426eff260436185398bfff50e9237", "05b5e32076dae0948335ac3d651c6dbea64ce911423e2f2c7c1bdfa6b1414130", "8b14622d2f91f1698d53fe479a1e5c006498b98b85b450bd923a5d00cb52a613", "86ee66a6c7d9b5c4f0e2d2a0e1561e2afa5541a724ee027fc70bb7e80a2c6098", "88d1382c144202d0d7557587b0d5d02169292a250543cb0a06c34f452f7b3b36", "e373d86ec9dddd645d9a6d06efce22b896421d57a44d37a6504a5d19df217373", "02aa14929c57898585ce665afa3f54ecc587e9f849950afcb0ef462a2ae024b0f0224dfd73684b88c7fbe92d02b68f759c475266ff3cd7b97a14943649305521326bde085630864629291bae25ff8822a14c4b666a9259ad0dc42a8290ac7bc7f53a16f379f758e5de750f04fd7cad47701c8597f97888bea6fa0bf2999956fbfd0ee68ec36e4688809ae231eb8bc4369f5fe1573f57e099d9c09901bf39caac48dc11956a8ae905ead86954547c448ae43d315e669c4242da565938f417bf43ce7b2b30b1cd4018388e1a910f0fc41fb0877a5925e466819d375b0a912d4fe843b76ef6f223f0f7c894f38f7ab780dfd75f669c8c06cffa43eb47565a50e3b1fa45ad61ce9a1c4727b7aaa53562f523e73952bbf33d8a4104078ade3eaaa49699a69fdf1c5ac7732146ee5e1d6b6ca9b9180f964cc9d0878ae1373524d7d510e58227df6de9d30d271867640177b0f1856e28d5c8afb095ef6184fed651589022eeaea4c0ce1fa6f085092b04979489172b3ef8194a798df5724d6b05f1ae000013a08d612bca8a8c31443c10346dbf61de8475c0bbec5104b47556af3d514458e2321d146071789d2335934a680614e83562f82dfd405b54a45eb32c165448d4d5d61ca2859585369f53f1a137e9e82b67b8fdaf01bda54a317311896ae10280a032440c420a421e944d1e952b70d5826cd3b08b7db9630fe4fd5f22125de840fcc40b98038af11d55be25432597b4b65b9ec1c7a8bbfd052cbf7e1c1785314934b262d5853754f1f17771cfb7503072655753", "e76781ae63841fffea3021961594c22a8720c7d8aa808bc86e71a36ad7f86ff87c07d3c650a08e23e9b54f00b40ba0159169dfca34c140ce934019b2eaa8ea843580b35f14ea5192de8a12f9abc9061015e1479ef98d19a534e9e46164c3cac4eb54264cedcd83afc2ac2e087e39dfbae76bd550cc64a404d20c22ca003bf75b12fbb8c7151372700b439b3e0657ecc307708fc37494bd0639e8e1eaea378f27a13574b71fa4883b80712c7beb5c305f8d67e91997f80319ddb115b95123897aae5f2d14ffcfac7f6549ca548f6eabdf74817027d42d92d5cdf88ed8d511d1b5c4322f777974886c0ed01399180afa597dd2b77c58b27c8a612069e386ad634cb017a8e9f48e37c43ee8733a0acb69f8ed9f6f305f3bd1e982b94b1e51f4ba985b20ec974ac9a793aa264d615b9dea4859a4d4caa70d7a6b65307685ab534e5455631f6d68a451d8af2d418252800f684231afc26d1fefc403d75f2e120f5be2b674486009267cbc0cb001bb47f0ff4697eaf53dc99c10773a38cd06b48ba39119db4984d09a5bde13890ea0613d0ce0043eae9a2089141fd9465913c1cc3327a55942b9fd8fb81c847d8fddf8bdbacfa0fb0552c1fe4cc4c07f4dcf151c5e74e8d69b2b8bf7fd95eceb655e00535816d38b4a28d4a9aeebb69ab4dd12bf13fd5a459b6bb683ffd9dd7b0d0ce7296775808a843f3b8cc789fd5f43e084d87d6ada8d1f28c264e644e9ad965c28088a52e4b35642f9b5e0664990963bc23b9bb48f46747353580ecc4520cff1fa7f8fbc030e647df144ee6ca5b316b3af90489a809d9c9f", "856e1a9709b0c416933f5970715c56e2e05c2ea97d815125701479c33a5d91cb", "78a4e33988d71d718e595555284c249a62b7128806a54c3b36a3aa57149316368b14622d2f91f1698d53fe479a1e5c006498b98b85b450bd923a5d00cb52a613", "7236eab9f01298c84f3828f6ac154276b5b76462f5742d69dc477a105dc2711b12e9b5828c0176fef44a540f60958e5a3ed6a2cc5edde913d14cf8e8e28ea25c18627a84a2be961f44726767e9f8431b"], + ["0bb56c49c0632d4cc7e48551db46428f1b1a52661e07e0c3bcc23174ccbbbda1fa1924f416cd48390e2b11c6e78256d4c4c5641acad9a20c24fbe6cb4ee78125", "21e91a3c4aa3f27fa1b63396e2b41db908fdab8b18cc7304e94e970568f9421c", "e066b5e79686e9f36ecec7", "3b3e883e958cd6e0754d74caae1e5a4398abeb7d10ee5f75a4ab8ef7038e3db3", 12119135386131850622, "c36dcfd34a0cb6637876105e79bf3bd58ec148cb64970e3223a91f71dfcfd5a0", "ff4b667fbaf3d4b3b908b9828820dfecdd753750b5f9d2216e56c615272f854464c0ca4b1e85aedd038292c4e1a57744ebba010b9ebfbb011bd6f0b78805025d27f3c17746bae116c15d9f471f0f6288a150647b2afe9df7cccf01f5cde5f04680bbfed87f6cf429fb27ad6babe791766611cf5bc20e48bef119259b9b8a0e39c3df28cb9582ea338601cdc481b32fb82adeebb3dade25d1a3df20c37e712506b5d996c49a9f0f30ddcb91fe9004e1e83294a6c9203d94e8dc2cbb449de4155032604e47997016b304fd437d8235045e255a19b743a0a9f2e336b44cae307bb3987bd3e4e777fbb34c0ab8cc3d67466c0a88dd4ccad18a07a8d1068df5b629e5718d0f6df5c957cf71bb00a5178f175caca944e635c5159f738e2402a2d21aa081e10e456afb00b9f62416c8b9c0f7228f510729e0be3f305313d77f7379dc2af24869c6c74ee4471498861d192f0ff0f508285dab6b6a36ccf7d12256cc76b95503720ac672d08268d2cf7773b6ba2a5f664847bf707f2fc10c98f2f006ec22ccb5a8c8b7c40c7c2d49a6639b9f2ce33c25c04bc461e744dfa536b00d94baddf4f4d14044c695a33881477df124f0fcf206a9fb2e65e304cdbf0c4d2390170c130ab849c2f22b5cdd3921640c8cf1976ae1010b0dfd9cb2543e45f99749cc4d61f2e8aabfe98bd905fa39951b33ea769c45ab9531c57209862ad12fd76ba480", "caf6408def1f0f2baa17b130c3ae729589be69d828be54306916413cd2502117", "8d67e3ba4dbc9da5e83823a12311639651a4ffa95f27c1830d91d8b73cfbf131", "ea7c13f7e1205e78c8ce4ee4fdcdb7ee76928ddf6dbe1b2d6f6981b7c9657910", "857ba247d468e18dfe9673e9059923c22e9b700d563df8a989cc63000615b20d", "89fd2cf37956baaf1127bb0e33400109db0350f4abb7d6d81fa5848e1bb16926", "dba63794b67c496d011cfb6bba297ca57d18c7a9addffbc837176acf3a301e23", "80e7522cb03251c855341f06f9413341e16e83b489e15a0a0065c33bf38158c4", "02e066b5e79686e9f36ecec77e65417b6cd12fa8c36dcfd34a0cb6637876105e79bf3bd58ec148cb64970e3223a91f71dfcfd5a0ff4b667fbaf3d4b3b908b9828820dfecdd753750b5f9d2216e56c615272f854464c0ca4b1e85aedd038292c4e1a57744ebba010b9ebfbb011bd6f0b78805025d27f3c17746bae116c15d9f471f0f6288a150647b2afe9df7cccf01f5cde5f04680bbfed87f6cf429fb27ad6babe791766611cf5bc20e48bef119259b9b8a0e39c3df28cb9582ea338601cdc481b32fb82adeebb3dade25d1a3df20c37e712506b5d996c49a9f0f30ddcb91fe9004e1e83294a6c9203d94e8dc2cbb449de4155032604e47997016b304fd437d8235045e255a19b743a0a9f2e336b44cae307bb3987bd3e4e777fbb34c0ab8cc3d67466c0a88dd4ccad18a07a8d1068df5b629e5718d0f6df5c957cf71bb00a5178f175caca944e635c5159f738e2402a2d21aa081e10e456afb00b9f62416c8b9c0f7228f510729e0be3f305313d77f7379dc2af24869c6c74ee4471498861d192f0ff0f508285dab6b6a36ccf7d12256cc76b95503720ac672d08268d2cf7773b6ba2a5f664847bf707f2fc10c98f2f006ec22ccb5a8c8b7c40c7c2d49a6639b9f2ce33c25c04bc461e744dfa536b00d94baddf4f4d14044c695a33881477df124f0fcf206a9fb2e65e304cdbf0c4d2390170c130ab849c2f22b5cdd3921640c8cf1976ae1010b0dfd9cb2543e45f99749cc4d61f2e8aabfe98bd905fa39951b33ea769c45ab9531c57209862ad12fd76ba480", "3f4e9b1856e7bfba7abbc94a72b4abb1d84626793077e837daf33fffa27c7a33978a5432510d993c7d9224c097acc525881c76083c1b651a9de1b5c1a6e0482fae8f986ab59fa7cd4398996e2bc03adca990323baabddaae40b056b7ac17f820d11c0decba14f257a6cf0918198f389cdb2955772596927cbf5588561335e7d62e6a8af7bc33b99a55afa1b7ef20eb4ed6de8969d29f0421cd4d990666fdcf1ebd09065702134d31c32926a38b6b6b48fdc9b3c764c3cd95b972e768ebd8aae90d6a4a98b2d92fd9dfa2a299d060e85ef5683f51d0514a6eba72573f7bae84a2fd92be64241c27a6e5ceacbf37b2d9a975df7aeebba14d8c81158ecf5a0a25e12f985d08fbb4a1c13f761f3ffee8d538e393f3580b7382cd0bf517ce78871c19acf8ca065d7c8387cecd0d37ae217f440694772abd4b365556854baa8bcca9c4fef7189912f98a2527689276a4008c838fe74f7c2b759fc2ab7afe3782806e31b1c530cc46203bb3a566caf4d15b9940b43f33a86a65d49da8b6787de09638b481f3a8108a969ecadf9098bff2140c4b42e2b0fb10b90289b0c6db8bc085e8afe95dd36a4536ead7e95c99662cd928c22c3ebf39791578bc66fea3014d2292943083e746812452b00bc2f3e47c494746ced557b13ae3030d8a9578102bbad2fc3b845f31ae16f8d80b77f8431584a37e8f30b0b95cc4555abc053a0b4ff913b00369f1747b1f1c0ac8754f017e9947ca63255b3c23f456e23f96761399601fd8dadb5e3f90ab1b20138180ed69732239c8c215d9cc8ac8059bde816327d220b9a8ecba5d", "e6b70550e1d7a2be7304396441ec6ac0474599f9ead755c2cf276b8750c5cf2d", "3b3e883e958cd6e0754d74caae1e5a4398abeb7d10ee5f75a4ab8ef7038e3db3857ba247d468e18dfe9673e9059923c22e9b700d563df8a989cc63000615b20d", "02b1373eb18956322b47a1700db743316ede4644d6593cd79422d7513d1b80e68505dfe9d6862e794e30288baea8b0bcb38b354977aaee572ee8868b2da07da2992c6d9fb8bd590b8da02811b509e8c6"], + ["ebd4806d81254989fadba8cd58967d6fd87383bc093863d5abfcddd38f1539fab7e5d4f0619167b8d482cb548cb55983496f77d3dcaff56e32410bfec1f26811", "b25f303f5815c4533124acf9d18940e77522ac5dc4b9570aae8f47b7f57fd876", "1ca7b649399e13e4394462", "3feb345aecd3429a16e10f3d1320bc9971b59e639d62b6961aea781567a8609e", 9624581763228770449, "4a95b205526cfcb4c4e1cc955175b3e8de1f5d81b18669692350aaa1a1d79761", "ff7582e54d7a5b57a683b32fb1098062dad7b0c2eb518f6862e83db25e3dbaf7aed504de932acb99d735992ce62bae9ef893ff6acc0ffcf8e3483e146b9d49dd8c7835f43a37dca0787e3ec9f6605223d5ba7ae0ab9025b73bc03f7fac36c009a56d4d95d1e81d3b3ebca7e54cc1a12d127b57c8138976e791013b015f06a624f521b6ee04ec980893c7e5e01a336203594094f82833d7445fe2d09130f63511da54832de9136b39f4599f5aa5dfbb45da60cdceab7eefde89be63f3f7c0d2324847cce1405def7c469b0e272494e5df54f568656cb9c8818d92b72b8bc34db7bb3112487e746eefe4e808bbb287d99bf07d00dabededc5e5f074ffeae0cba7da3a516c173be1c513323e119f635e8209a074b216b7023fadc2d25949c90037e71e3e550726d210a2c688342e52440635e9cc14afe10102621a9c9accb782e9e4a5fa87f0a956f5b85509960285c22627c59483a5a4c28cce4b156e551406a7ee8355656a21e43e38ce129fdadb759eddfa08f00fc8e567cef93c6792d01df05e6d580f4d5d48df042451a33590d3e8cf49b2627218f0c292fa66ada945fa55bb23548e33a83a562957a3149a993cc472362298736a8b778d97ce423013d64b32cd172efa551bf7f368f04bdaec6091a3004a757598b801dcf675cb83e43a53ae8b254d333bcda20d4817d3477abfba25bb83df5949c126f149b1d99341e4e6f", "d2f9adff531b65432ba2d7daa6d86e62e4edc786d9e0b27d26628b79da6b1514", "9a09e472e8e996fcc30ed5237208dbb00171320e6bea439186009dad2138ab29", "18fcbd40acf1a7f4d609879a5f5e3b3970094ff8be8418607016c6a697f89c20", "3bc17a580d530f8930a36b8d6fea67857f7b8520fd2e0ab5d5cbab1accd54e3a", "cfe03eb2d33676b773837da839172d33333188c9dfef05c832a25c86d3bf0e8f", "d2c2889e037eac606058682baa3886a4c2dd44eadf8b2ce43995ded761fdafb5", "fee3e3b5fd6cd854442b2ac29770fb0e3932f471524326da4a57c25618069e99", "021ca7b649399e13e43944629120f4d41e6291854a95b205526cfcb4c4e1cc955175b3e8de1f5d81b18669692350aaa1a1d79761ff7582e54d7a5b57a683b32fb1098062dad7b0c2eb518f6862e83db25e3dbaf7aed504de932acb99d735992ce62bae9ef893ff6acc0ffcf8e3483e146b9d49dd8c7835f43a37dca0787e3ec9f6605223d5ba7ae0ab9025b73bc03f7fac36c009a56d4d95d1e81d3b3ebca7e54cc1a12d127b57c8138976e791013b015f06a624f521b6ee04ec980893c7e5e01a336203594094f82833d7445fe2d09130f63511da54832de9136b39f4599f5aa5dfbb45da60cdceab7eefde89be63f3f7c0d2324847cce1405def7c469b0e272494e5df54f568656cb9c8818d92b72b8bc34db7bb3112487e746eefe4e808bbb287d99bf07d00dabededc5e5f074ffeae0cba7da3a516c173be1c513323e119f635e8209a074b216b7023fadc2d25949c90037e71e3e550726d210a2c688342e52440635e9cc14afe10102621a9c9accb782e9e4a5fa87f0a956f5b85509960285c22627c59483a5a4c28cce4b156e551406a7ee8355656a21e43e38ce129fdadb759eddfa08f00fc8e567cef93c6792d01df05e6d580f4d5d48df042451a33590d3e8cf49b2627218f0c292fa66ada945fa55bb23548e33a83a562957a3149a993cc472362298736a8b778d97ce423013d64b32cd172efa551bf7f368f04bdaec6091a3004a757598b801dcf675cb83e43a53ae8b254d333bcda20d4817d3477abfba25bb83df5949c126f149b1d99341e4e6f", "be1dffd3370c675669cc9ae1d0302d7f906d2523093c24f4257a83bc4f36623a082ce6eb4521957191d57e1411ede71d44b56c57cb22814a046939d2fff92b4662762d4f21c078427472b91810105556f4de0a27e770084772cbfebf87db3314ab70f26d11ea5de267c3a9a8f46bad13c7362610bdba8102d4b726ef26ec794a1566571bfdc102477da5b49bbf9fe4b1a44ed0b3bced99ba819a4f30226549445bc61cff5c3316335f6bd4a9a424c94ae0b5cbe48afb2b94d0c7e44e323095a72e4264e91c4894b9e845af323502dae8c18678a4f740e5a63a4c702992facdd35735b1d1348b919c700c42d330d386afb873fabad8cb3218151b401801e369344ff20aaa6673474f4bfc98d07e367bc42ef1a04fa1bc1252188dd9d3e000e3f5e9dfc9e13ee9db55040d17227da44a3e08fd5ec858c49c2e6a711f8e68d0a1df88ef0940f72ed73ef49e8a45ae2e5e1bf137ba58cfb92579abb2a49313a2ff3db61693d2b758af20472ac6406ba355b48cee22e70fb8f9d48ea3934b6224ace269b9ef546dbfc52abecfac5940f040bd21e90efa8275561a88bc18e26b988d1179b7a2c3afd86ef2a090625223234b39c9e2068d945dd7763b010c28c89b72e25513b39c3ce11773428ad344e1d5d51b920014f91706ffae3d86361477fd5de013422c06a332e3457975cf9be9f9ab3a06872ef0717d3908bdebf8418ce557d52d51a250c08c5b793ad4bc0f16c62789fea2cab39ccca407ee9e47f56d20a741912c6baddbd7fa7b97e546336128745ae7d730a55a6ac7b8fcbd72ce78959c7a7975212c", "eb3ed9fcb3aa91c4f5ecfd43dbda40330693c3a6567545fd236af1908e2942a3", "3feb345aecd3429a16e10f3d1320bc9971b59e639d62b6961aea781567a8609e3bc17a580d530f8930a36b8d6fea67857f7b8520fd2e0ab5d5cbab1accd54e3a", "60f3e894e3864efb48ccae50e10da773dccf8562455d1b731aad44e15e3e401831ce6f92f4532d90839259ce9cb144621f1201778f615d0987010c8d135c32d56ee2846865a261de1425d23bcc51b8a0"], + ["c37c7dbbe551d9d3b1a496887db2e842dc945201f40810df4d763932ed5c76398b3573fe23f1e8b7e79f1c1695c097c124ff1f7d6e61f2c58f1439a756969d19", "a668a0ae2bb934c82c4142da69d12ca7de9a7df706400ec79878d868e17e8f71", "564fc381fc4dc8118de47c", "aeeea50c6bb02e5e224dc2959c229d0e3bb879c4ab00aa0ab25a40106b80bbb7", 11137853725062838288, "2537b871b4294a65d3e055ff718dd9dc8c75e7e5b2efe442637371b7c48f6ee9", "ff9e3ea38a4b0f2f67fc2b908cda657eae754e037e262e9a9f9bd7ec4267ed8e96930e1084783c37d6f9dd15fd29f4cc477e66f130d630430dcc0104899b4f9f46eb090ef7fc90b479abf61f93955ee00e6a1848f1ab14ad334f2b68035808cdf1bb9e9d9a816baf728a955b960b7701fa626687dc3c9cba646337b53e29816e9482ddf5578a8768aae477fce410ac2d5de6095861c111d7feb3e6bb4fbb5a54955495972798350a253f05f66c2ecfcbc0ed43f5ec2e6d8dba15a51254d97b1821107c07dd9a16ef8406f943e282b95d4b362530c913d6ba421df6027de5af1e4745d5868106954be6c1962780a2941072e95131b1679df0637625042c37d48ffb152e5ebc185c8a2b7d4385f1c95af937df78dfd8757fab434968b0b57c66574468f160b447ac8221e5060676a842a1c6b7172dd3340f764070ab1fe091c5c74c95a5dc043390723a4c127da14cdde1dc2675a62340b3e6afd0522a31de26e7d1ec3a9c8a091ffdc75b7ecfdc7c12995a5e37ce3488bd29f8629d68f696492448dd526697476dc061346ebe3f677217ff9c60efce943af28dfd3f9e59692598a6047c23c4c01400f1ab5730eac0ae8d5843d5051c376240172af218d7a1ecfe65b4f75100638983c14de4974755dade8018c9b8f4543fb095961513e67c61dbc59c607f9b51f8d09bdcad28bcfb9e5d2744ea8848b2623ac07f8ef61a81a359", "b27f4859150d4845ab57788261500a12012d63c009c67744bae0d58388ffee2f", "543ea71156c9a6f8041fa77ec1c5af90288f2720f13ff093c686266b92d7a024", "1d51ea92fa43550a0eddea236e17a01693c22d8dd81c9c9ec876a24e67d4930b", "19e0264b8288f73ebf9714b0df858ef7ab39ec502cd298f2c484a9f4c7da7436", "8fbeb6b3038e6949916a2c060ef9a4b1fef13ace2fee0025da32c36d231a6134", "67d68a5a0593fd167d38082e49d2303086e55a43c124d5aaa820ab0c3f5cc537", "6b8d83f2f1fd1ead7d4542b363093407c50a20ed7f0e8cf2db536db1be25e98d", "02564fc381fc4dc8118de47c10b8a1baf39a919a2537b871b4294a65d3e055ff718dd9dc8c75e7e5b2efe442637371b7c48f6ee9ff9e3ea38a4b0f2f67fc2b908cda657eae754e037e262e9a9f9bd7ec4267ed8e96930e1084783c37d6f9dd15fd29f4cc477e66f130d630430dcc0104899b4f9f46eb090ef7fc90b479abf61f93955ee00e6a1848f1ab14ad334f2b68035808cdf1bb9e9d9a816baf728a955b960b7701fa626687dc3c9cba646337b53e29816e9482ddf5578a8768aae477fce410ac2d5de6095861c111d7feb3e6bb4fbb5a54955495972798350a253f05f66c2ecfcbc0ed43f5ec2e6d8dba15a51254d97b1821107c07dd9a16ef8406f943e282b95d4b362530c913d6ba421df6027de5af1e4745d5868106954be6c1962780a2941072e95131b1679df0637625042c37d48ffb152e5ebc185c8a2b7d4385f1c95af937df78dfd8757fab434968b0b57c66574468f160b447ac8221e5060676a842a1c6b7172dd3340f764070ab1fe091c5c74c95a5dc043390723a4c127da14cdde1dc2675a62340b3e6afd0522a31de26e7d1ec3a9c8a091ffdc75b7ecfdc7c12995a5e37ce3488bd29f8629d68f696492448dd526697476dc061346ebe3f677217ff9c60efce943af28dfd3f9e59692598a6047c23c4c01400f1ab5730eac0ae8d5843d5051c376240172af218d7a1ecfe65b4f75100638983c14de4974755dade8018c9b8f4543fb095961513e67c61dbc59c607f9b51f8d09bdcad28bcfb9e5d2744ea8848b2623ac07f8ef61a81a359", "77c6efc8b542a707c0a5cf5ce3f3b96de191957c9fa6e9bb4b8d899e1f19e020ba7bb3fef16781c88cc5d44a5ef8173147dc3d1b516af6dd77ddb6ee67aaf542cee2bed3e4a07ece428f22a801cf01baad1827fd425746c545001c356d0abeaaa5a422dfff0ee218ac37ef8397c62ca86fabebb688b38fb4a6542911be1c5e71778b5eb53af1c4cb4dd994724f610f38724a73df092beae8b87f7f6a2bc09df2aa18c2f8eeba63ee0d31353b6f283ef59ac1536073da7a6d82bfdc097402080fa103cb8b3efb941ee501f6412cfbc250afadbe544ac51fce415a2493ba839e3818b0fe3018bfa437f06e3186148aa405bab821a26ea07f93cfe7568fe3ef08fa0b80fcec5bd5915f688cf599315e79aaea34d518d955feef303f69b287c6d0516da239fbbddbaf2556ebce77a3d597235c22d38c5b5eeb98c7c08da8d376bba1b50785be82bfe09ae71cccaf31a2f0cfa076d1e4d1b52fee45c8ed23df33a81cb1a8acec9f535da49670f9986d5c92c82b0ad220f85f3b3872ebe053cdeb961bd2d3ab3bcd676e6fd7cbe9795e1f2d8287007c910e7b430169e451f0b2d763e543033bc6c7389fa1615ba19d1f2748b217c960fe050407c8f473356baa6e0c7d77fac6c7db4512af5796b3bcf123e090b980ebc2d64b86dd24cb9a6dab1db413047538902e2e490e4fc878aa04dbef6699639c3dab17c5147048ac6d48490dc4885ed986706335f41ba41559659e1b53da76514cc40adb66c35ce56f3abe39e1aee5849fffcc6e1f1bf811ceb665a6fcf8806bbbba4a5b8738a117dcaffb4fdf1008006f", "b4f88a292d09d935b4775a2930eb38cebd5af6ff3f39ef5bb24cd57281f08cfb", "aeeea50c6bb02e5e224dc2959c229d0e3bb879c4ab00aa0ab25a40106b80bbb719e0264b8288f73ebf9714b0df858ef7ab39ec502cd298f2c484a9f4c7da7436", "94e37fd66282c02e90e769914caf95a495f4897f55a5ae95ade8bf6761e31ba5d1cfeb306f4e22014251cbe3f8724be76921e2ada46e3b145d1b043eb12a0efab5160934bc759e0201d866ada7443571"], + ["74a8411a20bc3c53f7e7abb9316c442b4b09cf88bbed4a90b92f5a1ced93162bc337346720ec0cd0ea735d9e323f20db778ad18a84c79ee6287799ef02764107", "0c811e4c31fbb49f3a90bbd05dce62f344e7077593159ae35050b04c9e6b86bc", "c6e8f0d50ae8058791dc0e", "8e66b792ecb156ef685ee8ea35d382758ba41597a33a93baf381d63c175ba98b", 7387862906040043846, "2501e51b012aea9446a2104e93f815a0b3a29b458314f3d8be2b9823d342f462", "ff13e942a7e19a46e970b5c506708430317b1bb3b35df68ae33a4926a03e6bfeb5510416fcbb0524c9ca5074156cc5a5d6fe1c995edc60a2f550411aa41e3da3bdcf64bcf04a0510571b936d47e55cec0330ee8dfe73563404f047d7f3a8a3d7743bc554955210f1eb0d08599ea77d5f974d87176d37d98b9c0ad440407209ed6a9f08464d565593e1a63b938536b49244e97d880173b640f2ddb74d068ecb46cf289b7d891307bba37054cf91b31fc82f74d5fcc000942ede911825f53fe609686f463223b1e9bc03bde895d1238fad04a3bfce68a075e8a37c0e87bf46dd015545f9b4fb0eec645ffcbbe0ca5f8c561b257d52d602d8c94c502873a01d9251d8c860c041525b3bf4e3a2eb9272815c7586768428b4c2b25e3745f009c5dce20b69d5d7c43ceb736b6831e8c110f16cfdb3a467e9414c00ecf13731500894555678c497faba9a95d01cc464390fc4a76bfa8b0e1c68a525d706d6604b2330b6b3485215f606f1883a751588c7efa506c3e8d0c60192e8476bd1175d9562087bdb818e66216286bafe47ff4dbcced51444480a9a5673ece7fac73a0ed41ab0051753a7caa89be3139afd9793b3e02f27f040046595acd47bf13fd0da27f09eda48036d3ee437f2ee8f8606ea97343c33584657f46dba99db5cfe6ca176fab7b0f3bfa0ab61e340c34eb9f17c7ec2be03b180f0bb6f434c2a6542e00e84373f4f", "4735a6fd215c7b95033dab62ccf9cd51008908a6cdd0aa021b888b98e23c3911", "bddae8dff1205e04968fae1fd9be51d825f5d8781d933d0f5bce9ca83ee8ed20", "be43ee84707075ac4808d0975407c02736d76664f4e7aece01d9cc68324ae904", "f9f7a0105ea9f445fb7a14497262c6e4d73289327b8a2df5e263f3e39907ea0c", "fa19a1527b76048ff37fa4f82789fe80b0cdd35d5da9c2ec3fe3043805c06123", "2db5b892b61b9c553b6c9b7acc7d7105c1dd4c28c67f978b6d79c71b98a0d000", "16e3f985c07fefe530d9e6945edec1903bb1ca8da5a25be95978637a408c2efe", "02c6e8f0d50ae8058791dc0e4649cda32bf686662501e51b012aea9446a2104e93f815a0b3a29b458314f3d8be2b9823d342f462ff13e942a7e19a46e970b5c506708430317b1bb3b35df68ae33a4926a03e6bfeb5510416fcbb0524c9ca5074156cc5a5d6fe1c995edc60a2f550411aa41e3da3bdcf64bcf04a0510571b936d47e55cec0330ee8dfe73563404f047d7f3a8a3d7743bc554955210f1eb0d08599ea77d5f974d87176d37d98b9c0ad440407209ed6a9f08464d565593e1a63b938536b49244e97d880173b640f2ddb74d068ecb46cf289b7d891307bba37054cf91b31fc82f74d5fcc000942ede911825f53fe609686f463223b1e9bc03bde895d1238fad04a3bfce68a075e8a37c0e87bf46dd015545f9b4fb0eec645ffcbbe0ca5f8c561b257d52d602d8c94c502873a01d9251d8c860c041525b3bf4e3a2eb9272815c7586768428b4c2b25e3745f009c5dce20b69d5d7c43ceb736b6831e8c110f16cfdb3a467e9414c00ecf13731500894555678c497faba9a95d01cc464390fc4a76bfa8b0e1c68a525d706d6604b2330b6b3485215f606f1883a751588c7efa506c3e8d0c60192e8476bd1175d9562087bdb818e66216286bafe47ff4dbcced51444480a9a5673ece7fac73a0ed41ab0051753a7caa89be3139afd9793b3e02f27f040046595acd47bf13fd0da27f09eda48036d3ee437f2ee8f8606ea97343c33584657f46dba99db5cfe6ca176fab7b0f3bfa0ab61e340c34eb9f17c7ec2be03b180f0bb6f434c2a6542e00e84373f4f", "2d404a6881a6ee760cb53b9cc2715ca76a3a2fc9693b1abbcdc75cb6d6c36ecf84d693672c53ced8798cc8f1e53b8a9de7bbb5e8c5a46c3a7412df11c5da16b4dd22901a592b0e932977ba06673d6fd038acbaa9bf79c15ba62b6e3074ef953b814cf1bdf01577ed3e3faef47155c91c68ee32881b737494b3b476083b3bd17793c498931eaa92b17c7d104758fc8b3493d247417f5ec1979a352893e99563b6c3ab95cc5afa3732efaece9e7432c80415e25f555653c7da5db0cc61087421959bb1df8003b73da0bef060f3a84c8bc24cc76d0d9e9c33765c20f07d80e20fdf27815dbd9d717c0966f80b94b95915081ea45537a5a074b9c94b43ddf4a9cbade904510eaa969e666c9434b9f63eae62ad58279962e94133055cbcc4b155c00f1b83ff4128a8abb4ce68e9f1e308e6f97e513af595471a1677ef78e9770f43adde1a64586de6a587c3d693fea8fcc6acc894961e2f47b202e86a573879b5bfd729da2fbefc645cfab1880d517640df5f53e57c72d65a633aa536b29834bf2816b1f716bf436d6b2b6e477328c958a6b8cf73b95d22f6993b3fc525db627f6f38d0779a1d39af05ed74fdfeff987a9588d80b7e79694ae4552929881c5bfe20492fd6f337ca88dfb501e545d23673acacbc3d3314a8bbf5ec70b705cc9d2657bdd5a70915bef6d0f039d3eba6bb715be51ebf6ef659ea32ff80c82c0421675fe371ef49f1b9e38f437b4a7655dc2916aa3086de6c62a82b361c053fc63454ccd02c22d41ff5bb8362deaa70825ad2f993639fc446069d78a61d338df58f7763e355e6a9ff", "8b0d298ee8b42534a42fb9635ba758ea9f918b8316c0e894a908488901d9fba3", "8e66b792ecb156ef685ee8ea35d382758ba41597a33a93baf381d63c175ba98bf9f7a0105ea9f445fb7a14497262c6e4d73289327b8a2df5e263f3e39907ea0c", "f3bf9076f3db66326da60cc7943c854d8de99f5753f70c32ed01fb2e849c9dc73f80b5cbaab4992dd7e738b961fd753f7c5b2924d1d9630661339259283e3a953c57df3a48ca8271fc5f264d6f15b6b3"], + ["73a25eba9bd7a8ed2b5b1b8d5a056bde8d05e6a28067b3845791bebfa7ae2acd36326fe627bee80e3292e0e5132de16ca4f81e5a6fc09c95ff13b52e96b7890f", "f5e8ded81892511cc2851b00b832712a6d3ba5666517bcd3567621a7cf844558", "81f2757c532ed3b62e8901", "55db7290073ba00666e87d2561b8883c662c5678ff27302a82e20a720170891a", 17209482587585417762, "fc548862f5a07094fd428a7bbc15d7b38d05362c9ca985f58a76647d2be4c2cd", "ff6b3d17d6870971d7a098baf72c6f6f1214cf1faae488bd7de259d3415c2f0ddec7457004f35708d1eccccc0df65a04943ad5cbc13f295f000fe056c40b2d88f27dc34cfeb803be3483a9ebf9b5a9026057725d63ead2c0c0ff1fe26ac1e7bdfcd6fad875842d194f331750462c06b8d7982d67995ed5d3ae96a05ae0067f4eb1c7c93231bd39773cbe0a9d66b0c9aa8cff6a376e1f372eac6ac4e46cc0942245d4c2dcf02d7640ffcc5a6ac3a87f5c411551bcc2f26cb94961d53f95ddb19ae930c8d70f031b29a5df99ff36695e802cbcb6b58c1ba7ed5eacfa76414a41ad4a44f71f1b580d34c3a952920b254a145fea517f5b42b2f65ecd0f82595478d80ae5c8ceea12a161ccbb5eac09990fc619a46080436dbd08d74784af002d58e06faf7f3ceae7d3419b1fca265a5559cf9e2d3b60978d81a678b9ed8e4486b4d14609d6c127c0c2fbffe30a60f7bff1d9fb8300ed009253ba9b996fa05241b10f5ac9a8408e925b626bb21a471fe3bede52bba097b2a99a9ba5a86658c3fd9ec55bfa9b328567254ab36d2c7f44d2c7e13eb54beb70ea8fa94b6c6e012d79e3f53689c2b1a18eaf2d471d13c1ab39d9194ae843ab1d28ffa8f69dc7e15cc38b12e8fcd79255b7216056d9edb7482fb98aa033b65e51c1a08b8a11d84d0409b734f452aaf0d6b18f50258683d3f9a76d399fd047eee288bb4585851dc93eccc623", "e8065c4096d3543340011f5890b17eedd2a706440734784101ae2d8e87e505ad", "c279fa9d1c841193d332f8ccf4d0b1e45601a8af6676d762fba7313345893514", "6d2997d1ce0a949a63700f461b5712aeeb43d45504e35bda16529777c74d191b", "9dc4c8c032d3be66d2636ba0020c63f4265329ffac2ae635573263f499bd4c13", "e4769586304a6a9b3a2aef3af58b97dac2cc4aeb389f68c12887731e0e12bc1e", "f6ba4b1fbe01fa2f1dd4093c5cc485a9bfd9ef0f578949d6e100b0055cb8f331", "d3c22051003e882a5dddfb4823d6772696a7e99f26b1a6acd24beed5f22f9ff8", "0281f2757c532ed3b62e890122924cd13b5dd4eefc548862f5a07094fd428a7bbc15d7b38d05362c9ca985f58a76647d2be4c2cdff6b3d17d6870971d7a098baf72c6f6f1214cf1faae488bd7de259d3415c2f0ddec7457004f35708d1eccccc0df65a04943ad5cbc13f295f000fe056c40b2d88f27dc34cfeb803be3483a9ebf9b5a9026057725d63ead2c0c0ff1fe26ac1e7bdfcd6fad875842d194f331750462c06b8d7982d67995ed5d3ae96a05ae0067f4eb1c7c93231bd39773cbe0a9d66b0c9aa8cff6a376e1f372eac6ac4e46cc0942245d4c2dcf02d7640ffcc5a6ac3a87f5c411551bcc2f26cb94961d53f95ddb19ae930c8d70f031b29a5df99ff36695e802cbcb6b58c1ba7ed5eacfa76414a41ad4a44f71f1b580d34c3a952920b254a145fea517f5b42b2f65ecd0f82595478d80ae5c8ceea12a161ccbb5eac09990fc619a46080436dbd08d74784af002d58e06faf7f3ceae7d3419b1fca265a5559cf9e2d3b60978d81a678b9ed8e4486b4d14609d6c127c0c2fbffe30a60f7bff1d9fb8300ed009253ba9b996fa05241b10f5ac9a8408e925b626bb21a471fe3bede52bba097b2a99a9ba5a86658c3fd9ec55bfa9b328567254ab36d2c7f44d2c7e13eb54beb70ea8fa94b6c6e012d79e3f53689c2b1a18eaf2d471d13c1ab39d9194ae843ab1d28ffa8f69dc7e15cc38b12e8fcd79255b7216056d9edb7482fb98aa033b65e51c1a08b8a11d84d0409b734f452aaf0d6b18f50258683d3f9a76d399fd047eee288bb4585851dc93eccc623", "7229a0a56a144b042c1ead9180ac54dac6c55cf4c22fbe7cde99960bc620d4dd60e4bf18a0ea7ad9093bcd3ff6d1611c565f88e735ef4c518c77d62228e1e4a135ca6cb4ed5abbdf3e81d09650a8fa9b5c3d05b6dacf3c3db3b363e4105723700c69139f81ecc48d883da039dded5ef6040ab2120e533b1ffd0674db5b926e587f16e7e8962b124835bd56cfd8e75bf6aa4dcd4d6f0b5561719c80aa82b3bcea167a31c6698761e2d26cb56dd30416721c93373292853358fafe7495558db99e47a3a16ed22cdb9d7d16cfd9a7bb559c7286ed84f8899cb0522e8a497f3e14452ba8a94a7f58e5de371d76ecc9efe20ae79bee12bce4e4b6f23535e5c3c43a4ca2076fd673f0806fa985c588d114c07d8ce3a233e54d77116c8a2a56a682e7a485df71b302a036ddab214dee776219cc242594f75b8ebd566d74b16c9ec0058bca2881b79b10e8a8010820618ac6526cf94b13d9759f37339334e8b2c6bdd1d0f5e2463cff2b8da6d2c686aa987cd1f07e9aa260dd0428a4ff78aa8fda477ab38acfccb1909177b527e938f1f9dcf31f4f40a9628951fc2a7abc041e8c933608bb47b450b28feee04158a8174bffe4970602488642c19e61d473f3de0cb0b64a30d6f14668d1b01777566fb5acc2e92e64d9757fba13c1ee9cd03abe98bd7e8ad7041c3feae7c1a7243ae3610aac64fec6c9fc943d6abce910adbe23b546b4c24aa9f2ce5d97062ee0d1ccc48cfd1fdba7fdac0b04d1b3dc7a70781cdda2a2703de003cd0151ec65bf7d1ac63bb735bc2bb67ad2b01ed6b9ae2ebbd37a8f8ec1a653a87e", "1ba4acd77510c4f0c766adf7c7df1d1c54d5bce3d60af35e8dd48fdd04a78c0b", "55db7290073ba00666e87d2561b8883c662c5678ff27302a82e20a720170891a9dc4c8c032d3be66d2636ba0020c63f4265329ffac2ae635573263f499bd4c13", "430daa6b75632280d5e6dacbd2a0ffe2af9860c83a3d2a87f1796288ebed64d0cdc460e2c861c4f9387d9259fc6001acd0e76f3b0fdb5dac974c26b51b859fabe02eabae968aab2e5e61efc2d4462c1e"], + ["a4d79c819a6c5e0167fca98ce2629815f9bac926b62718cfbe5045d92dd71cd33675d556e0771e40cc3d618d9bda132f13953d82432e81594a971e98b0714039", "67799a9001a2ed3676a8b403ae25ffd772f7081e9a32bcc1c5e2edd4e2a6576b", "ddb7c5bc4de9df521bb04b", "653d07c907946ac3020ebde1b4f610210c30c450e4271265a05d6ece446df439", 7122345086698755501, "2dd417df26dcd220f2b731772b439e96d614e1facb486c7a7d5171b1de359f6a", "ffd3a96f649c969102a1964fb4b4a1a4279c68e6c372e42187d754e804a61653092069fb9b6d25266890808b015df28c801065da6febdc1a56bfd002625acfaa5373fde149c1cfc3649b4869696d44ecb12479c5ebef995f10029f8b530eeb3fdc2e50e8757fc0bb9e263023db82f878d9ac7ffb0bd4391df1d879899a3ef57bfd0d1f7755648edd85bb052a6edf71cd2628c987429f36dc505ccc43f30e7a869c9e255e2af9fcf30c121796d190000960cb6fe2f1bf246118b498f3247f9d484c73cf09393039e45326b8ffffb3e7e6159c46699f100792d4672950348a90552e45943beeacf03f3216f94e274d63d637d9f190e8a266cdeef153530bee5cb8355260505c2c2e5d990fffdc34ec0ff7f1af81b24ced0efa6213da6c7c60c487f5f7b03f8160a057f46d05bf8218b3add9c06893bd02db9b61191dfb133bfabe4858e47a4cc32e416ec08b8ac7915a43733f4406e9d967c560f344d7e904a28045d99f3af8c82e97e1b9c1b205e585fbebb48faf58f1b65dca2497e09a70aad4865f85715a280e186f3fc1740d8184d33e8322169521cdc132212939c84a108964e2de74b6ea55b4cb8f6f9bee98b10d415109455f48b776082dc30b4bc73477075511700308158ce2f2f9bf0f691b2ce53e61142cb740c15b7b623cf48b3f7bfefa31bcdc665c6d7123e95350811375947b055a43db07e03f33627df5c638bf", "0055f35c6c8262ac74fe27d72a33bdb96f1ce057c330d1ccba2f7da8715500b5", "ea3844759a9a1cc528b295ce70137a85f9f08e41a5c7c1cac155a669a318533e", "6aba28105bc072c52ab8a314797ff86666dfb7cd8a2ae17c585fb7b6515b971c", "03fb794375275d23d158d5646bc463a8b738bc7938f60dfb155bef4d461eec29", "959bea8e11968b0f343c04cd6d5016fcd433907536a246ba1c5d3e8897f3231c", "e26919b40c70af741df904517255035889ee5a44426d6ab85c074b862ba06308", "09dac6511c3844587f829c2f1ea037a81a8d5485ed04eaf2758005b32a20470b", "02ddb7c5bc4de9df521bb04bad956ddc1ea7d7622dd417df26dcd220f2b731772b439e96d614e1facb486c7a7d5171b1de359f6affd3a96f649c969102a1964fb4b4a1a4279c68e6c372e42187d754e804a61653092069fb9b6d25266890808b015df28c801065da6febdc1a56bfd002625acfaa5373fde149c1cfc3649b4869696d44ecb12479c5ebef995f10029f8b530eeb3fdc2e50e8757fc0bb9e263023db82f878d9ac7ffb0bd4391df1d879899a3ef57bfd0d1f7755648edd85bb052a6edf71cd2628c987429f36dc505ccc43f30e7a869c9e255e2af9fcf30c121796d190000960cb6fe2f1bf246118b498f3247f9d484c73cf09393039e45326b8ffffb3e7e6159c46699f100792d4672950348a90552e45943beeacf03f3216f94e274d63d637d9f190e8a266cdeef153530bee5cb8355260505c2c2e5d990fffdc34ec0ff7f1af81b24ced0efa6213da6c7c60c487f5f7b03f8160a057f46d05bf8218b3add9c06893bd02db9b61191dfb133bfabe4858e47a4cc32e416ec08b8ac7915a43733f4406e9d967c560f344d7e904a28045d99f3af8c82e97e1b9c1b205e585fbebb48faf58f1b65dca2497e09a70aad4865f85715a280e186f3fc1740d8184d33e8322169521cdc132212939c84a108964e2de74b6ea55b4cb8f6f9bee98b10d415109455f48b776082dc30b4bc73477075511700308158ce2f2f9bf0f691b2ce53e61142cb740c15b7b623cf48b3f7bfefa31bcdc665c6d7123e95350811375947b055a43db07e03f33627df5c638bf", "7b598778a7284d52a747774c54bd9257b3f17af13ecc72c0e3cd95ebfafaa37d16651553dd27f01c9cf24b62d7dcfd52fa4b2b3b4a8ca9ebfce7f4fcec27e6058e4468c15010d017cb901abfb22ead869983f69aedf2da7d6aafd1306ee736f2db33bce4b09fca74692a5209a7392b7ea9685be9ec431ffe50f70f9022740503452ab51492b1f7477eda427b423a931b26386c56e427863d46b199ffa08c529fa5721f68e914f6ea6a8ae6aecbf737471ebd83dba9a7cd897566204e2bae63e34e7032510296920d7e7a7ccf0febe7a833696a4b6741885e9b940c61dd8d4438547415310b15cf18dc1990078c708beac332a8e08146a6958ea6f43fd0c2c8e999aa4fdf1e77efde54fd65c67a3f07daf5f6044960a0b6dd841ff8b8a592c7b109342c735c2a0e37b30b8baa5c7701ebc7a8f820c0227ca5003f36ee68f7b28981c27332039dd6a494f0cd02bdd28f683eca1b032afc09dd0cd856cbc1a35e74d40c2453dfe242c86a7a60bcbddb17966c7dba769eabd1c167b7e81978f9128bac26a28d77213079cb56c095a7c060de0e775ca8ac8e6ca94d19c6162e44f7a8f0149d31d3463d01b61a1463a9de3d8ab740040a76e05b376428862987595b87cea694fe920a067e816b4f29a3a22450140f135d719a971b81fc1916980a55ddf8d98730573635a07085c4e77c7e1cdbb685426ee462cc3083a3f5a3b917c06f9a96f9f7bd81aca49bef95b92806c42d0912013142b22a7bad7212114691f1dc7264c67e7634f5d795c9753062e306c06bc103aa01c10d1f5dd4cd59f6532cb723e3a026", "4a25254ccc444ec61c2baceb2ee3977a6332449a3a53add231abf3d18bb3293d", "653d07c907946ac3020ebde1b4f610210c30c450e4271265a05d6ece446df43903fb794375275d23d158d5646bc463a8b738bc7938f60dfb155bef4d461eec29", "7bf4127d22cc573587512ff81e553e3c98235f51c7237e9e761a08f2e1e80d042698fc3b1d0318f1fdca8e41a316d6af3ac0c40ce19947a2bafe804d466ed079827fc14191ebb599178749e9c406af26"] +] + + +@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin") +class TestZcashNoteEcryption(unittest.TestCase): + def test_zcash_note_encryption(self): + for tv in zcash_parse(ZCASH_TEST_VECTORS): + recipient = Address(tv.default_d, Point(tv.default_pk_d)) + note = Note(recipient, tv.v, Fp(tv.rho), tv.rseed) + enc_note = encrypt_note( + note, + tv.memo, + Point(tv.cv_net), + Point(tv.cmx), # name error, this should by `cm` + tv.ovk, + ActionShieldingRng(32*b"\x00"), + ) + self.assertEqual(enc_note.epk_bytes, tv.ephemeral_key) + self.assertEqual(enc_note.enc_ciphertext, tv.c_enc) + self.assertEqual(enc_note.out_ciphertext, tv.c_out) + + +if __name__ == '__main__': + unittest.main() diff --git a/core/tests/test_apps.zcash.orchard.crypto.sinsemilla.py b/core/tests/test_apps.zcash.orchard.crypto.sinsemilla.py new file mode 100644 index 00000000000..db836ed80f9 --- /dev/null +++ b/core/tests/test_apps.zcash.orchard.crypto.sinsemilla.py @@ -0,0 +1,68 @@ +from common import * + +from apps.zcash.orchard.crypto.sinsemilla import Sinsemilla + +@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin") +class TestZcashOrchardSinsemilla(unittest.TestCase): + def test_zcash_sinsemilla(self): + TEST_VECTORS = [ + { + "msg": [], + "personal": "", + "hash_to_point": unhexlify("cbe12c93bf02e95257ae18fbb008611c654020094e905f256ce0a35454bb1c1c"), + }, + { + "msg": [], + "personal": "test", + "hash_to_point": unhexlify("4fbdd41a50e0fa8f826fb264f0f4b1d89bb558ef1a4beb02a0ca509154c1653f"), + }, + { + "msg": [0], + "personal": "", + "hash_to_point": unhexlify("19e3e9745b35f6386117d428f2868f3340f84eb6d5a12d267a4e1596fa2b9ba5"), + }, + { + "msg": [1], + "personal": "", + "hash_to_point": unhexlify("2c61bcf32756be2a3f906ee517841efebc2870291d2c48da341522da4823b18a"), + }, + { + "msg": [0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1], + "personal": "sinsemilla-trezor-test-4", + "hash_to_point": unhexlify("79b1ecf8ee86009c7f80b2b3b2e576857ee6f659289629e2335cb722ef23861f"), + }, + { + "msg": [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0], + "personal": "sinsemilla-trezor-test-5", + "hash_to_point": unhexlify("34f878ea0b1c2423c2a40ca331d505896d108ecc11938a497093d17236938397"), + }, + { + "msg": [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1], + "personal": "sinsemilla-trezor-test-6", + "hash_to_point": unhexlify("20fc91bd375da6493d68f4ce97e8b0375b2a8b4785ddf49039deb455af807d34"), + }, + { + "msg": [0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0], + "personal": "sinsemilla-trezor-test-7", + "hash_to_point": unhexlify("3fd1e74811669a3243e66df938d0e59c21424ea08a7f344f923b84b5fbb75915"), + }, + { + "msg": [0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1], + "personal": "sinsemilla-trezor-test-8", + "hash_to_point": unhexlify("9c953612f6064be1f2127e15bd18be4005fe6bc65f7bcbdc3c61b099b6949297"), + }, + { + "msg": [1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1], + "personal": "sinsemilla-trezor-test-9", + "hash_to_point": unhexlify("39550273c01e1d6fa2eadf78a813f54a22c91dd9288ab658b2c2408e4be72b2e"), + }, + ] + for tv in TEST_VECTORS: + h = Sinsemilla.personalized_by(tv["personal"]) + h.update(tv["msg"]) + point = h.finalize() + self.assertEqual(point.to_bytes(), tv["hash_to_point"]) + + +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 index 2e39582d906..19fa0686fda 100644 --- a/core/tests/test_apps.zcash.unified_addresses.py +++ b/core/tests/test_apps.zcash.unified_addresses.py @@ -8,54 +8,47 @@ SAPLING = unified_addresses.Typecode.SAPLING ORCHARD = unified_addresses.Typecode.ORCHARD -TESTVECTORS = [ +null = None +ZCASH_TEST_VECTORS = [ ["From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/unified_address.py"], ["p2pkh_bytes, p2sh_bytes, sapling_raw_addr, orchard_raw_addr, unknown_typecode, unknown_bytes, unified_addr, root_seed, account, diversifier_index"], - ["e6cabf813929132d772d04b03ae85223d03b9be8", None, None, "d4714ee761d1ae823b6972152e20957fefa3f6e3129ea4dfb0a9e98703a63dab929589d6dc51c970f935b3", 65533, "f6ee6921481cdd86b3cc4318d9614fc820905d042bb1ef9ca3f24988c7b3534201cfb1cd8dbf69b8250c18ef41294ca97993db546c1fe0", "753179793677386e336a6d6a73676a39777663656e7238723570366833387679636c686d71307767396b7a70786c7534367a387636346b3567737a72387966777a346a7672796c76766733673633337a30326c756b38356e6d73636b366432736578336e3564376b6e3638687a7a3574763475647439703673793770676c6565756c76676c767832363237646666353771396665703577676478386d3065737832386d307a767578706d7779617a74336a756e3272707177386e75366a326663657167686b353563656436366a73366b366a786e387932787475653866337061716a726b3871366e70746e6e", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 0, 0], - ["7bec9de217c04f7ce1a86f1fb458aa881c8f39e4", None, None, "d8e5ecb4e005c28718e61a5c336a4f369e771ccdb3363f4f7a04b02a966901a4c05da662d5fd75678f7fb4", 65530, None, "75317a35677538783364766b7677636d726a30716b3568727839706361646c3536683834663777647970366e7635337233643563636365646563686d77393835746765357733633272353639716137326c676775753578727178683739616a7a63376b716d65733230706b747a71726a6c707835367168676d716d3536686e39777432686379787064616d616b", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 1, 0], - ["aa6d43480fd9d91375ce6c4a020706361bd296de", None, "88533c398a49c2513dc85162bf220abaf47dc983f14e908ddaaa7322dba16531bc62efe750fe575c8d149b", None, 65530, None, "7531343367706a3772643934766d39356d7a73757537746a74716161677934706d6678386c6b77656d70786a7463777a33357a746361383530796e6c7a323932307477617a6171703270367168787878337a357178616b6e73716372676c7578716a337070757367776635757963686c61677938376b376874613768773965793336776d7930367065776c6470", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 2, 0], - [None, "a8d7551db5fd9313e8c7203d996af7d477083756", "52fd6aedefbf401633c2e4532515ebcf95bcc2b4b8e4d676dfad7e17925c6dfb8671e52544dc2ca075e261", None, 65534, None, "753178797970646a307a7978637466666b6878796d766a6e6b376e383371666c376e7365356c3071726b346e3266376465376c3733727a79787970347463727975356d6b7875617a6c646e633279306479747a7567797a79636739373034616a66786173376b63757761776d706877776e383839743938743735376579716667346a766566746b687672337167", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 3, 0], - [None, "f44ab023752cb5b406ed8985e18130ab33362697", None, "165082de84f2ad7204426ffafd6b6c7de9cab6d25c13846a1786715268c415948db788f4a5e0daa03d699e", 65533, None, "7531706a336c72656d6e7175737368393878667161336a66647077303872726b35377330346b6c32366865707a7133746a72736e78653574367371716567653976716d776c63366c786373746e6333306e3575357232776b6b7a687039367a3564306a797530716137746b686378366663386a35396b616b387a35636570363261716d61336d36343566683863", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 4, 0], - [None, None, None, "ea9df83fbee07d6f7895ebb2ea41ec7c4ba682b863e069b4a438e31c9571c83126c305d75456412aeaef1b", 65531, None, "753132787567643930666c726b646b6575336e6c6e6e337565736b793533707175356d323479366170786d38386d34387637333734636c7335367a7039336e61796c617864636866307161796678747267653034376d393533717a3376326772346c74737232736b3372", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 5, 0], - [None, None, None, "3c40246912b6efefab9a55244ac2c174e1a9f8c0bc0fd526933963c6ecb9b84ec8b0f6b40dc858fa23c72b", 65530, None, "75317370757467353667736a763233637435346d7277646c616e7a7665716337747a73356d78786e616135636465676d303368673778363661797079647336356d39327674397561786c3637327375687063367a3768747776657079686b727066757376617a71756539", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 6, 0], - [None, "defa3d5a57efc2e1e9b01a035587d5fb1a38e01d", None, "cc099cc214e56b1192c7b5b17e958c3413e27fefd553380700aca81b24b2918cac951a1a68017fac525a18", 65535, None, "75317667736b636d3939783567687561757668337978713777747037756e366130793663617964736e6e33357032647577707773356873367079676a6877703738326a716e65727a6c6878773370343971666d713237383339716a7472667976686b377964393877396e3064366a6e7336756834666333687364663736366b6e74716e6c6a646b64353667636e", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 7, 0], - [None, None, None, "5f09a9807a56323b263b05df368dc28391b21a64a0e1b40f9a6803b7e68f3905923f35cb01f119b223f493", 65530, None, "75316378636379656d6d3038747964776d743968703273356e6638776a766c757575366c32653861396a666c6c647861736e7a6b6438667665727170636a30786e767261637a71673235356377356e767936783977727566666d703975657a727a72376763783535396b", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 8, 0], - [None, "10acd20b183e31d49f25c9a138f49b1a537edcf0", "9b60ae3d302248b349d601567e3d7795bfb334ea1fd1a7e71402169ebbe14bd2ceaa244ccd6e5aa2245613", "e340636542ece1c81285ed4eab448adbb5a8c0f4d386eeff337e88e6915f6c3ec1b6ea835a88d56612d2bd", 65531, None, "75317a656b68686d686b353478356365356333367274376e63323735676570376e6176326e73783473683061666c6c75703976726835687338367a38736b6a746436646e736c7667736d6174743068386832343763676e666b73646c776c39786d617275797570666c743064716673637830647979656d3266616139776571653378616b397736656672353437636a3832397232746e7974613032687866647873646a6d76397a72356b746b70323066706378656164686672683032616b346136686e7876357336377267717272766670646a7435", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 9, 0], - [None, "af9db6990ed83dd64af3597c04323ea51b0052ad", None, "cdf7fed0d0822fd849cffb20a4d5ee701ad8141e66d81ddfabf87875117c05092240603c546b8dc187cd8c", 65532, None, "753165353471636e30746570796c33307a7a326672677a37713461366d736e326530326e7076326e6666736433683532336d747838643232616a7666767371757235736a7a3876666e6d77327973363730387170386b6139306a3561343330757938763833616c6a63306330357a6a7535347879356e7677336d66686b376e7737366b6b7964796c713466656c", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 10, 0], - [None, None, None, "24fd59f32b2d39dde66e46c39206a31bc04fa5c6847976ea6bbd3163ee14f58f584acc131479ea558d3f84", 65530, None, "75317a38777372686d66366d3967766136766c33737a636b303670393730783577686d36336a666a3266726d6d63396e39756d34796373387975746a37673833387672676832306c667879353279306832367474386e6776643267796370797176396b793032716b6373", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 11, 0], - [None, None, "78d85bd0db639043377987cdd814c6390016964b684016faf1ad4f166c5f72399a5e8d469ec6beb873d55d", None, 65535, None, "75317861686a333570376d7639756c6b3337327333766465687172663438753077646633786c3772787a7270653461307468753864306d396d7961617078376b35767836747a357074636a76637675346472667137753771777a6d667565336b74387376736333736535", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 12, 0], - ["33a6dd87b4d872a4895d345761e4ec423b77928d", None, None, "5178924f7067eac261044ca27ba3cf52f798486973af0795e61587aa1b1ecad333dc520497edc61df88980", 65533, "91e00c7a1d48af046827591e9733a97fa6b679f3dc601d008285edcbdae69ce8fc1be4aac00ff2711ebd931de518856878f73476f21a482ec9378365c8f7393c94e2885315eb4671098b79535e790fe53e29fef2b3766697ac32b4f473f468a008e72389fc03880d780cb07fcfaabe3f1a84b27db59a4a153d882d2b2103596555ed9494c6ac893c49723833ec8926c1", "7531687970706c733364776d616c783373756c746b72397564763237376679716a6478307378716c746638676a6e777976343968743575327270336c6c767632756e796d7330383675616a6b6638393837636175616a7136383670356638687276393474616336663078796637796d7a3636747279366b7936726179336d6a633567786661683030637370766b3564676d67736e3737663274336775763270307861366b6c6138717479376d6b6e6b6d337a68303932306c77733633326166743071686b3532363579736c337067323237747866373461736d7075656e326c746533616a6330667a376b34736878797a656d6e7035773770336b746c6874643030366d6b61787979306d746637646a73646175397a666b657332616e387661687a6737647173677938326330707830396d39683061657a736e7936786c66706767667268656d7661786a3578747871356a6e67763076306167726c3073757079676639636574656a35323779727a7a6574386471747164616771", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 13, 0], - ["a56c057ef71dab58aa90e47025695c5faaea5123", None, "a75a6de421d2ad1ee8f4b25e398adda9c0aaa6ab1f2518981a9ddb1de6a3957d77842332d6289dbe94e832", "b208c9235c8d40e49b76100b2d010f3783f12c66e7d3beb117b2c96321b7f6562adb4efc144e39d909e728", 65533, None, "7531646670723876647335683361756e79657a7a7877726d38756461353273743837733876726c676732746730357430713070783336686368783974676b786b6c77747370753332786a6135617271336b7470326e387a613470773779776a30676d68713372776539353072386b3973756e736a76773734743538716c3333347065673464766b616c6b746d6e676e716b7077723332353837653779747932376e6d673636747371377976723779343639776570366b7077346a3530786e6c6d78306a78786737766c6735796c6671387566657664", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 14, 0], - [None, None, None, "9e5445d6cd3cb9f98b0df1062bda47adffd5a66c0c2c483c8bf15c3176d755914a3576496b5c35fee28a88", 65531, None, "75316a676c686a326d617936646674777a39753271796e786a717a6e75743637343768617375306d646d6c63303266636173756178756764797a776a326c38346d6a3966677a6a3779306b396663706a373336736c6d6a38676b37377567386c6c61766367326c666d6d", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 15, 0], - ["b02aec10f6fa02a08667bf9b924c3d0574a1334f", None, None, "2598d84dffb34f5908b90732490f3881399150d4c694fce9bf30d1560b2c56f09829fe123b9add20e5d71c", 65534, None, "7531397163617a647761793438707566366a77616a78307732386d307871756d746d6e6435677974796c6c6e79676867396c76393978356d3872387439673566396a307a30786e34787a6d6e7866747a3772746633756164786b79367178706e6b7438666b66686c78386b63396d6e72646c6e7874733536786378656a7a6472776c65787a7637377876797634", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 16, 0], - [None, None, "d3a803803feee7a032a24adfaa8f6a94cecb9671c1333d0d5d1a3d79d82bc310727c665364d71022559c50", "7c98b8f613f9ff02746bea2a167cfd1bd3a1862af9631bf61d9d604e0824e2cb8467a1e549db87a76e7a8a", 65535, None, "75316136346c303971727378756c666a7a6e6d366b326735333575737968746166386564363076346a726a6d6b77766b757834743770647963336e6b7a7265666467746e77383432306c6a3873686d30356a6139667878676e68726139326e6873713536677838633270757a33666b6b676e726b7166357975716664746637743672616e343767646366357676646661637a7766337575793466797368336d7a7538686435746b6c30356d76726765396e38", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 17, 0], - ["26c061d67beb8bad48c6b4774a156551e30e4fe2", None, None, "a80405d5568ab8ab8f8546163d951ab297fd5e6f43e7fcebcb664feacfab5afd80aaf7f354c07a9901788c", 65535, None, "7531787a757764386163686667776d336577793976326d6a3537373268726b6e6d6578777a6339346d7a6133356d78363863656e767877727a3973396670306e39767a753872756a357a71666d6d376c65387775366c363275346c6d30376e75717865656d383733677838366a766e776c70787379636c397576366b786b72686d30726c677037307830357366", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 18, 0], - [None, None, "8660070e3757ff6507060791fd694f6a631b8495a2b74ffa39236cf653caea5575b86af3200b010e513bab", "63b7b706d991169986aee56133f0a50b2a0c8225fba6dae95176007b1f023a1e97c1aa366e99bf970fda82", 65534, None, "7531766736326d676a64646e6c763577366c646b793278653063387465746d633832747539766c7a7a6b75796e783439666e75716a76786a743564676e33636d3874356e38357a6371356c6a727467377a6d77686b3730683672646d636c6637736378786e67756b35666c76663261707037367875393037636d6a796c787673656e3235786539763776336b727378613975793076326a6a7133376b6834796d6c61666e3870657671616c716134646d3637", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 19, 5] + ["e6cabf813929132d772d04b03ae85223d03b9be8", null, null, "d4714ee761d1ae823b6972152e20957fefa3f6e3129ea4dfb0a9e98703a63dab929589d6dc51c970f935b3", 65533, "f6ee6921481cdd86b3cc4318d9614fc820905d042bb1ef9ca3f24988c7b3534201cfb1cd8dbf69b8250c18ef41294ca97993db546c1fe0", "753179793677386e336a6d6a73676a39777663656e7238723570366833387679636c686d71307767396b7a70786c7534367a387636346b3567737a72387966777a346a7672796c76766733673633337a30326c756b38356e6d73636b366432736578336e3564376b6e3638687a7a3574763475647439703673793770676c6565756c76676c767832363237646666353771396665703577676478386d3065737832386d307a767578706d7779617a74336a756e3272707177386e75366a326663657167686b353563656436366a73366b366a786e387932787475653866337061716a726b3871366e70746e6e", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 0, 0], + ["7bec9de217c04f7ce1a86f1fb458aa881c8f39e4", null, null, "d8e5ecb4e005c28718e61a5c336a4f369e771ccdb3363f4f7a04b02a966901a4c05da662d5fd75678f7fb4", 65530, null, "75317a35677538783364766b7677636d726a30716b3568727839706361646c3536683834663777647970366e7635337233643563636365646563686d77393835746765357733633272353639716137326c676775753578727178683739616a7a63376b716d65733230706b747a71726a6c707835367168676d716d3536686e39777432686379787064616d616b", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 1, 0], + ["aa6d43480fd9d91375ce6c4a020706361bd296de", null, "88533c398a49c2513dc85162bf220abaf47dc983f14e908ddaaa7322dba16531bc62efe750fe575c8d149b", null, 65530, null, "7531343367706a3772643934766d39356d7a73757537746a74716161677934706d6678386c6b77656d70786a7463777a33357a746361383530796e6c7a323932307477617a6171703270367168787878337a357178616b6e73716372676c7578716a337070757367776635757963686c61677938376b376874613768773965793336776d7930367065776c6470", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 2, 0], + [null, "a8d7551db5fd9313e8c7203d996af7d477083756", "52fd6aedefbf401633c2e4532515ebcf95bcc2b4b8e4d676dfad7e17925c6dfb8671e52544dc2ca075e261", null, 65534, null, "753178797970646a307a7978637466666b6878796d766a6e6b376e383371666c376e7365356c3071726b346e3266376465376c3733727a79787970347463727975356d6b7875617a6c646e633279306479747a7567797a79636739373034616a66786173376b63757761776d706877776e383839743938743735376579716667346a766566746b687672337167", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 3, 0], + [null, "f44ab023752cb5b406ed8985e18130ab33362697", null, "165082de84f2ad7204426ffafd6b6c7de9cab6d25c13846a1786715268c415948db788f4a5e0daa03d699e", 65533, null, "7531706a336c72656d6e7175737368393878667161336a66647077303872726b35377330346b6c32366865707a7133746a72736e78653574367371716567653976716d776c63366c786373746e6333306e3575357232776b6b7a687039367a3564306a797530716137746b686378366663386a35396b616b387a35636570363261716d61336d36343566683863", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 4, 0], + [null, null, null, "ea9df83fbee07d6f7895ebb2ea41ec7c4ba682b863e069b4a438e31c9571c83126c305d75456412aeaef1b", 65531, null, "753132787567643930666c726b646b6575336e6c6e6e337565736b793533707175356d323479366170786d38386d34387637333734636c7335367a7039336e61796c617864636866307161796678747267653034376d393533717a3376326772346c74737232736b3372", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 5, 0], + [null, null, null, "3c40246912b6efefab9a55244ac2c174e1a9f8c0bc0fd526933963c6ecb9b84ec8b0f6b40dc858fa23c72b", 65530, null, "75317370757467353667736a763233637435346d7277646c616e7a7665716337747a73356d78786e616135636465676d303368673778363661797079647336356d39327674397561786c3637327375687063367a3768747776657079686b727066757376617a71756539", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 6, 0], + [null, "defa3d5a57efc2e1e9b01a035587d5fb1a38e01d", null, "cc099cc214e56b1192c7b5b17e958c3413e27fefd553380700aca81b24b2918cac951a1a68017fac525a18", 65535, null, "75317667736b636d3939783567687561757668337978713777747037756e366130793663617964736e6e33357032647577707773356873367079676a6877703738326a716e65727a6c6878773370343971666d713237383339716a7472667976686b377964393877396e3064366a6e7336756834666333687364663736366b6e74716e6c6a646b64353667636e", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 7, 0], + [null, null, null, "5f09a9807a56323b263b05df368dc28391b21a64a0e1b40f9a6803b7e68f3905923f35cb01f119b223f493", 65530, null, "75316378636379656d6d3038747964776d743968703273356e6638776a766c757575366c32653861396a666c6c647861736e7a6b6438667665727170636a30786e767261637a71673235356377356e767936783977727566666d703975657a727a72376763783535396b", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 8, 0], + [null, "10acd20b183e31d49f25c9a138f49b1a537edcf0", "9b60ae3d302248b349d601567e3d7795bfb334ea1fd1a7e71402169ebbe14bd2ceaa244ccd6e5aa2245613", "e340636542ece1c81285ed4eab448adbb5a8c0f4d386eeff337e88e6915f6c3ec1b6ea835a88d56612d2bd", 65531, null, "75317a656b68686d686b353478356365356333367274376e63323735676570376e6176326e73783473683061666c6c75703976726835687338367a38736b6a746436646e736c7667736d6174743068386832343763676e666b73646c776c39786d617275797570666c743064716673637830647979656d3266616139776571653378616b397736656672353437636a3832397232746e7974613032687866647873646a6d76397a72356b746b70323066706378656164686672683032616b346136686e7876357336377267717272766670646a7435", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 9, 0], + [null, "af9db6990ed83dd64af3597c04323ea51b0052ad", null, "cdf7fed0d0822fd849cffb20a4d5ee701ad8141e66d81ddfabf87875117c05092240603c546b8dc187cd8c", 65532, null, "753165353471636e30746570796c33307a7a326672677a37713461366d736e326530326e7076326e6666736433683532336d747838643232616a7666767371757235736a7a3876666e6d77327973363730387170386b6139306a3561343330757938763833616c6a63306330357a6a7535347879356e7677336d66686b376e7737366b6b7964796c713466656c", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 10, 0], + [null, null, null, "24fd59f32b2d39dde66e46c39206a31bc04fa5c6847976ea6bbd3163ee14f58f584acc131479ea558d3f84", 65530, null, "75317a38777372686d66366d3967766136766c33737a636b303670393730783577686d36336a666a3266726d6d63396e39756d34796373387975746a37673833387672676832306c667879353279306832367474386e6776643267796370797176396b793032716b6373", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 11, 0], + [null, null, "78d85bd0db639043377987cdd814c6390016964b684016faf1ad4f166c5f72399a5e8d469ec6beb873d55d", null, 65535, null, "75317861686a333570376d7639756c6b3337327333766465687172663438753077646633786c3772787a7270653461307468753864306d396d7961617078376b35767836747a357074636a76637675346472667137753771777a6d667565336b74387376736333736535", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 12, 0], + ["33a6dd87b4d872a4895d345761e4ec423b77928d", null, null, "5178924f7067eac261044ca27ba3cf52f798486973af0795e61587aa1b1ecad333dc520497edc61df88980", 65533, "91e00c7a1d48af046827591e9733a97fa6b679f3dc601d008285edcbdae69ce8fc1be4aac00ff2711ebd931de518856878f73476f21a482ec9378365c8f7393c94e2885315eb4671098b79535e790fe53e29fef2b3766697ac32b4f473f468a008e72389fc03880d780cb07fcfaabe3f1a84b27db59a4a153d882d2b2103596555ed9494c6ac893c49723833ec8926c1", "7531687970706c733364776d616c783373756c746b72397564763237376679716a6478307378716c746638676a6e777976343968743575327270336c6c767632756e796d7330383675616a6b6638393837636175616a7136383670356638687276393474616336663078796637796d7a3636747279366b7936726179336d6a633567786661683030637370766b3564676d67736e3737663274336775763270307861366b6c6138717479376d6b6e6b6d337a68303932306c77733633326166743071686b3532363579736c337067323237747866373461736d7075656e326c746533616a6330667a376b34736878797a656d6e7035773770336b746c6874643030366d6b61787979306d746637646a73646175397a666b657332616e387661687a6737647173677938326330707830396d39683061657a736e7936786c66706767667268656d7661786a3578747871356a6e67763076306167726c3073757079676639636574656a35323779727a7a6574386471747164616771", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 13, 0], + ["a56c057ef71dab58aa90e47025695c5faaea5123", null, "a75a6de421d2ad1ee8f4b25e398adda9c0aaa6ab1f2518981a9ddb1de6a3957d77842332d6289dbe94e832", "b208c9235c8d40e49b76100b2d010f3783f12c66e7d3beb117b2c96321b7f6562adb4efc144e39d909e728", 65533, null, "7531646670723876647335683361756e79657a7a7877726d38756461353273743837733876726c676732746730357430713070783336686368783974676b786b6c77747370753332786a6135617271336b7470326e387a613470773779776a30676d68713372776539353072386b3973756e736a76773734743538716c3333347065673464766b616c6b746d6e676e716b7077723332353837653779747932376e6d673636747371377976723779343639776570366b7077346a3530786e6c6d78306a78786737766c6735796c6671387566657664", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 14, 0], + [null, null, null, "9e5445d6cd3cb9f98b0df1062bda47adffd5a66c0c2c483c8bf15c3176d755914a3576496b5c35fee28a88", 65531, null, "75316a676c686a326d617936646674777a39753271796e786a717a6e75743637343768617375306d646d6c63303266636173756178756764797a776a326c38346d6a3966677a6a3779306b396663706a373336736c6d6a38676b37377567386c6c61766367326c666d6d", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 15, 0], + ["b02aec10f6fa02a08667bf9b924c3d0574a1334f", null, null, "2598d84dffb34f5908b90732490f3881399150d4c694fce9bf30d1560b2c56f09829fe123b9add20e5d71c", 65534, null, "7531397163617a647761793438707566366a77616a78307732386d307871756d746d6e6435677974796c6c6e79676867396c76393978356d3872387439673566396a307a30786e34787a6d6e7866747a3772746633756164786b79367178706e6b7438666b66686c78386b63396d6e72646c6e7874733536786378656a7a6472776c65787a7637377876797634", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 16, 0], + [null, null, "d3a803803feee7a032a24adfaa8f6a94cecb9671c1333d0d5d1a3d79d82bc310727c665364d71022559c50", "7c98b8f613f9ff02746bea2a167cfd1bd3a1862af9631bf61d9d604e0824e2cb8467a1e549db87a76e7a8a", 65535, null, "75316136346c303971727378756c666a7a6e6d366b326735333575737968746166386564363076346a726a6d6b77766b757834743770647963336e6b7a7265666467746e77383432306c6a3873686d30356a6139667878676e68726139326e6873713536677838633270757a33666b6b676e726b7166357975716664746637743672616e343767646366357676646661637a7766337575793466797368336d7a7538686435746b6c30356d76726765396e38", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 17, 0], + ["26c061d67beb8bad48c6b4774a156551e30e4fe2", null, null, "a80405d5568ab8ab8f8546163d951ab297fd5e6f43e7fcebcb664feacfab5afd80aaf7f354c07a9901788c", 65535, null, "7531787a757764386163686667776d336577793976326d6a3537373268726b6e6d6578777a6339346d7a6133356d78363863656e767877727a3973396670306e39767a753872756a357a71666d6d376c65387775366c363275346c6d30376e75717865656d383733677838366a766e776c70787379636c397576366b786b72686d30726c677037307830357366", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 18, 0], + [null, null, "8660070e3757ff6507060791fd694f6a631b8495a2b74ffa39236cf653caea5575b86af3200b010e513bab", "63b7b706d991169986aee56133f0a50b2a0c8225fba6dae95176007b1f023a1e97c1aa366e99bf970fda82", 65534, null, "7531766736326d676a64646e6c763577366c646b793278653063387465746d633832747539766c7a7a6b75796e783439666e75716a76786a743564676e33636d3874356e38357a6371356c6a727467377a6d77686b3730683672646d636c6637736378786e67756b35666c76663261707037367875393037636d6a796c787673656e3235786539763776336b727378613975793076326a6a7133376b6834796d6c61666e3870657671616c716134646d3637", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 19, 5] ] -class ZcashTestVector: - def __init__(self, inner): - self.inner = inner - def __getattr__(self, name): - index = TESTVECTORS[1][0].split(", ").index(name) - return self.inner[index] +def get_receivers(tv): + receivers = dict() + if tv.p2pkh_bytes is not None: + receivers[P2PKH] = tv.p2pkh_bytes + if tv.p2sh_bytes is not None: + receivers[P2SH] = tv.p2sh_bytes + if tv.sapling_raw_addr is not None: + receivers[SAPLING] = tv.sapling_raw_addr + if tv.orchard_raw_addr is not None: + receivers[ORCHARD] = tv.orchard_raw_addr + if tv.unknown_bytes is not None: + receivers[tv.unknown_typecode] = tv.unknown_bytes - -def get_receivers(tv: ZcashTestVector): - receivers = dict() - if tv.p2pkh_bytes is not None: - receivers[P2PKH] = unhexlify(tv.p2pkh_bytes) - if tv.p2sh_bytes is not None: - receivers[P2SH] = unhexlify(tv.p2sh_bytes) - if tv.sapling_raw_addr is not None: - receivers[SAPLING] = unhexlify(tv.sapling_raw_addr) - if tv.orchard_raw_addr is not None: - receivers[ORCHARD] = unhexlify(tv.orchard_raw_addr) - if tv.unknown_bytes is not None: - receivers[tv.unknown_typecode] = unhexlify(tv.unknown_bytes) - - return receivers + return receivers COIN = coininfo.by_name("Zcash") @@ -63,18 +56,18 @@ def get_receivers(tv: ZcashTestVector): @unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin") class TestZcashAddress(unittest.TestCase): - def test_encode_unified(self): - for tv in map(ZcashTestVector, TESTVECTORS[2:]): - receivers = get_receivers(tv) - ua = unified_addresses.encode(receivers, COIN) - self.assertEqual(ua, unhexlify(tv.unified_addr).decode()) + def test_zcash_encode_unified_address(self): + for tv in zcash_parse(ZCASH_TEST_VECTORS): + receivers = get_receivers(tv) + ua = unified_addresses.encode(receivers, COIN) + self.assertEqual(ua, tv.unified_addr.decode()) - def test_decode_unified(self): - for tv in map(ZcashTestVector, TESTVECTORS[2:]): - address = unhexlify(tv.unified_addr).decode() - receivers = unified_addresses.decode(address, COIN) - self.assertEqual(receivers, get_receivers(tv)) + def test_zcash_decode_unified_address(self): + for tv in zcash_parse(ZCASH_TEST_VECTORS): + address = tv.unified_addr.decode() + receivers = unified_addresses.decode(address, COIN) + self.assertEqual(receivers, get_receivers(tv)) if __name__ == '__main__': - unittest.main() + unittest.main() diff --git a/core/tests/test_apps.zcash.zip244.py b/core/tests/test_apps.zcash.zip244.py index 4ab474cff99..aeb278d2605 100644 --- a/core/tests/test_apps.zcash.zip244.py +++ b/core/tests/test_apps.zcash.zip244.py @@ -4,14 +4,126 @@ from apps.zcash.hasher import ZcashHasher from apps.bitcoin.common import SigHashType from apps.common.coininfo import by_name +from apps.zcash.orchard.crypto.builder import Action +from apps.zcash.orchard.crypto.note_encryption import TransmittedNoteCiphertext ZCASH_COININFO = by_name("Zcash") +null = None +ZCASH_TEST_VECTORS = [ + ["From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/zip_0244.py"], + ["tx, txid, auth_digest, amounts, script_pubkeys, transparent_input, sighash_shielded, sighash_all, sighash_none, sighash_single, sighash_all_anyone, sighash_none_anyone, sighash_single_anyone"], + ["050000800a27a726b4d0d6c27a8f739a2d6f2c0201e152a8049e294c4d6e66b164939daffa2ef6ee6921481cdd86b3cc4318d9614fc820905d0453516aaca3f2498800019f33bf3a109bdd1b232b47b1646d91e1296634ebde5ccad57288b5b2228186e54b6968912a6381ce3dc166d56a1d62f5a8d7551db5fd9313e8c7203d996af7d41a38e01d94903d3c3e0ad3360c1d3710acd20b183e31d49f25c9a138f49b1a5301466b3da612149df5eda0f14f2efc5c6ac03884428a315dc91f8d7b492ebc57e475a4a6f26572504b192232ecb9f0c02411e52596bc5e90457e745939ffedbd121e37ec1e9dddc31b06dc9576a1738ef73e6ba71648913dbf75a779fdd488d83f857deecc40a98d5f2935395ee4762dd21afdbb5d47fa9a6dd984d567db2857b927b7fae2db587105415d4642789d38f50b8dbcc129cab3d17d19f3355bcf73cecb8cb8a5da01307152f13936a270572670dc82d39026c6cb4cd4b0f7f5aa2a4f5a5341ec5dd715406f2fdd2afa733f5f641c8c21862a1bafce2609d9eecfa158cfb5cd79f88008e315dc7d8388e76c1782fd2795d18a763624c25fa959cc97489ce75745824b77868c53239cfbdf73caec65604037314faaceb56218c6bd30f8374ac13386793f21a9fb80ad03bc0cda4a44946c00e1b1a1df0e5b87b5bece477a709649e950060591394812951e1fe3895b8cc3d14d2cf6556df6ed4b4ddd3d9a69f53357d7767f4f5ccbdbc596631277f8fecd08cb056b95e3025b9792fff7f244fc716269b926d62e9596fa825c6bf21aff9e68625a192440ea06828123d97884806f15fa08da52754a1095e3ff1abd5ce4fddfccfc3a6128aef784a64610a89d1a7099216d0814d3a2d452431c32d411ac1cce82ad0229407bbc48985675e3f874a4533f1d63a84dfa3e0f460fe2f57e34fbc75423c3737f5b2a0615f5722db041a3ef66fa483afd3c2e19e59444a64add6df1d963f5dd5b5010d3d025f0287c4cf19c75f33d51ddddba5d657b43ee8da645443814cc7329f3e9b4e54c236c29af3923101756d9fa4bd0f7d2ddaacb6b0f86a2658e0a07a05ac5b950051cd24c47a88d13d659ba2a46ca1830816d09cd7646f76f716abec5de07fe9b523410806ea6f288f8736c23357c85f45791e1708029d9824d90704607f387a03e49bf9836574431345a7877efaa8a08e73081ef8d62cb780ab6883a50a0d470190dfba10a857f82842d3825b3d6da0573d316eb160dc0b716c48fbd467f75b780149ae8808f4e68f50c0536acddf6f1aeab016b6bc1a51ed44cfab70000c7b3534201cfb1cd8dbf69b8250c18ef41294ca97993db546c1fe01f7e9c8e367edcf04be34a9851a7af9db6990ed83dd64af3597c04323ea51b0052ad8084a8b9da948d320dadd64f5431e61ddf658d24ae67c22c8d1309131fc00fe7f235734276d38d47f1e191e00c7a1d48af046827591e9733a97fa6b679f3dc601d008285edcbdae69ce8fc1be4aac00ff2711ebd931de518856878f73476f21a482ec9378365c8f7393c94e2885315eb4671098b79535e790fe53e29fef2b3766697ac32b4f473f468a008e72389fc03880d780cb07fcfaabe3f1a84b27db59a4a153d1070689f2ccf975b2b176e1c69dbe381340ef1f98fdc4b453abda3a2bfac3069ba7f1cc50a81c2520e412fab4e5d397ecf739f280d5b684533d5d29cfe7e7302ec144b4e553acfd670f77e755fc88e0677e31ba459b44e307768958fe3789d41c2b1ff434cb30e15914f01bc6bc2307b488d2556d7b7380ea4ffd712f6b02fe806b94569cd4059f396bf29b99d0a40e5e1711ca944f72d436a102fca4b97693da0b086fe9d2e7162470d02e0f05d4bec9512bfb3f38327296efaa74328b118c27402c70c3a90b49ad4bbc68e37c0aa7d9b3fe17799d73b841e751713a02943905aae0803fd69442eb7681ec2a05600054e92eed555028f21b6a155268a2dd664052528a5f8ed028f59af985ad1315c2e25aeb9d7f134e4bf478642ab96b15d3b3e13ce2387ac84dc0819e81260e11d392a5f06db8b5633de281a0e9c958c24060297f608af1dc51616562b1ffff6e2a28bab1f7772713a0a4b56fe47fb5a7b73aeee5345566ecf3e95e825f92eb469eb5d69164206a0ea1ce73bfb2a942e73703214d270d80534389b1a1e2bba67481eb3667d6d38254ac4b44559b4708cdd12898972a895bf0fb055cf1fb9b73029d6bfb27da2b5294f5cb354a894322848cc3d35b9554a5f62b44a7dcb25406e5ba07882cb6473714e77a051a7dcd29fea0a943785b325cdab95404fc7aed70525cddb41872cfcc214b13232edc78609753dbff930eb0dc156612b9cb434bc4b693392deb87c530435312edcedc6a961133338d786c4a3e103f60110a16b1337129704bf4754ff6ba9fbe65951e610620f71cda8fc877625f2c5bb04cbe1228b1e886f4050afd8fe94e97d2e9e85c6bb748c0042d3249abb1342bb0eebf62058bf3de080d94611a3750915b5dc6c0b3899d41222bace760ee9c8818ded599e34c56d7372af1eb86852f2a732104bdb750739de6c2c6e0f9eb7cb17f1942bfc9f4fd6ebb6b4cdd4da2bca26fac4578e9f543405acc7d86ff59158bd0cba3aef6f4a8472d144d99f8b8d1dedaa9077d4f01d4bb27bbe31d88fbefac3dcd4797563a26b1d61fcd9a464ab21ed550fe6fa09695ba0b2f10eea6468cc6e20a66f826e3d14c5006f0563887f5e1289be1b2004caca8d3f34d6e84bf59c1e04619a7c23a996941d889e4622a9b9b1d59d5e319094318cd405ba27b7e2c084762d31453ec4549a4d97729d033460fcf89d6494f2ffd789e98082ea5ce9534b3acd60fe49e37e4f666931677319ed89f85588741b3128901a93bd78e4be0225a9e2692c77c969ed0176bdf9555948cbd5a332d045de6ba6bf4490adfe7444cd467a09075417fcc0062e49f008c51ad4227439c1b4476ccd8e97862dab7be1e8d399c05ef27c6e22ee273e15786e394c8f1be31682a30147963ac8da8d41d804258426a3f70289b8ad19d8de13be4eebe3bd4c8a6f55d6e0c373d456851879f5fbc282db9e134806bff71e11bc33ab75dd6ca067fb73a043b646a7cf39cab4928386786d2f24141ee120fdc34d6764eafc66880ee0204f53cc1167ed20b43a52dea3ca7cff8ef35cd8e6d7c111a68ef44bcd0c1513ad47ca61c659cc5d325b440f6b9f59aff66879bb6688fdb462af43582b983f92b5698b87db46e4b02dd8e81eca555a44f2f1aef11d88a0bcee76af9ad3f9c46a67062e1a9ca7ea5c014384af07219c7c0ee7fc7bfc7933d174650f46b4cc000190c19b44c57ae891aa86646c10a177a8626be064409931c37d9e8bdc433b7d79e08a12f738a8f0dbddfef2f2657ef3e47d1b0fd11e6a13654db2854fcbff49aa0dadafec320b6ed2d4b279aee9060c1b221e2eb2f13b0691c4d842406d0ec4282c9526174a09878fe8fdde33a29604e5e5e7b2a025d6650b97dbb52befb59b1d30a57433b0a351474444099daa371046613260cf3354cfcdada663ece824ffd7e44393886a86165ddddf2b4c41773554c86995269408b11e6737a4c447586f69173446d8e48bf84cbc000a807899973eb93c5e819aad669413f8387933ad1584aa35e43f4ecd1e2d0407c0b1b89920ffdfdb9bea51ac95b557af71b89f903f5d9848f14fcbeb1837570f544d6359eb23faf38a0822da36ce426c4a2fbeffeb0a8a2e297a9d19ba15024590e3329d9fa9261f9938a4032dd34606c9cf9f3dd33e576f05cd1dd6811c6298757d77d9e810abdb226afcaa4346a6560f8932b3181fd355d5d391976183f8d99388839632d6354f666d09d3e5629ea19737388613d38a34fd0f6e50ee5a0cc9677177f50028c141378187bd2819403fc534f80076e9380cb4964d3b6b45819d3b8e9caf54f051852d671bf8c1ffde2d1510756418cb4810936aa57e6965d6fb656a760b7f19adf96c173488552193b147ee58858033dac7cd0eb204c06490bbdedf5f7571acb2ebe76acef3f2a01ee987486dfe6c3f0a5e234c127258f97a28fb5d164a8176be946b8097d0e317287f33bf9c16f9a545409ce29b1f4273725fc0df02a04ebae178b3414fb0a82d50deb09fcf4e6ee9d180ff4f56ff3bc1d3601fc2dc90d814c3256f4967d3a8d64c83fea339c51f5a8e5801fbb97835581b602465dee04b5922c2761b54245bec0c9eef2db97d22b2b3556cc969fbb13d06509765a52b3fac54b93f421bf08e18d52ddd52cc1c8ca8adfaccab7e5cc2f4573fbbf8239bb0b8aedbf8dad16282da5c9125dba1c059d0df8abf621078f02d6c4bc86d40845ac1d59710c45f07d585eb48b32fc0167ba256e73ca3b9311c62d1094903570519d4442f0200e6ad11f2452dc9ae85aec01fc56f8cbfda75a7727b75ebbd6bbffb43b63a3b1b871e40feb0db002974a3c3b1a788567231bf6399ff89236981149d423802d2341a3bedb9ddcbac1fe7b6435e1479c72e7089d029e7fbbaf3cf37e9b9a6b776791e4c5e6fda57e8d5f14c8c35a2d270846b9dbe005cda16af4408f3ab06a916eeeb9c9594b70424a4c1d171295b6763b22f47f80b53ccbb904bd68fd65fbd3fbdea1035e98c21a7dba5fe1089f7d1c032f24d36835aa8815266e897ff829403cfac3a715954b9b68958a0111a2c9265633ba2831a2e86b941e569d58d99c1383597fad81193c4c13151f40aedb487b5c04ae3b1ddfbafa26e720099f26d5a7535aee57306fd2c4f30673cd9b698fecf32faf88f62e21c90665859dd26833d21d9bc5452bd19515d3fa5c1e68bc209b9dc2a10ae6b630726a67b33603c691fafc281dd94dc9888a68c4f45155aa7897c045aafd9335be2e0ddcf5f586d7f6b4fe12dad9a17f5db7031", "552c96bd33834ba1a8a3ecd80a2c9cb41187553a3dcfe7928316bb70704b85d0", "12767e5f678567360fb3a1cb9cf858613ffe2263b653c6a370ee1f6820abdc57", [1800841178198868], ["650051"], 0, "88da64b95b56d8296ab1f721eb5be66d0fd478f2b96b93d5dcee8f7a1000b0ff", "2d4ebf4d424238ad0bc2469970347eaf767ff906958e35107fd22c1dc536e459", "683ecaa564002ca5a80bea04370c78855b8d9c9c382309c70b29bdd98d75b066", null, "9c9e75ee15f4eda15d777939529fa3a4f64b935c7d21835f79393e7aa23e2879", "a7dff00a96fd2b41c5808d35e4a6a2aa7b40eeebb6dcf3b9f281eb6c17e43af4", null], + ["050000800a27a726b4d0d6c21fc998c31f4dd208010000000000000000000000000000000000000000000000000000000000000000ffffffff06041f4dd20800ffffffff015058e5754c2104000753ac51530051520001e5849f96bae6f2056f33ab1e6989d7d264adc97855a990103b4d1e6350d5c31a39c3caf69459e462f141be8b39037ffa255ce27e4ad7b566a29620a9f011ab08fb2ad3050652b3f65b8e34526a2a15fc2ddc5b5113e4882c7cca0dd5577be067ba7a175dae4bbe3ef4863d53708915090f47a068e227433f9e49d3aa09e356d8d66d0c0121e91a3c4aa3f27fa1b63396e2b41db908fdab8b18cc7304e94e970568f9421c0dbbbaf84598d972b0534f48a5e52670436aaa776ed2482ad703430201e53443c36dcfd34a0cb6637876105e79bf3bd58ec148cb64970e3223a91f71dfcfd5a04b667fbaf3d4b3b908b9828820dfecdd753750b5f9d2216e56c615272f854464c0ca4b1e85aedd038292c4e1a57744ebba010b9ebfbb011bd6f0b78805025d27f3c17746bae116c15d9f471f0f6288a150647b2afe9df7cccf01f5cde5f04680bbfed87f6cf429fb27ad6babe791766611cf5bc20e48bef119259b9b8a0e39c3df28cb9582ea338601cdc481b32fb82adeebb3dade25d1a3df20c37e712506b5d996c49a9f0f30ddcb91fe9004e1e83294a6c9203d94e8dc2cbb449de4155032604e47997016b304fd437d8235045e255a19b743a0a9f2e336b44cae307bb3987bd3e4e777fbb34c0ab8cc3d67466c0a88dd4ccad18a07a8d1068df5b629e5718d0f6df5c957cf71bb00a5178f175caca944e635c5159f738e2402a2d21aa081e10e456afb00b9f62416c8b9c0f7228f510729e0be3f305313d77f7379dc2af24869c6c74ee4471498861d192f0ff0f508285dab6b6a36ccf7d12256cc76b95503720ac672d08268d2cf7773b6ba2a5f664847bf707f2fc10c98f2f006ec22ccb5a8c8b7c40c7c2d49a6639b9f2ce33c25c04bc461e744dfa536b00d94baddf4f4d14044c695a33881477df124f0fcf206a9fb2e65e304cdbf0c4d2390170c130ab849c2f22b5cdd3921640c8cf1976ae1010b0dfd9cb2543e45f99749cc4d61f2e8aabfe98bd905fa39951b33ea769c45ab9531c57209862ad12fd76ba4807e65417b6cd12fa8ec916f013ebb8706a9a556c762f88500006effeda06c4be24b04846392e9d1e6930eae01fa21fbd700583fb598b92c8f4eb8a61aa6235db60f2841cf3a1c6ab54c67066844711d091eb931a1bd6281aedf2a0e8fab18817202a9be06402ed9cc720c16bfe881e4df4255e87afb7fc62f38116bbe03cd8a3cb11a27d568414782f47b1a44c97c680467694bc9709d32916c97e8006cbb07ba0e4180a3738038c374c4cce8f32959afb25f303f5815c4533124acf9d18940e77522ac5dc4b9570aae8f47b7f57fd8767bea1a24ae7bed65b409e1dd26b8dddd68858d6f5161f073d90636860a9aaee18629b06330a8ee30591debfcef56a026bb28c3b06ec2cfaf5b79ab72694d1d012a7594dd80ae7dfa0c00", "a3cbadd7a58d80a4c2f61809c24a2f086c58ceecaf7af9414c38bdbdc4e46e98", "ad64580ed3a28a3ba41e2d320b5ff2a07fa19db074afc455e92e0f326be08a6a", [], [], null, "a3cbadd7a58d80a4c2f61809c24a2f086c58ceecaf7af9414c38bdbdc4e46e98", null, null, null, null, null, null], + ["050000800a27a726b4d0d6c2c2eb518f68984d02010000000000000000000000000000000000000000000000000000000000000000ffffffff060468984d0200ffffffff00000000", "28d16c3cd78a6b7a50b11a1b8417764c4e63c6e3d8aa289f7e87e39845872764", "332155b1cc23a2571e86e49e06010cd25321dcfcca34ae14e8b3f4f00270d287", [], [], null, "28d16c3cd78a6b7a50b11a1b8417764c4e63c6e3d8aa289f7e87e39845872764", null, null, null, null, null, null], + ["050000800a27a726b4d0d6c25e3dbaf7ae12670d010000000000000000000000000000000000000000000000000000000000000000ffffffff0604ae12670d00ffffffff01516cf4adec75070003656500000000", "6bf4efe77af69b7219475f60a0f792db0263e4e12fa1d9ee1a1b9a68540590da", "993bfca6149975a4013797ead55839a13a0fb152f68372bb0e0fd9499477f903", [], [], null, "6bf4efe77af69b7219475f60a0f792db0263e4e12fa1d9ee1a1b9a68540590da", null, null, null, null, null, null], + ["050000800a27a726b4d0d6c2ff6acc0ffc2e490d03146b9d49dd8c7835f43a37dca0787e3ec9f6605223d5ba7ae0ab9025b73bc03f7fac36c009636363635100635365bca7e54cc1a12d127b57c8138976e791013b015f06a624f521b6ee04ec980893c7e5e01a3362035904ac000053d7445fe2d09130f63511da54832de9136b39f4599f5aa5dfbb45da60cdceab7eefde89be63f3f7c00452006aace1405def0244fd7f99b67d040004630063ac12f6465073e1020009636a5351520065ac65000000", "bc34e5ca581c5c6544aafb3e5865348f71b8aa2a782df8b6bfa1791bf5a73758", "6c36bc25fc4856e5098e5d33033c5b3875217fcb45526118bb058a2dd7b6ea5e", [1848924248978091, 447389782351145, 620151782842275], ["ac0000", "6565", ""], 0, "a960f4baa5f4331f4dadc374566bc047e7c07153b0385a587a2be86a518ab5d3", "8f607656c52d9ad2231120c24faa7b6855ae571bee46e61d76a25cffb2bb4fcb", "fdcf8a04690dc2769d934337fe8b47242a6ed7c9d86d3e01332484d38d71e785", "b3a28ebd7cf37a443a40909f513f081fbe0fd78c67f7c4f01b5fb1159789861a", "62eada92bbc5af09ab7b95834ae6ba413b17cdf196a6f99c541b710ddacfa545", "dff0eb1e03b9c2fe301db9b0183583618893c8474ef565921d5f2832ede3bf49", "1de607f188a40b5c152226d94e44411b6902947d42fbb25fb86c9a4e77a4f35b"], + ["050000800a27a726b4d0d6c223e119f635ef1d05024b216b7023fadc2d25949c90037e71e3e550726d210a2c688342e52440635e9cc14afe100665515151ac53782e9e4a5fa87f0a956f5b85509960285c22627c59483a5a4c28cce4b156e551406a7ee8355656a20043e38ce103bd9a274e288d020000aafe033252c7030005516a63656338eb8b41ca5104000653516365acac000000", "90d2886cb628813371c7d1bd02031b6ca66b42d1db4e118d65f31b2dccb63235", "9c0532e6788fe9e28b3b67f571989be77ae761dfd375c74bbf5db3cafaa1f9a5", [1561051182746413, 1535468271734483], ["656a516aac516a6552", "52"], 1, "24a91d017e691fe7d580e3fc16872d612c14bfcbe5e2725e16d09ec0c1c91305", "2bea7c00cf77fa59a63ab0bd3eb7b10659f8fc9e4c4894bafc37640e8655f562", "e63603eade7dde98ef0ed68de640707df6cc9c337837bcbdcde05a07ff7d873b", "e4b2f98ad7b55fb256a12923303abcaeee38351090e976b0195dd75bdcf94ad9", "61c705d5505f0d5cfb3a28519d791f1457704f7b38392eff885a8207fa683c57", "f35ea9beb0b7ed682d27ef78dde1178d6a55f307c85121cd830258d89d33cdd1", "aece3f45769876d87b50478b90cd5e3b37b7f4c55b38f756a57486a31061dfa8"], + ["050000800a27a726b4d0d6c24723622987d8d704000002aca3c4c6433b1da60595c2bafc722b38e7910f63616b22c9d677b17979566db2149b1d99341e4e6f9120f4d41e629185002c72c012c414d2382a6d47c7b3deab591efff360fe1199056c56e5feec61a7b8b9f699d6012c2849232f329fef95c7d181172c284ce8a4b322961ad781a5b9736d7f12e8643453c105a79a9f5ae009fa1ae6c25a9462ebcbb0fd5f14554bc97747c33e34da90c816d8d0d50bfe37618c5812891484fa259322c15092d4155d8696d6f12f24fd364496b3be0871ca3d02d2b6bcd59ead5e27e83ef2d4408754e9003161877669e1a4d850bf9fc1c231854d037e262f9a9f9bd8904467eaead83f0938e26e345f9cc695985f00a1255a0a4f95db9dadcdb7c240899aea2783c2d21c2d3279f886f7ab3d4e8af5381fa0b01848f1ab14ad334f2b68035808cdf1bb9e9d9a816baf728a955b960b7701fa626687dc3c9cba646337b53e29816e9482ddf5578a8768aae477fce410ac2d5de6095861c111d7feb3e6bb4fbb5a54955495972798350a253f05f66c2ecfcbc0ed43f5ec2e6d8dba15a51254d97b1821107c07dd9a16ef8406f943e282b95d4b362530c913d6ba421df6027de5af1e4745d5868106954be6c1962780a2941072e95131b1679df0637625042c37d48ffb152e5ebc185c8a2b7d4385f1c95af937df78dfd8757fab434968b0b57c66574468f160b447ac8221e5060676a842a1c6b7172dd3340f764070ab1fe091c5c74c95a5dc043390723a4c127da14cdde1dc2675a62340b3e6afd0522a31de26e7d1ec3a9c8a091ffdc75b7ecfdc7c12995a5e37ce3488bd29f8629d68f696492448dd526697476dc061346ebe3f677217ff9c60efce943af28dfd3f9e59692598a6047c23c4c01400f1ab5730eac0ae8d5843d5051c376240172af218d7a1ecfe65b4f75100638983c14de4974755dade8018c9b8f4543fb095961513e67c61dbc59c607f9b51f8d09bdcad28bcfb9e5d2744ea8848b2623ac07f8ef61a81a35910b8a1baf39a919a7b60bc604d63185f759221d847cc54a22765a4c33475b5791e9af3271fc8d9350667090d8184ec50522d804f23c4fb44ffa481bc92ae408d1b9f2b131904f9705c59e2f4bde7a3b2c085d93fd2abc5e14d163001a12f51938d021afa92239b873dc6c357eaa8af4ee6d00540657fe32914103b5d98f68bd3e2b5359f08ccd88d0c811e4c31fbb49f3a90bbd05dce62f344e7077593159ae35050b04c9e6b86bc432dc8b048c73c0018ca5b69411297732a4e1aa99a928c71e7a24fd277856aa42501e51b012aea9446a2104e93f815a0b3a29b458314f3d8be2b9823d3421505ff6d8890e904a24a7de951a2a1c64ed2e4f9e9a5165eba4799cefeb5d148005545f9b5fb0eec6503febbdd26a238514383734afec8a481cfb2fe1ff92f1ea78407b407c6b84a214ef607cc5904d9e8c73febffa01c2b1779dc420f089eade20b69d5d7c43ceb736b6831e8c110f16cfdb3a467e9414c00ecf13731500894555678c497faba9a95d01cc464390fc4a76bfa8b0e1c68a525d706d6604b2330b6b3485215f606f1883a751588c7efa506c3e8d0c60192e8476bd1175d9562087bdb818e66216286bafe47ff4dbcced51444480a9a5673ece7fac73a0ed41ab0051753a7caa89be3139afd9793b3e02f27f040046595acd47bf13fd0da27f09eda48036d3ee437f2ee8f8606ea97343c33584657f46dba99db5cfe6ca176fab7b0f3bfa0ab61e340c34eb9f17c7ec2be03b180f0bb6f434c2a6542e00e84373f4f4649cda32bf686666143f622aa480460b5afac518607cd9af8bcd6b58c30127316b25d5ea7bf6b0cab8542ff69d9b2f180be12ed75344a395aa10f852f083ad64ef40e9c0309e9bba54b8cb33c95498a69538d3ae5b25e247098306fa8c74a8ee5bca941531d61aac27aab3dc5617d5606c9577a2a8346e8d85b32b8505775108dc85e2ade2eac1e636e1af4054c8b6f57632df269c3723b320872e4c57b218358dc7e9905bb04edf92edf0df635f3bf361e57a13296e1447af5087872d636e27518a9876e15eb01f5e8ded81892511cc2851b00b832712a6d3ba5666517bcd3567621a7cf8445589653262020c33bf78031b8ee0707de072068c170570327e6d9f5c6ddc335402efc548862f5a07094fd428a7bbc15d7b38d05362c9ca985f58a76647d2be4c2cd6b3d17d6870971d7a098baf72c6f6f1214cf1faae488bd7de259d3415c2f0ddec7457004f35708d1eccccc0df65a04943ad5cbc13f295f000fe056c40b2d88f27dc34cfeb803be3483a9ebf9b5a9026057725d63ead2c0c0ff1fe26ac1e7bdfcd6fad875842d194f331750462c06b8d7982d67995ed5d3ae96a05ae0067f4eb1c7c93231bd39773cbe0a9d33a0a40b101d020077d97ce424013d64b4d0d272ec01946b7a5eedfab4d68cd6d1b2667d04b29d0caf370098ffe4918e0ca1df47f275867b739e0a514d3209325e217045927b479c1ce2e5d54f25488cad1513e3f44a21266cfd841633327dee6cf810fbf7393e317d9e53d1be1d5ae7839b66b943b9ed18f2c530e975422332c3439cce49a29f2a336a4851263c5e9bd13d731109e844b7f8c392a5c1dcaa2ae5f50ff63fab9765e016702c35a67cd7364d3fab552fb349e35c15c50250453fd18f7b855992632e2c76c0fbf1ef963ea80e3223de3277bc559251725829ec03f213ba8955cab282d9625348a614b59bde45885649bae36de34def8fcec85343475d976ae1e9b27829ce2ac5efd0b399a8b448be6504294ee6b3c1c6a5342d7c01ae9d8ad3070c2b1a91573af5e0c5e4cbbf4acdc6b54c9272200d9970250c17c1036f06085c41858ed3a0c48150bc697e4a695fef335f7ad07e1a46dc767ff822db70e6669080b9816b2232c81a4c66cc586abfe1eaa8ca6cf41fc3c3e6c7b886fb6dac9f4822b4fc6fff9d0513d61a21c80a377671d135a668a0ae2bb934c82c4142da69d12ca724756a379a69f83e70cee0c78cf313a777fab48ee203e14fed624162e9cf3865245cfb31ea07ee889e470fee46a06230f70051f11e798c720d37f227ca01b109a1cee92ad5f6b0201ae71eab446999cdcbc4a5fc3b1af13894ae93ddffe8ee1850136b30cda1d83d8d3bea7b131c06141cc2a85fa5b43d05ec954e11b6f37b03f46213e942a7e19a46e970b5c506708430317b1bb3b35df68ae33a4926a03e6bfeb5510416fcbb0524c9ca5074156cc5a5d6fe1c995edc60a2f550411aa41e3da3bdcf64bcf04a0510571b936d47e55cec0330ee8dfe73563404f047d7f3a8a3d7743bc554955210f1eb0d08599ea77d5f974d87176d37d98b9c0ad440407209ed6a9f08464d565593e1a63b938536b49244e97d880173b640f2ddb74d068ecb46cf289b7d891307bba37054cf91b31fc82f74d5fcc000942ede911825f53fe666b0c9aa8cff6a376e1f372eac6ac4e46cc0942245d4c2dcf02d7640ffcc5a6ac3a87f5c411551bcc2f26cb94961d53f95ddb19ae930c8d70f031b29a5df99ff36695e802cbcb6b58c1ba7ed5eacfa76414a41ad4a44f71f1b580d34c3a952920b254a145fea517f5b42b2f65ecd0f82595478d80ae5c8ceea12a161ccbb5eac09990fc619a46080436dbd08d74784af002d58e06faf7f3ceae7d3419b1fca265a5559cf9e2d3b60978d81a678b9ed8e4486b4d14609d6c127c0c2fbffe30a605198367017df5c2b2c020b405035feb4b2cdfe3a281bdbd968e0a90fa651361a42de272cb8c2f54e96f51df91ef119cc7ab7e136a3bdb818b4d78c8e986670030274392265433281c8a771171c2b70a07272d2fbaabf813bf2cc8c2b2bc256d49827ffa8f6b096b4c3a792c5de003f4c33b7216056d9edb7482fb98aa033b65e1199f5837e81ed2fe494a719ffc653fd2bb9ef91327ac210482a6ded0ab8e1c80988bb4585851dc93eccc62322924cd13b5dd4eed66ed8d9972d772629ea64742ee83c04112f09ae574827aa4beb0038f2555a8ba36a9bfba028d7c21ea3cd0bbaa9ae4811c6af06fe80a8c02ab7a00e18e4a6aa1ea1b76945d2615d43ac118b56c2f2960fe93a025f13ec91ffc6d2c353699abb092dedc065db8fa214dbc46466f897b88c58b30152133aa3831af37c74d99e9e36ff7011d3238305691508a2c3a43e755dc081b511d6482a7db65fa9699ea87ff47099ed3637dbb0a3d0ef79796a8ef1e4d94d42b4bc2b4a038ae6e46b24cfc84153d31eaf895063a5ca959be63f37f2ba0d432366736d8632fce072b6ae5b6f3fd59d3faff638275a992fefc87e60d44c2cadc2b5c494e3e72eb4597c96b40167799a9001a2ed3676a8b403ae25ffd772f7081e9a32bcc1c5e2edd4e2a6576b783cce3aae11fa432262548856183ee682d5dc31beb38f061cbdeca7021a444e2dd417df26dcd220f2b731772b439e96d614e1facb486c7a7d5171b1de359f6ad3a96f649c969102a1964fb4b4a1a4279c68e6c372e42187d754e804a61653092069fb9b6d25266890808b015df28c801065da6febdc1a56bfd002625acfaa5373fde149c1cfc3649b4869696d44ecb12479c5ebef995f10029f8b530eeb3fdc2e50e8757fc0bb9e263023db82f878d9ac7ffb0bd4391df1d879899a3ef57bfd0d1f7755648edd85bb052a6edf71cd2628c987429f36dc505ccc43f30e7a869c9e255e2af9fcf30c121796d190000960cb6fe2f1bf246118b498f3247f9d484c73cf09393039e45326b8ffffb3e7e6159c46699f100792d4672950348a90552e45943beeacf03f3216f94e274d63d637d9f190e8a266cdeef153530bee5cb8355260505c2c2e5d990fffdc34ec0ff7f1af81b24ced0efa6213da6c7c60c487f5f7b03f8160a057f46d05bf8218b3add9c06893bd02db9b61191dfb133bfabe4858e47a4cc32e416ec08b8ac7915a43733f4406e9d967c560f344d7e904a28045d91e50d79e42867c0ad0ffb55f68875e586420108a1b092576415dc13693a1212b0e7003084ef95a27a7d4284d276111d860142cb740c15b7b623cf48b3f7bfe3af08df8d1d3e11ff198214e673776f04f0c4e846c32a10c0d559e4968b4f8e1b96cdc1ea7ea31dd86d680e25985e1d5d02580d04274234af2a51b56bb68a29e03bab7b50f306ef5d9a4f8135d69614ab34158fba370f78763d4020081fe39cc231630e4c08915e631771550e9ce1fca2c63fe06b7989d584fa7d782a88c1e7d64b6fbf55e3596af9bcb7585f8c7d3aa5c2082b265249df05701dab031c4bac1ea267a2996a2028d1e6a0f80a3847c531dba96ee65a24189bd2712e40e959664981e58b2a4f951ef8f497dfff2f2f271eab89c628e18b5fcb43882537eaf6ad2a6b1754633caa86bf2c76f3993154fc73e6fbba2210c2743f530a427849a301e00e01129f03a4607f87cbe0762c0b1c65855deba8422ca4b88abeea6a4382cf16ccd6dc7c37c44e549c4534819acd8bb0a02a5fa7a1c1d3806fbc3407fd7da93fd0de6400d3ab8977485cddfbed5932f507b79947adb2fad37615aa717db5f298099f20f263b359a1151a6b75c01365eb154ae42140d6e10342f14f34dc33e07ff0e4d1a6be375b32f84b92e5d81ebb639c4f27e715aa42cc75707d4ebd1bbfbe8f90fc7c953e7a9715e65af8267373d3451674ff084efd92ccf3bcc7aca1467b6327e4f9522b2cc579a7a8fff7ca7cf145dfc13eafc34153b2c3e8afbe53444d0c73b3bd5bc870b01cd457911e356313fd1dafb4c8151634a01aff7cf116d433c3d2b3adda9cebe18f7d172443e5e7b5ac9abe8db2256d7ebe2ff28020939503870597b9a955892c7389650a2d42ec92be723fedf2f2ede5a472aa1e74f33ad41901544edbbe3ac464cf439196015f4f22ac2b8fc01496beab4d45907f479812a259431a2cbc93d4f3b84e4dd366020273a6752e501af6ff1b78ddc817e6ea351d6006becf8d2ffb03990f67774a81e05b7f4bbad8577fa27c9de64e1b11dcf384f5956443748755a9fc6f2a00b10c3657ebac03bfc0b587bef2f45ec8acdaa51c143b0cb25b9142c61bd790a80d7c23f90cc03495b51e4d2843e557f9e2545108c6c6fae359f645c276891c0dcab3faf187700c00310a4fef5631400009a2dbd0e138d2deae41caea5f186577a77d1b737fe21f0fa5a18ebb52755b526ef6130fb56944cfab87527c250d113b29bcac9aaa10c2e7de415edb0806c6da03020a134ca7ecdc8da1bd57a37f55a46940b45b241b1c16ee100927d1bd860d445a9de50d4c384d6e1d00108026c0ea5ebbf0b72fbf5c370bce18d3acbc46599099baae1d802f77333494a7ae130fe86e8f818f9261a2dadb4125229ba0ffc0e7090324430b521a90d224ab7a1024e1d893e7404fedb348e4d5e2235c59a7876a0fc60145c6a009687684460271ee133a437fe52fb6cfba97fcec161df515dde905a24da6d37bdc34044a955e682b47471ca1e8c78c51ed377cd4afa894bd9bd12e707156da0726f7cf5729fabe37216221507c5506ef59ece2a581c9d8b0b2074ab0e848ca6b7054d1841837e8791bd82715a28ab569a9a287a4f6490086b1c22169521cdc132212939c84a10896422170234cd82055a8c1c2e53a0e214938a97ed7cc8de0f4ed4b21b945b55e9eb0559ea858d43fc3113165ea18b7b893a5e326a5b0af475e27a54b207b41f92e33699060cb6704ab5690db57aa812cb9c24430644c3b3b2a44f2718a7df88abc4117b587deff78de9c73af28080b2fd05003e11d3e1b3299dc9521f8b513badb010", "3f1b9faa405d8270e2d9538e339f919793841691ab033d1830a8dcbeed4afc4d", "c90c059feb7c807a6b34777c14a6c028b092d82cd5855ed5fe8defae87d289f7", [], [], null, "3f1b9faa405d8270e2d9538e339f919793841691ab033d1830a8dcbeed4afc4d", null, null, null, null, null, null], + ["050000800a27a726b4d0d6c21bfeb91b0b31691c03c2e825a597b8fb75bc562d654d62104640dd74e56cd14baaba565b84b845e163d1caef250153981637204f96a59c8e8024d9041b2029e94c15245f1a958840ba3f380a4d20f1184e77827de3ff8f0153459afe241f723c084823230e003d3d21e53501ec0499b083a7dad685c57127f4de64733a880c2db20752535163525263f664a35100000003f927b9469e18229d02c33dec3f117c5d2a8a85db9b5756dd52b8190db2596280fa21394377a4551c76d1f75ac03c262054dffd79a9ded05e888958199eea4501ccfa4152d445a6b308549efc1d9b2b97d39da90c6388be8052458325bfd2f5bf73741d5785837a6b844b474775718c29dd99084e9f88ef153a8329f532a690171c2d1e3074dfae3e23db3948a453c39481a9914dd0ac79e927360129be3a7f119544122000610bd2aacbd82325a59b95154ecd82c88d23abd1e20770ffb8aabf83fc0734964ccd411d1c935714e24aab566f4f08424014c4eca91b590f082b473f361c87415d37bd20d70fd0b52b6ddf1865f766702e32b05b3cf1630ee8597aae19633f3516a8555ac5be32c675be1817efbffd9369041a089c283f19649968c2498cde56f500434f280d77a9c62e43cbd3f136a4c6a00a43e6ed530cb2e8ae838860adc88aacc7bd6a00ae0c19ff4533a485efde082b5f4d1f7a8ebe7ed82b7b05a8cfe1e373459f1bdcbf9525747e8c9508a555facb798740e0bdf994d9739bbe5538a0ae0f076c582c0f5ba878b99b8249db1d7e95056c98af083d98cb0ed9e3f7436e1c7643766f966b83e999206ebd1393b9b2a7f414480fa017480069f85c7749c435ae2fba2ddc1038d547d84854817ef39635c29827aad86726c9ade3b265b9086c8b5b75ef56fe4bd8b4d62893895b3fd2734fdac464156d7e5ebc7ecf1d83b86f659637e3b142c164963b8cdcf4ba4f4035dffc5a789458847781918ac72fc18bbbf5110032e66d75b3171ef4b513290164a77b42b0a4cfb89639ab23845e1aa2a452f3731c8cb65082a622a7c2e0013ea47d0bdd42d6990466649a905c684c3251716d61f760d53de6e3f790fba7f5f1f4de267113bdfcd7422822330b32d58e6777765f22a4116344eeb65b2ec516393ab3751b5356d2b0c9500c0f3e469181035bc3660f0b8f9fbe6e40b5e89cb79b063714ca75e72e2e100a10d63bf784df0820ef25f8ef40fe5f05fb95683f9105ff3cb2d219ab76605a064f69219f1dc0d00b3b48642f970dc00cca4b8b43308be18286ec5a4288d600a3785cb622d468a4c6969b3792f2485027d0ad9aa4a9c2cc972f9ee5190a95b1eb058dddd8c08e7d753f5e011b2bcfee1d52c1c4f20aa3f712741fc093a1b36af555f74e30f85d5cc959307f7435f7ef04ca2c3125bcef2a990176ae339325d5a588da5796faae5bab7c82977c0ff797093e2c1f3a7782a6d39a61ee5528990d8d369e8edcfe38bb702dff02da3428545d9d6157a51e55ebca6a8506e3699a3d7085a4d9fed5094c68b375e984f68393300871e308fcf74e276b62266a8f4ee3945f094d17a7c07cfe0bfd4895a14fac971c92a195b442683c4956bbb195a4fa66dc9cd542c76b9150c84bf890789942f55c200b773ecdd7992cff3eca24de3e0984e10e68ae387534b96cde3792f135bf5f68787d370ca8c4c4074dc5d601ae90495437c3c2d48a3d966683ac05160b7a84eaa7aab74009e57a85f7bf68a2e482000f829c545073a15d5cd0fcc57439a4350eaf098dfb82a085ea8a4af6fa8381f0658819eab483f65b325d5aeda15232cfadec75ab1866e4c0155a9c74a7a57ccf34c483ac7da1588a1b6b9941f11040f94cf78fad89bf11fed69aa0d83105adacdd4e5f04a62424023c9b9e33c4fb7f12bdf21f07f265c537d51c6551f4617b915d21991839c3d0d36393d646e0a8a41509217d0e7d2ca1a0a0d677a3eaca23edeb07b74e652a0bc50c6c083a55d6c7306e74086f4768933aa24873681867a7893d77cb7f29b8c847c583f2d071a686616e206719f761ae39c110442e06163d2b84590360695d4e19849e634f24d9ad396c19ff83ce74f46e645f932e141a41195936c85d514414f112e60b1a2537c38d6dc6c4638305c9bd6c62e366bc63123e3e6dd36eedd3136fce8deeca2aa09a3298a39d83859efc9b2b69cf9a7dee08a98e4be558ac7912fdcb42209075420260f7cad0f2c01f2afe33073f26249d944f7a50dd84839bc3ea7fdee4ed71449cf07533d26e1e27a3efb032c3a3b34bd3092622d2062ae536ef5149c49b5bc9475eafab6e675761008b0daddeecaa604470bbe0fada255d290e92b190c2c2d8c2dee5455d1fa9a9f3db7779b584643464aa8014ba66994de25517f83980e66ee4f62314ae6dbef452d5d38b0a16f3991f36d8a8b39ddc0d5595eed98762878cdf3f4a2edc5cda77d5fe4faf63a15f568a540da57dd9beb6fb1a977ccb91b4d79cb39b28911a29e7bf028ac6103796dfb6b20967239ad373c3c51d3927f2380019fbdbdde59697322636a0aea1fd22c588575c0f89649e9f3ed393cccabba2e794b7c4b2daf8ddeb7f45270d3f95edba5b0de7a32819233b0c55350114ccbc481586fd0542c3a0afdd245228ac7474b3f549b103a0062df1bdae35be3f6a92dad6177cb848eee24c8520a330bdfb26d75fe7b4b365d094451222eae18b9849f5aa17e52ca5c71e844075cd44038e5c894ca2cd19765cf8f61b619af02456ae695962fe5e931a63b5c79052ecd333e18412db91e15f7cbc70b4cd7e8e3c951f358572e37767e7d52704a6721b30efc41017ae4d231558c5c82cc7dd7e3356c09dc24906f0438dfcc300856ac2ced8f77fa8015736c661e80248aeeb774874aa79d290b8f5027a0a509537fc7c689b7ad86116cfec2647ccaae1c74b416f3e6ae8f7cc60eaaf7b6a590d51544138e1732945603a53462c60e1f6cb0c9ca0390c488224c313269fcd59fcb611fb2d9b4c8fa601bb1cb8d07d797bf5de52bceeb02301c8962ac1fc0491dc81affd6c1ebf89a13d6f290eda5d5cef382215c5e951d71305ef33d9737126d0e662905f1250926f6a229990e38f69ad9a9192b302f26bdda465d90b94b12c57fa3fd6930083f184438d8a889d3f5ecea2c6d23d6736f2a0f18e26f4fa45d1be8f3dc4a707137e95d2ad594f6c03d24923067ae47fd6425efb9c1d504e6fd5575340945601fe806f5756acb562f13c0ca1d803a195c2ebb2ef02ac33e6a88dea075ba996d3c336648e8694d3a19d3dca531beb50d4327c5c0c23cb7cfdb08ca7cf2cac6bc139d0741473d376029cb4ab6bf054557ce294c728a4687d57ec8909ff51a4d02f9dcd11193d7d1c9fdae6a17396a1bf57a994934f5e7a59f045debeaff62ef326b947f2a8b49555e4d99b3bf5c81ff9fe314e047af152508f57015ca402c67d925c99acea3ee8cc4b008c5cb43966e714ef480fd05e07c7b2dda9aa3966113eaa293d3f622b309d64803ce1e6378b6aac4fab527c43cd45ed0a3c1a4b9fb18dcccfcdb6ac0c2421639cda0075a20dc5111b8d3d3199495bd9133dbab94541410e4fba92c7b606a5cb122f140cf1a3596f2788f3c8b92660f14cb65af5dd23dfdbac1371ecf4b33712fed2292c44f70834cf96c05d58827e69bfc2e696fa0874025e2c3d19b072020031513b1962ec540856cb189387cfbfcc0f7c68223cba47fb0c9b486e4d99171961f7675a8b46328a3bc109bf07c66d5ede771cc4c74ce80333829191eedc493508a644530a6144f22dcf97525a4cdca1ad71073b080b73ea4549f5401bff4318268e6ad637363157a19a53f123a0b0e16d0b77f02028da464100fde76d83dd0bb224ede28042290ab2bce854139bca36e5b2cbdfdd9106fc9f18b95553e4fe548b3e4a87daa7ef1ee38ee9b4e0dcd63e80ecbba7e74b3e3ba3d0e8a6392a062b8e065a54414c3c5bd9ce4e85ca6293e884d1456a4c31e1654ff23ef26e2e14e1298a49c072e22f9d98bb0f9b03bd5fd013fcef3ed6a49aeb98720254087ef728e319db964cea54d0eca76cfe56288b6f64f4a19df37ed17be812e82d7d40536f378a931c82cf7111d6e117809363809b6be378f8fd5a1ce22a8d3c4547abd959830aaaf013de2bf27a1fbae8b16f38d1349b6031ce02348a240579c0e535790458b4963b616933d1005c1d03d4c95180c8d17a55ef4bee465668b20ea4118ca5692e", "6762f9c57230a58dca6d444547b2f6e0e213492dbb40564ae101df13941c6152", "1c3ec5104d96b66837b6e381f702c7e48a097aaf125c9b82895b578b92eafd14", [787459282010655, 1685382316228727, 1715663111103469], ["656a", "650051526a", "526a5153005152516a"], 2, "a7e272ca044087c4d35661888b348c57e98b7981a16fe5e88181c8e15aae581c", "da3edd81f4fa79e47fb676ca0b56b1299ff5b5ec5823ec871e3d7c12884fe577", "dde86c6961d02d655103c9d19c89623fde4ec6b5282bb8fbc7e8ba46911e9a3e", null, "411508bb7b2194f617adc6c782bb7720c8dfc25fef3bc48ec731a24aa2c1c105", "b4253a25bf7ee6cd209c5f07666fea853e4b800862a29aa5a8961f953203a3b0", null], + ["050000800a27a726b4d0d6c281836c3be99a081703a460e968aa7109870bbed17df5f888c8ca1467ae17dbbcde31c1105cb5bda88ac6c627000452ac52ac0ffe81ec58bf1e6d1bb7aaada41fba0bb588778a7f65202ad811ea73d26c74550395aff75325107c096a5251acac00650051a2e7424719a3d185b7e0a43a472e298ac0afdc5287d7ad124cd9405a62cd1ca08b282efef7f928df0852525251ac5353aceaa5ff1200000000", "221d40597b563f206458f9736b8549237c5554242c419022435a3350a0708ea1", "6a75e49324e85320fe2f22466618b70b4bbefea248e5d3673f18f2e487a2b20a", [1076763594431866, 316847576141144, 1780844721475339], ["006551ac65630053", "63520053", "acac00656a6351"], 2, "b5a18ff657839c6979c2f2afaeeaf2f5c1cf43df92f4a65755fa42ae2b7706ab", "9d802c8ace5bc5f5b03c7dac8a29ef33424def82cbc4fc8400c0bb9d5e084147", "ae2db14c57781fb55c2f6658f6141883f7ebe0d39f439cf566dc550fb9da7168", null, "e711834b5a4eb84aa2986a53c2b2ba2fd6374e666f0311bfca8cbceefdde1e38", "5c3a0839fdc73417ae7d5f15dceed6c1a4cbe80c6c25ce0386fef7c058a8b943", null], + ["050000800a27a726b4d0d6c257b85751235dbc100252e41e002931b45746198e5dd9571a56a7e0d423ff27989d3eb417ecd3c3093fb82c5658009624c53219a60cd0a8c4da367e29a71779a73032985a3d1fd03dd4d06e05566f3b84367cf0faee9b0963525200526a53acac5d82d0a602efabecef1bc70100020063977663550e760600016a00000309a89a5f40b37303e1816df13a9d7e20db159c9f42773a2fb023d95abb1e1cbe81c092edb230fa38ea13c5de7c0e5031a1e84daec3ebe62d5f6c4abe5ce90a3fcdc3a6500c011c8a0fc03db6cc0c56cfd6543aed33ac6b7c4db783dee6b329277f96c7e90bb9b47302465f375c1d3ca48e54facae0f9c2dd4d64d9046152b43634227fbe32f663bd1d90bbfa22f0e9c793477193ddcb40181a679eae7811323231f01f55c7ad04cfb63f7c4a3d0a2b0ffb0b05a6be055b8c94ca80bb0a1d13cd4cd69ab98304ae2515d5f7699d4abee5c20be609d873511012f234bd85a7eff5fb634cff2658ba6516048563095ecefb3015ee3f03ca52a177f261ecdc26bc089d34c6404846e9c647fcfe98cc6acdbb464f64278ad8ce9d1ae0d415bc0c05245fddaf4ebc8dc703a85cb270f796ad2d937e2ac0d5e0a34821758000aa59c9d4652485294ee0ab29696b21430fa54dcfbf2b9c49d142064209eeeed4d471ffc017d4e20a796b0927804c061b9f4a7091fe015ada68fd8442e01825c88dfe55cf5de38936f7ce25311b902ba97a3c12a95cfa1c3a591b818f60832709d9e4839e410fb36b84f3ac4f070fc35e161978259e5b8edc744d90919aa770bb36215128e582b59641e23852e958eb8fc3c0aa96152ba4f77f138d6a6712a3ae3226015883f81db23e583c869c4c71143a6fffd65e8dfdc50c99a2f1f314cdcc71359e235f1d7dc2b5f38ef7b970843163c03f9dd40a8015efdc8791956a3f3cedd9ea64f8efa7a0815a70381d71467817bd04ca529aede07ff60d176aed0f855a2eaea89eaeaca89358c081826a0812a5bca28be1373f086dbdba7e43e203212c9fed21474ba19a055ffcc179412e893a744832298c5fe24cc6b18667f49b34dfb12379267419a9cb9403d8167d8d1e91d2811a043b29243b069b37587847dc6fcddb1831bd1cc2567ca033ac40f74ab6955f683b12e4e8254e4ea760d38b3f46791c5c4cb12bc7ccb0ed1865f25d601c303f81fb1fa1db48533d3d6b288e4d9a4dff8ec21c96f578399710c825fe7e32f93a8c0743f9ebd54cc151c7610337aebf7e9b915720a54351d49ab8c22fa34998dcf583d4387361ef3ff86f50ec53f49249e4ad349603066fc9c661d69f911dfa7241c8d5792d78332e93db6593e593fd5d456abdac79a716fa6e3f3928a84d197086ecc27eac188ff2b71521762ad47bec08992d86850eb3ea13d5070807a2cb6680a249ea1c04203748daab9b0d3b3c2e9dcfe7760c79dda3c0259e7da9ccfa5fb647a5e20f3f3bc866da24eade36b683a2bd7197fb672726f42008b46ad7f8abdb18117f322c57dc017b0a371f4863135b4db5a1b6e0111e630e23459a748833991cff71a05c4ab19dd99771582d038104b7e039a376f7acbbeadb34f945beb9d7ca0e4e3d5c5e4eb1d8526ebd13dacb1ba35735c6d04a4555acf4bf117626500d77b38189dd4888041225acbe3874a4c0f607fe6745f9355b3fa188f1d65c09f389af1b9d6232aa79447919c550f6f31fec35481cb922de2db5b4da2f81948617028e321706a3a778c1938c443bb00e5b0ff06ad8ab9b1ab0c11477673f85df9561dbea45d5f9781ebe317a0710ae5461e34fe6f1b1aa9b4e67b14910984802c2a7e38193bc7bdc8ba3e4e3d1d933bfb580f5b3e87a2a06517051410fe1b4ff1ea0ade824f338515456a57c7a916a74388ee8f1281f9ade0ae2a2613a0612c469df792b8df4cae4fc25c1cadba95a807ce61e5a5303faaf9e14653996b5a8adc34fd475ef1499094babaf1f3f07da9a390b1d9fc9a08327987adfe9564863fbdfa8f6b46a8841583099afb7870118face76347e40b6fd8cd15582ae8e23be9a0219bc3e4e4546a30d3bbbbd1686086876be0e4c859be71fb58f4fab3d28c0b4f7e75ad1edb7f88946fb40cfa5786a0fcba1303c8347ecee93d46d140bb5f69531d666548b109ce764bead7c87bd4c876494de82db6e5073a6c94f7c099a40d7a31c4a04b69c9fccf3c7dd56f5544776c53b4df7953981d55a96a6dcff9904a90842e5bafec8840c2d255bf5ad61c460f98feb82a10fa1c099f62776798236c5ca7f1e46ebdb2b144d8713e56c772f2c3b860ea5b03a8854bc6e6590d63cc0ea54f10b73ba241bf74b635551a2aaca9687ac5269fd368b26d70a737f267685998a3f7d2637914909c746495d24c498635ef97ac66a400894c09f73488eb7cf33f6dad1666a05f91ad7757965c29936e7fa48d77e89ee0962f58c051d11d055fce204a562de68088a1b2648b8174cbcfc8b5b5cd077115afde1783f5cdb26f7bfdf87b21b1c70dc56bf1c82857a40ca2aa236ec566912d864937aa07ddffcd377395cba616d63c0b69c01fcc45391fd5b8763fb96d7ca333a127911b4ed95b00c24cb578ffab9f1116030048608cc990e74ebad5c348ad9a885e907e0be5a7883c88489cb41432dac862023d46789eb7d989af779e5b8d28305d7e2124d3b55770f8c050ab25e1a4602eb58cf1234eae15f33bbdece27a6b12db3e4dbfd3a2bfcc9ee6ed016c0f665be8133b7dc1d86044db0f9db40fb0e9f8bc2e4db5382a8b4f815b4e8434ad0dfbc51a5e9b145e1596cbf4670b7e05dfdafbb0cf3ddee28d76a82428e8aba4364e84bac379298df2932e69bb5d045516efc33ae6cc3947ceb09ed371667212a831b5485eafce8488188ea4e27d0cdf7ddd348abff777f4a13bbc716b6a5944ee727965690e209b49eb962c039975f939ed5c6e4c400d887759433d3ad716da0cb446113c7727a64b58c3f8a0f81189f98005233a81366aee73cec85228ebcfd5ee3c3fb44db76ba243f2842b7b5fc746ae51b0bc4bd4fc9fd833565ea852b92b224f6990318ad8c7d9437e20e2a1f20e818f9057c5abaaa2e5c15b94945cd424c28a5fa385dadfe4907b274d842707db3697a5ae6c8f542e5ecc07fe47350d1014670212efe81fb7c73e8450df814ef6232f7490f63ccf07480f884a66eaffc28fea448d7b401cdae10e7c0c7f9a7b15331969fc8cb36396773de191931c750f6ce5caaf29768ebb27dacc738056a8125b4772bf87ae10a8a309b9bd655043cfc3159494368c5ab8cadb7f671e9626bd263e31181a604b506a03b439a7ffe4355892477e2bdf338c62c3922f7d3c9a56c7103d911948a84b5ae2dbb16a3761add053a0f967e6b5bc94211b6547153267c6ee1cad0d974a71088583735e4f63d33156dadd54c2faf89114a127b97b94cc2a22ef303f459d04fc0b53ace5918d47ff33a558bd71a75f355fbd06bbccf4e02c3c0a4b63d0cc949801d63a64cb2d32373b2c7b274ab2db4682142c8b21d84c481f5ef21e4b5e3603451bf94774d0ef47f63fa6abb78d21c193cbe65b695fe67423c1e2d312e2776fa24ece84683e74876c55ea0369e4ea0e86494e00dde236a1689731f0a5d8203afde5c423640b81e4f631c981c03839f4b44c9e8030023f8b9d8178560daf975111955a2bca3423eeefc527be3a8543eb90a5ec02f35c7c64b7dd59a72da0074634e01d2abf3637add77c7350f12b011b294168ec75576e47d169e3938bf6ae2aa8ff7cfba7cacb1f92b6e4c2497bffa9f17cad242fa9c3179c1a3aa81f7361649572c715c25a1f6cd5ace82c00ab2342b9c3cb4fffdda160ca5ab9e9baf2139ef9afbe1b1f309462afce462a79bb9698e22c957c590a753a76b87e009121e06f6a1bf62a08bf435d92e2fffe86e2a9cbba9133a68e4aebf33c38436f2545fc2d52832d165af415b244adc5f57377deedf460aa3beb43419c6b082e835cee2f16f2f8767f03d9f42a84a76cb6867b2bc75baeeccafe61519cfceac5527961b324dce09335a5853a6b4da3e471fc1fb196f76d9b879c7200862ead18dea1f3ec9037f37b6ea289166510b475b20044d452e0f6ec3ab47161507d53a2dc60949f1e450c3eadaef886e6b827c5bb5ef11f4028a704fc5a9382c6b03e7d8081e07989286a0d9ce561faa1b6e9623d7ab71007fd277b986a048834602eaf8780f0675baae68415d3af01019b5ea480dce9362ec8d5df3e780ffa72eba8a8df73c540870a839bb03910a491770e1c84b223ee9165270c4ccf6fc7075c37e6fbb14381507fa18f13953336ab2bedc600c615bd49927e9d7f4884e6ed3fd5e4b7c38", "5bb44ebf8030ac2fb74d82b432d480218a3d1648e3270b89ffb3cf735c2ce7a0", "b3d8ef975a6afa7200923c1160b7ebcfce43b5b91ee3b3dd91784938bfa68ea6", [1399781968202734, 1999413718097392], ["005300", "535152526552005363"], 1, "a81923458121cdb56475f96ed2d92080f56ed306e41f45012af57cfb348acda1", "0b752de948552d1170c1c9382db14eb46ada2db164da90141a626a035df3b4d5", "ef8af06f6aec49394f466ddd5a6c112f80ded7687ca21c10782fda261be5f687", "9ba54ff9f5da79fba51cdf4891c021d8dbe888bf215f7c2fd79e43ba5dd1cc76", "2254839a4ed25a96548dd804dc333829baa4da2677e9c83df8ab2d5a57cd666a", "1497bf31df9b0f6e77e1173a9eb408ef5e19d844aa9a85b6e7546f30de1f0be2", "39acb5fd91a12dd40736fc6aba53fdf52f05838094c3aa6ef345d94a1a0724bf"] +] + +AUXILIARY_SAPLING_DIGESTS = map(unhexlify, [ + "4ae5dd1bfb8c15bc3de59ab71530912abe24059b10665722f3fb8fae9b55006c", + "8928246ab2688c651481df538caae0c9df113665f03947ef24eac26744be4ff4", + "6f2fc8f98feafd94e74a0df4bed74391ee0b5a69945e4ced8ca8a095206f00ae", + "6f2fc8f98feafd94e74a0df4bed74391ee0b5a69945e4ced8ca8a095206f00ae", + "6f2fc8f98feafd94e74a0df4bed74391ee0b5a69945e4ced8ca8a095206f00ae", + "6f2fc8f98feafd94e74a0df4bed74391ee0b5a69945e4ced8ca8a095206f00ae", + "fb05fc3237bae8aaaa3f0f1da344c8a2f2e7245acd8fc22ec36bb8afead4c980", + "6f2fc8f98feafd94e74a0df4bed74391ee0b5a69945e4ced8ca8a095206f00ae", + "6f2fc8f98feafd94e74a0df4bed74391ee0b5a69945e4ced8ca8a095206f00ae", + "6f2fc8f98feafd94e74a0df4bed74391ee0b5a69945e4ced8ca8a095206f00ae", +]) + +class MockSaplingHasher: + def __init__(self, digest): + self.digest = lambda: digest + @unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin") class TestZcashSigHasher(unittest.TestCase): - def test_zcash_hasher(self): + def test_zcash_zip244_hasher(self): + """ + Test ZcashHasher on official Zcash test vectors. + Digests of Sapling bundles are precomputed externally, + because we don't implement this part of the ZIP. + Coinbase transactions pass too due to some dirty tricks. + """ + for tv, aux in zip(zcash_parse(ZCASH_TEST_VECTORS), AUXILIARY_SAPLING_DIGESTS): + tx = parse_tx(Reader(tv.tx)) + + is_coinbase = len(tx["transparent"]["inputs"]) != len(tv.script_pubkeys) + t_inputs = tx["transparent"]["inputs"] + for txi, amount in zip(t_inputs, tv.amounts): + txi.amount = amount + # Trezor requires reversed hash + txi.prev_hash = bytes(reversed(txi.prev_hash)) + if is_coinbase: + # script_pubkeys_digest is never used for coinbase + # this is just a mock script_pubkey + t_inputs[0].script_pubkey = b"" + else: + for txi, spk in zip(t_inputs, tv.script_pubkeys): + txi.script_pubkey = unhexlify(spk) + + t_outputs = tx["transparent"]["outputs"] + + header = tx["header"] + assert header["version"] == 5 + sign_tx = SignTx( + coin_name="Zcash", + version=header["version"], + version_group_id=header["nVersionGroupId"], + branch_id=header["nConsensusBranchId"], + lock_time=header["lock_time"], + expiry=header["nExpiryHeight"], + inputs_count=len(t_inputs), + outputs_count=len(t_outputs), + ) + + hasher = ZcashHasher(sign_tx) + hasher.sapling = MockSaplingHasher(aux) + + for txi in t_inputs: + hasher.add_input(txi, txi.script_pubkey) + for txo in t_outputs: + hasher.add_output(txo, txo.script_pubkey) + if is_coinbase: + # force SigHasher to use `T.2` instead of `S.2` + hasher.transparent.has_inputs = False + hasher.transparent.has_outputs = True + + if tx["orchard"]["nActionsOrchard"] > 0: + for action in tx["orchard"]["actions"]: + hasher.orchard.add_action(action) + hasher.orchard.finalize( + tx["orchard"]["flags"], + tx["orchard"]["balance"], + tx["orchard"]["anchor"], + ) + + # test txid computation + self.assertEqual(hasher.txid_digest(), tv.txid) + + # test sighash computation for a shielded inputs + self.assertEqual(hasher.signature_digest(), tv.sighash_shielded) + + if tv.transparent_input is not None: + # test sighash computation for a transparent input + if is_coinbase: + computed_sighash = hasher.signature_digest() + else: + target_txi = t_inputs[tv.transparent_input] + computed_sighash = hasher.signature_digest( + target_txi, target_txi.script_pubkey + ) + self.assertEqual(computed_sighash, tv.sighash_all) + + def test_zcash_hasher_transarent(self): # this test vector was generated using # https://github.com/zcash-hackworks/zcash-test-vectors tx = SignTx( @@ -96,6 +208,138 @@ def test_zcash_hasher(self): ) self.assertEqual(computed_sighash, expected_sighash) +class Reader: + def __init__(self, data): + self.data = data + self.index = 0 + + def remaining(self): + return len(self.data) - self.index + + def get(self, n): + result = self.data[self.index:self.index+n] + self.index += n + return result + + def compact(self): + b0 = self.get(1)[0] + if b0 < 253: + return b0 + elif b0 == 253: + return int.from_bytes(self.get(2), "little") + elif b0 == 254: + return int.from_bytes(self.get(4), "little") + elif b0 == 255: + return int.from_bytes(self.get(8), "little") + + def uint32(self): + return int.from_bytes(self.get(4), "little") + + def uint64(self): + return int.from_bytes(self.get(8), "little") + + def sint64(self): + return int.from_bytes(self.get(8), "little", True) + +def parse_tx(r): + return dict([ + ("header", parse_header(r)), + ("transparent", parse_transparent(r)), + ("sapling", parse_sapling(r)), + ("orchard", parse_orchard(r)), + ]) + +def parse_header(r): + return dict([ + ("version", r.uint32() ^ (1<<31)), + ("nVersionGroupId", r.uint32()), + ("nConsensusBranchId", r.uint32()), + ("lock_time", r.uint32()), + ("nExpiryHeight", r.uint32()), + ]) + +def parse_transparent(r): + w = dict() + nvin = r.compact() + w["inputs"] = [parse_t_input(r) for _ in range(nvin)] + nvout = r.compact() + w["outputs"] = [parse_t_output(r) for _ in range(nvout)] + return w + +def parse_script(r): + script_len = r.compact() + return r.get(script_len) + +def parse_t_input(r): + return TxInput( + prev_hash=r.get(32), + prev_index=r.uint32(), + multisig=None, + amount=0, # amout unknown + script_type=InputScriptType.SPENDADDRESS, + sequence=(parse_script(r), r.uint32())[1], + #script_pubkey=unhexlify("76a914682c89bfc3940621bd4a4bfc349a79b46ce707e388ac"), + ) + +def parse_t_output(r): + return PrevOutput( + amount=r.uint64(), + script_pubkey=parse_script(r), + ) + +def parse_sapling(r): + nSpendsSapling = r.compact() + vSpendsSapling = r.get(96 * nSpendsSapling) + nOutputsSapling = r.compact() + vOutputsSapling = r.get(756 * nOutputsSapling) + if nSpendsSapling + nOutputsSapling > 0: + valueBalanceSapling = r.uint64() + if nSpendsSapling > 0: + anchorSapling = r.get(32) + vSpendProofsSapling = r.get(192 * nSpendsSapling) + vSpendAuthSigsSapling = r.get(64 * nSpendsSapling) + vOutputProofsSapling = r.get(192 * nOutputsSapling) + if nSpendsSapling + nOutputsSapling > 0: + bindingSigSapling = r.get(64) + return dict([ + ("nSpendsSapling", nSpendsSapling), + ("nOutputsSapling", nOutputsSapling), + ]) + +def parse_orchard(r): + nActionsOrchard = r.compact() + if nActionsOrchard == 0: + return {"nActionsOrchard": nActionsOrchard} + actions = [parse_action(r) for _ in range(nActionsOrchard)] + flags = r.get(1) + balance = r.sint64() + anchor = r.get(32) + sizeProof = r.compact() + proof = r.get(sizeProof) + return dict([ + ("nActionsOrchard", nActionsOrchard), + ("actions", actions), + ("flags", flags), + ("balance", balance), + ("anchor", anchor), + ("sizeProof", sizeProof), + ("proof", proof), + ("vSpendAuthSigsOrchard", [r.get(64) for _ in range(nActionsOrchard)]), + ("bindingSigOrchard", r.get(64)), + ]) + +def parse_action(r): + return Action( + cv = r.get(32), + nf = r.get(32), + rk = r.get(32), + cmx = r.get(32), + encrypted_note = TransmittedNoteCiphertext( + epk_bytes = r.get(32), + enc_ciphertext = r.get(580), + out_ciphertext = r.get(80), + ) + ) if __name__ == "__main__": unittest.main() diff --git a/python/src/trezorlib/cli/trezorctl.py b/python/src/trezorlib/cli/trezorctl.py index 555818eccae..886c32f7ed4 100755 --- a/python/src/trezorlib/cli/trezorctl.py +++ b/python/src/trezorlib/cli/trezorctl.py @@ -48,6 +48,7 @@ settings, stellar, tezos, + zcash, with_client, ) @@ -389,6 +390,7 @@ def wait_for_emulator(obj: TrezorConnection, timeout: float) -> None: cli.add_command(settings.cli) cli.add_command(stellar.cli) cli.add_command(tezos.cli) +cli.add_command(zcash.cli) cli.add_command(firmware.cli) cli.add_command(debug.cli) diff --git a/python/src/trezorlib/cli/zcash.py b/python/src/trezorlib/cli/zcash.py new file mode 100644 index 00000000000..8da7abd4072 --- /dev/null +++ b/python/src/trezorlib/cli/zcash.py @@ -0,0 +1,90 @@ +# This file is part of the Trezor project. +# +# Copyright (C) 2012-2019 SatoshiLabs and contributors +# +# This library is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version 3 +# as published by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the License along with this library. +# If not, see . + +import click + +from .. import zcash, messages, tools +from . import with_client + +@click.group(name="zcash") +def cli(): + """Zcash commands.""" + +def _parse_network(network): + return { + "mainnet": "Zcash", #messages.ZcashNetwork.MAINNET, + "testnet": "Zcash Testnet", #messages.ZcashNetwork.TESTNET, + }[network] + + +@cli.command() +@click.option("-z", "--z-address", help="ZIP-32 path of an Orchard shielded address.") +@click.option( + "-w", "--network", + type=click.Choice(["mainnet", "testnet"]), + default="mainnet", +) +@with_client +def get_fvk(client, z_address, network): + """Get Zcash Orchard Full Incoming Key.""" + fvk = zcash.get_fvk(client, tools.parse_path(z_address), _parse_network(network)) + return fvk.hex() + +@cli.command() +@click.option("-z", "--z-address", help="ZIP-32 path of an Orchard shielded address.") +@click.option( + "-w", "--network", + type=click.Choice(["mainnet", "testnet"]), + default="mainnet", +) +@with_client +def get_ivk(client, z_address, network): + """Get Zcash Orchard Incoming Viewing Key.""" + ivk = zcash.get_ivk(client, tools.parse_path(z_address), _parse_network(network)) + return ivk.hex() + +@cli.command(help="""Example:\n +trezorctl zcash get-address -d -t m/44h/133h/0h/0/0 -z m/32h/133h/0h -j 0 +""") +@click.option("-t", "--t-address", help="BIP-32 path of a P2PKH transparent address.") +@click.option("-z", "--z-address", help="ZIP-32 path of an Orchard shielded address.") +@click.option("-j", "--diversifier-index", default=0, type=int, help="diversifier index of the shielded address.") +@click.option("-d", "--show-display", is_flag=True) +@click.option( + "-w", "--network", + type=click.Choice(["mainnet", "testnet"]), + default="mainnet", +) +@with_client +def get_address(client, t_address, z_address, diversifier_index, show_display, network): + """Get Zcash address.""" + if not t_address and not z_address: + return """Specify address path using -t (transparent) and -z (shielded) arguments.\nYou can use both to get Zcash unified address.""" + + kwargs = dict() + kwargs["show_display"] = show_display + if t_address: + kwargs["t_address_n"] = tools.parse_path(t_address) + if z_address: + kwargs["z_address_n"] = tools.parse_path(z_address) + kwargs["diversifier_index"] = diversifier_index + + kwargs["coin_name"] = _parse_network(network) + + try: + return zcash.get_address(client, **kwargs) + except ValueError as e: + return str(e) diff --git a/python/src/trezorlib/messages.py b/python/src/trezorlib/messages.py index 1c3cd854d21..7651c2c058a 100644 --- a/python/src/trezorlib/messages.py +++ b/python/src/trezorlib/messages.py @@ -257,6 +257,16 @@ class MessageType(IntEnum): WebAuthnCredentials = 801 WebAuthnAddResidentCredential = 802 WebAuthnRemoveResidentCredential = 803 + ZcashGetFullViewingKey = 893 + ZcashFullViewingKey = 894 + ZcashGetIncomingViewingKey = 895 + ZcashIncomingViewingKey = 896 + ZcashGetAddress = 897 + ZcashAddress = 898 + ZcashOrchardBundleInfo = 899 + ZcashOrchardInput = 900 + ZcashOrchardOutput = 901 + ZcashAck = 907 class FailureType(IntEnum): @@ -308,6 +318,11 @@ class PinMatrixRequestType(IntEnum): WipeCodeSecond = 5 +class ZcashSignatureType(IntEnum): + TRANSPARENT = 0 + ORCHARD_SPEND_AUTH = 3 + + class InputScriptType(IntEnum): SPENDADDRESS = 0 SPENDMULTISIG = 1 @@ -348,6 +363,9 @@ class RequestType(IntEnum): TXORIGINPUT = 5 TXORIGOUTPUT = 6 TXPAYMENTREQ = 7 + TXORCHARDOUTPUT = 8 + TXORCHARDINPUT = 9 + NO_OP = 10 class CardanoDerivationType(IntEnum): @@ -933,6 +951,155 @@ def __init__( self.private_key = private_key +class ZcashGetFullViewingKey(protobuf.MessageType): + MESSAGE_WIRE_TYPE = 893 + FIELDS = { + 2: protobuf.Field("coin_name", "string", repeated=False, required=False), + 3: protobuf.Field("z_address_n", "uint32", repeated=True, required=False), + } + + def __init__( + self, + *, + z_address_n: Optional[Sequence["int"]] = None, + coin_name: Optional["str"] = 'Zcash', + ) -> None: + self.z_address_n: Sequence["int"] = z_address_n if z_address_n is not None else [] + self.coin_name = coin_name + + +class ZcashFullViewingKey(protobuf.MessageType): + MESSAGE_WIRE_TYPE = 894 + FIELDS = { + 1: protobuf.Field("fvk", "bytes", repeated=False, required=True), + } + + def __init__( + self, + *, + fvk: "bytes", + ) -> None: + self.fvk = fvk + + +class ZcashGetIncomingViewingKey(protobuf.MessageType): + MESSAGE_WIRE_TYPE = 895 + FIELDS = { + 1: protobuf.Field("coin_name", "string", repeated=False, required=False), + 2: protobuf.Field("z_address_n", "uint32", repeated=True, required=False), + } + + def __init__( + self, + *, + z_address_n: Optional[Sequence["int"]] = None, + coin_name: Optional["str"] = 'Zcash', + ) -> None: + self.z_address_n: Sequence["int"] = z_address_n if z_address_n is not None else [] + self.coin_name = coin_name + + +class ZcashIncomingViewingKey(protobuf.MessageType): + MESSAGE_WIRE_TYPE = 896 + FIELDS = { + 1: protobuf.Field("ivk", "bytes", repeated=False, required=True), + } + + def __init__( + self, + *, + ivk: "bytes", + ) -> None: + self.ivk = ivk + + +class ZcashGetAddress(protobuf.MessageType): + MESSAGE_WIRE_TYPE = 897 + FIELDS = { + 1: protobuf.Field("coin_name", "string", repeated=False, required=False), + 2: protobuf.Field("t_address_n", "uint32", repeated=True, required=False), + 3: protobuf.Field("z_address_n", "uint32", repeated=True, required=False), + 4: protobuf.Field("diversifier_index", "uint64", repeated=False, required=False), + 5: protobuf.Field("show_display", "bool", repeated=False, required=False), + } + + def __init__( + self, + *, + t_address_n: Optional[Sequence["int"]] = None, + z_address_n: Optional[Sequence["int"]] = None, + coin_name: Optional["str"] = 'Zcash', + diversifier_index: Optional["int"] = 0, + show_display: Optional["bool"] = False, + ) -> None: + self.t_address_n: Sequence["int"] = t_address_n if t_address_n is not None else [] + self.z_address_n: Sequence["int"] = z_address_n if z_address_n is not None else [] + self.coin_name = coin_name + self.diversifier_index = diversifier_index + self.show_display = show_display + + +class ZcashAddress(protobuf.MessageType): + MESSAGE_WIRE_TYPE = 898 + FIELDS = { + 1: protobuf.Field("address", "string", repeated=False, required=False), + } + + def __init__( + self, + *, + address: Optional["str"] = None, + ) -> None: + self.address = address + + +class ZcashOrchardInput(protobuf.MessageType): + MESSAGE_WIRE_TYPE = 900 + FIELDS = { + 1: protobuf.Field("recipient", "bytes", repeated=False, required=True), + 2: protobuf.Field("value", "uint64", repeated=False, required=True), + 3: protobuf.Field("rho", "bytes", repeated=False, required=True), + 4: protobuf.Field("rseed", "bytes", repeated=False, required=True), + } + + def __init__( + self, + *, + recipient: "bytes", + value: "int", + rho: "bytes", + rseed: "bytes", + ) -> None: + self.recipient = recipient + self.value = value + self.rho = rho + self.rseed = rseed + + +class ZcashOrchardOutput(protobuf.MessageType): + MESSAGE_WIRE_TYPE = 901 + FIELDS = { + 1: protobuf.Field("address", "string", repeated=False, required=False), + 2: protobuf.Field("amount", "uint64", repeated=False, required=True), + 3: protobuf.Field("memo", "string", repeated=False, required=False), + } + + def __init__( + self, + *, + amount: "int", + address: Optional["str"] = None, + memo: Optional["str"] = None, + ) -> None: + self.amount = amount + self.address = address + self.memo = memo + + +class ZcashAck(protobuf.MessageType): + MESSAGE_WIRE_TYPE = 907 + + class MultisigRedeemScriptType(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { @@ -1172,6 +1339,7 @@ class SignTx(protobuf.MessageType): 10: protobuf.Field("branch_id", "uint32", repeated=False, required=False), 11: protobuf.Field("amount_unit", "AmountUnit", repeated=False, required=False), 12: protobuf.Field("decred_staking_ticket", "bool", repeated=False, required=False), + 13: protobuf.Field("orchard", "ZcashOrchardBundleInfo", repeated=False, required=False), } def __init__( @@ -1189,6 +1357,7 @@ def __init__( branch_id: Optional["int"] = None, amount_unit: Optional["AmountUnit"] = AmountUnit.BITCOIN, decred_staking_ticket: Optional["bool"] = False, + orchard: Optional["ZcashOrchardBundleInfo"] = None, ) -> None: self.outputs_count = outputs_count self.inputs_count = inputs_count @@ -1202,6 +1371,7 @@ def __init__( self.branch_id = branch_id self.amount_unit = amount_unit self.decred_staking_ticket = decred_staking_ticket + self.orchard = orchard class TxRequest(protobuf.MessageType): @@ -1630,6 +1800,29 @@ def __init__( self.node = node +class ZcashOrchardBundleInfo(protobuf.MessageType): + MESSAGE_WIRE_TYPE = 899 + FIELDS = { + 1: protobuf.Field("inputs_count", "uint32", repeated=False, required=True), + 2: protobuf.Field("outputs_count", "uint32", repeated=False, required=True), + 3: protobuf.Field("anchor", "bytes", repeated=False, required=True), + 7: protobuf.Field("account", "uint32", repeated=False, required=False), + } + + def __init__( + self, + *, + inputs_count: "int", + outputs_count: "int", + anchor: "bytes", + account: Optional["int"] = 0, + ) -> None: + self.inputs_count = inputs_count + self.outputs_count = outputs_count + self.anchor = anchor + self.account = account + + class TxRequestDetailsType(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { @@ -1659,6 +1852,8 @@ class TxRequestSerializedType(protobuf.MessageType): 1: protobuf.Field("signature_index", "uint32", repeated=False, required=False), 2: protobuf.Field("signature", "bytes", repeated=False, required=False), 3: protobuf.Field("serialized_tx", "bytes", repeated=False, required=False), + 4: protobuf.Field("signature_type", "uint32", repeated=False, required=False), + 8: protobuf.Field("zcash_shielding_seed", "bytes", repeated=False, required=False), } def __init__( @@ -1667,10 +1862,14 @@ def __init__( signature_index: Optional["int"] = None, signature: Optional["bytes"] = None, serialized_tx: Optional["bytes"] = None, + signature_type: Optional["int"] = None, + zcash_shielding_seed: Optional["bytes"] = None, ) -> None: self.signature_index = signature_index self.signature = signature self.serialized_tx = serialized_tx + self.signature_type = signature_type + self.zcash_shielding_seed = zcash_shielding_seed class TransactionType(protobuf.MessageType): diff --git a/python/src/trezorlib/zcash.py b/python/src/trezorlib/zcash.py new file mode 100644 index 00000000000..2567a1bfb30 --- /dev/null +++ b/python/src/trezorlib/zcash.py @@ -0,0 +1,200 @@ +# This file is part of the Trezor project. +# +# Copyright (C) 2012-2019 SatoshiLabs and contributors +# +# This library is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version 3 +# as published by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the License along with this library. +# If not, see . + +from . import exceptions, messages +from .messages import ZcashSignatureType as SigType +from .tools import expect + + +@expect(messages.ZcashFullViewingKey, field="fvk") +def get_fvk(client, z_address_n, coin_name="Zcash",): + """ + Returns raw Zcash Orchard Full Viewing Key encoded as: + + ak (32 bytes) || nk (32 bytes) || rivk (32 bytes) + + acording to the https://zips.z.cash/protocol/protocol.pdf § 5.6.4.4 + """ + return client.call( + messages.ZcashGetFullViewingKey( + z_address_n=z_address_n, + coin_name=coin_name, + ) + ) + + +@expect(messages.ZcashIncomingViewingKey, field="ivk") +def get_ivk(client, z_address_n, coin_name = "Zcash",): + """ + Returns raw Zcash Orchard Incoming Viewing Key encoded as: + + dk (32 bytes) || ivk (32 bytes) + + acording to the https://zips.z.cash/protocol/protocol.pdf § 5.6.4.3 + """ + return client.call( + messages.ZcashGetIncomingViewingKey( + z_address_n=z_address_n, + coin_name=coin_name, + ) + ) + + +@expect(messages.ZcashAddress, field="address") +def get_address( + client, + t_address_n=[], + z_address_n=[], + diversifier_index=0, + show_display=False, + coin_name = "Zcash", +): + """ + Returns a Zcash address. + """ + return client.call( + messages.ZcashGetAddress( + t_address_n=t_address_n, + z_address_n=z_address_n, + diversifier_index=diversifier_index, + show_display=show_display, + coin_name=coin_name, + ) + ) + + +def encode_memo(memo): + encoded = memo.encode("utf-8") + if len(encoded) < 512: + raise ValueError("Memo is too long.") + return encoded + (512 - len(encoded))*b"\x00" + + +EMPTY_ANCHOR = bytes.fromhex("ae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82f") + + +def sign_tx( + client, + t_inputs = [], + t_outputs = [], + o_inputs = [], + o_outputs = [], + coin_name = "Zcash", + version_group_id = 0x26A7270A, # protocol spec §7.1.2 + branch_id = 0xC2D6D0B4, # https://zips.z.cash/zip-0252 + expiry = 0, + anchor = EMPTY_ANCHOR, + verbose = False, +): + def log(*args, **kwargs): + if verbose: + print(*args, **kwargs) + + msg = messages.SignTx() + + msg.inputs_count = len(t_inputs) + msg.outputs_count = len(t_outputs) + msg.coin_name = coin_name + msg.version = 5 + msg.version_group_id = version_group_id + msg.branch_id = branch_id + msg.expiry = expiry + + orchard = messages.ZcashOrchardBundleInfo() + orchard.outputs_count = len(o_outputs) + orchard.inputs_count = len(o_inputs) + orchard.anchor = anchor + orchard.enable_spends = True + orchard.enable_outputs = True + + msg.orchard = orchard + Zcash Shielded Payments #2472 + if o_inputs or o_outputs: + actions_count = max(2, len(o_inputs), len(o_outputs)) + else: + actions_count = 0 + + signatures = { + SigType.TRANSPARENT: [None] * len(t_inputs), + SigType.ORCHARD_SPEND_AUTH: [None] * actions_count, + } + + serialized_tx = b"" + + print("T <- sign tx") + res = client.call(msg) + + R = messages.RequestType + while isinstance(res, messages.TxRequest): + # If there's some part of signed transaction, let's add it + if res.serialized: + if res.serialized.serialized_tx: + log("T -> serialized tx ({} bytes)".format(len(res.serialized.serialized_tx))) + serialized_tx += res.serialized.serialized_tx + + if res.serialized.signature_index is not None: + idx = res.serialized.signature_index + sig = res.serialized.signature + sig_type = res.serialized.signature_type + if signatures[sig_type][idx] is not None: + raise ValueError("Signature for index {} already filled".format(idx)) + log("T -> {} signature {}".format( + { + SigType.TRANSPARENT: "t", + SigType.ORCHARD_SPEND_AUTH: "o auth", + }[sig_type], + idx) + ) + signatures[sig_type][idx] = sig + + + log("") + + if res.request_type == R.TXFINISHED: + break + + elif res.request_type == R.TXINPUT: + log("T <- t input", res.details.request_index) + msg = messages.TransactionType() + msg.inputs = [t_inputs[res.details.request_index]] + res = client.call(messages.TxAck(tx=msg)) + + elif res.request_type == R.TXOUTPUT: + log("T <- t output", res.details.request_index) + msg = messages.TransactionType() + msg.outputs = [t_outputs[res.details.request_index]] + res = client.call(messages.TxAck(tx=msg)) + + elif res.request_type == R.TXORCHARDINPUT: + txi = o_inputs[res.details.request_index] + log("T <- o input ", res.details.request_index) + res = client.call(txi) + + elif res.request_type == R.TXORCHARDOUTPUT: + txo = o_outputs[res.details.request_index] + log("T <- o output", res.details.request_index) + res = client.call(txo) + + elif res.request_type == R.NO_OP: + res = client.call(messages.ZcashAck()) + + else: + raise ValueError("unexpected request type: {}".format(res.request_type)) + + if not isinstance(res, messages.TxRequest): + raise exceptions.TrezorException("Unexpected message") + + return signatures, serialized_tx