1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-07-04 05:42:34 +00:00

feat(core): add Zcash shielded transactions

This commit is contained in:
Tomas Krnak 2022-11-17 11:58:19 +07:00
parent 6311675119
commit ac73bfaf1e
64 changed files with 3789 additions and 158 deletions

View File

@ -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,11 @@ 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 uint32 orchard_inputs_count = 13 [default = 0]; // only for Zcash, number of Orchard inputs
optional uint32 orchard_outputs_count = 14 [default = 0]; // only for Zcash, number of Orchard outputs
optional bytes orchard_anchor = 15; // only for Zcash, a root of Orchard Merkle tree
optional uint32 account = 16 [default = 0]; // only for Zcash
}
/**
@ -211,6 +217,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 +236,9 @@ message TxRequest {
TXORIGINPUT = 5;
TXORIGOUTPUT = 6;
TXPAYMENTREQ = 7;
TXORCHARDOUTPUT = 8;
TXORCHARDINPUT = 9;
NO_OP = 10;
}
/**
* Structure representing request details
@ -245,6 +256,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 zcash.ZcashSignatureType signature_type = 4; // for Zcash only
optional bytes zcash_shielding_seed = 5; // for Zcash only
optional bytes tx_sighash = 6; // for Zcash only
}
}
@ -607,4 +622,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
}

View File

@ -0,0 +1,89 @@
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";
import "messages.proto";
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 Viewing Key.
* If field `full` is true, then Full Viewing Key will be returned.
* Otherwise Incoming Viewing Key will be returned.
*
* @start
* @next Failure
* @next ZcashViewingKey
*/
message ZcashGetViewingKey {
optional string coin_name = 1 [default = "Zcash"];
repeated uint32 z_address_n = 2; // z-address ZIP 32 path
optional bool full = 3 [default = true]; // true -> Full Viewing Key requested
// false -> Incoming Viewing Key requested
}
/**
* Response: Contains unified Full/Incoming Viewing Key.
* @end
*/
message ZcashViewingKey {
required string key = 1;
}
/**
* Request: Ask device for a 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 {
option (wire_type) = 22;
}

View File

@ -357,4 +357,12 @@ enum MessageType {
MessageType_WebAuthnCredentials = 801 [(wire_out) = true];
MessageType_WebAuthnAddResidentCredential = 802 [(wire_in) = true];
MessageType_WebAuthnRemoveResidentCredential = 803 [(wire_in) = true];
// Zcash
MessageType_ZcashGetAddress = 900 [(wire_in) = true];
MessageType_ZcashAddress = 901 [(wire_out) = true];
MessageType_ZcashGetViewingKey = 902 [(wire_in) = true];
MessageType_ZcashViewingKey = 903 [(wire_out) = true];
MessageType_ZcashOrchardInput = 906 [(wire_in) = true];
MessageType_ZcashOrchardOutput = 907 [(wire_in) = true];
}

View File

@ -0,0 +1 @@
Add Zcash shielded transactions

View File

@ -9,8 +9,8 @@ TREZOR_MODEL = ARGUMENTS.get('TREZOR_MODEL', 'T')
UI2 = ARGUMENTS.get('UI2', '0') == '1' or TREZOR_MODEL in ('1', 'R')
FEATURE_FLAGS = {
"RDI": True,
"SECP256K1_ZKP": True, # required for trezor.crypto.curve.bip340 (BIP340/Taproot)
"RDI": False,
"SECP256K1_ZKP": False, # required for trezor.crypto.curve.bip340 (BIP340/Taproot)
"SYSTEM_VIEW": False,
"ZCASH_SHIELDED": False,
}
@ -674,7 +674,12 @@ if FROZEN:
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/tezos/*.py'))
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',
exclude=[
SOURCE_PY_DIR + 'apps/zcash/get_address.py',
SOURCE_PY_DIR + 'apps/zcash/get_viewing_key.py',
])
)
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/webauthn/*.py'))
@ -683,7 +688,13 @@ if FROZEN:
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/bitcoin/sign_tx/zcash_v4.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/enums/Zcash*.py'))
source_mpy = env.FrozenModule(source=SOURCE_PY, source_dir=SOURCE_PY_DIR, bitcoin_only=BITCOIN_ONLY, zcash_shielded=FEATURE_FLAGS['ZCASH_SHIELDED'])
if FEATURE_FLAGS["ZCASH_SHIELDED"]:
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/zcash/orchard/*.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/zcash/orchard/*/*.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/zcash/get_address.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/zcash/get_viewing_key.py'))
source_mpy = env.FrozenModule(source=SOURCE_PY, source_dir=SOURCE_PY_DIR, bitcoin_only=BITCOIN_ONLY)
source_mpyc = env.FrozenCFile(
target='frozen_mpy.c', source=source_mpy, qstr_header=qstr_preprocessed)

View File

@ -630,7 +630,12 @@ if FROZEN:
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/tezos/*.py'))
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',
exclude=[
SOURCE_PY_DIR + 'apps/zcash/get_address.py',
SOURCE_PY_DIR + 'apps/zcash/get_viewing_key.py',
])
)
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/webauthn/*.py'))
@ -639,7 +644,13 @@ if FROZEN:
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/bitcoin/sign_tx/zcash_v4.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/enums/Zcash*.py'))
source_mpy = env.FrozenModule(source=SOURCE_PY, source_dir=SOURCE_PY_DIR, bitcoin_only=BITCOIN_ONLY, zcash_shielded=FEATURE_FLAGS['ZCASH_SHIELDED'])
if FEATURE_FLAGS["ZCASH_SHIELDED"]:
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/zcash/orchard/*.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/zcash/orchard/*/*.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/zcash/get_address.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/zcash/get_viewing_key.py'))
source_mpy = env.FrozenModule(source=SOURCE_PY, source_dir=SOURCE_PY_DIR, bitcoin_only=BITCOIN_ONLY)
source_mpyc = env.FrozenCFile(
target='frozen_mpy.c', source=source_mpy, qstr_header=qstr_preprocessed)

View File

@ -50,7 +50,6 @@ SECTIONS {
build/firmware/vendor/secp256k1-zkp/src/precomputed_ecmult_gen.o(.rodata*);
. = ALIGN(512);
} >FLASH2 AT>FLASH2
.flash : ALIGN(512) {
KEEP(*(.vector_table));
. = ALIGN(4);

View File

@ -196,8 +196,8 @@ dependencies = [
[[package]]
name = "pasta_curves"
version = "0.4.0"
source = "git+https://github.com/jarys/pasta_curves?rev=a4f755013aad344982383c9f5af362697d928325#a4f755013aad344982383c9f5af362697d928325"
version = "0.4.1"
source = "git+https://github.com/jarys/pasta_curves?rev=e11dfe4089d0da24483094a99cceb89f32974c17#e11dfe4089d0da24483094a99cceb89f32974c17"
dependencies = [
"blake2b_simd",
"ff",

View File

@ -63,8 +63,9 @@ default_features = false
[dependencies.pasta_curves]
optional = true
version = "0.4.0"
version = "0.4.1"
default-features = false
features = ["uninline-portable"]
# Build dependencies
@ -88,4 +89,4 @@ path = "./blake2b_hal"
[patch.crates-io.pasta_curves]
git = "https://github.com/jarys/pasta_curves"
rev = "a4f755013aad344982383c9f5af362697d928325"
rev = "e11dfe4089d0da24483094a99cceb89f32974c17"

View File

@ -9,17 +9,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."""

View File

@ -39,9 +39,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::<Wrapped<Point>>::try_from(other)?;
let other = other.deref().inner();
(other * this).wrap()
let point = Gc::<Wrapped<Point>>::try_from(other);
if point.is_ok() {
let point = point.unwrap();
let point = point.deref().inner();
(point * this).wrap()
} else {
let scalar = Gc::<Wrapped<Scalar>>::try_from(other)?;
let scalar = scalar.deref().inner();
(this * scalar).wrap()
}
}
ffi::mp_binary_op_t_MP_BINARY_OP_ADD => {
let other = Gc::<Wrapped<Scalar>>::try_from(other)?;

View File

@ -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
@ -40,14 +37,14 @@ class Scalar:
...
def to_bytes(self) -> bytes:
...
def is_not_zero(self) -> bool:
...
def __mul__(self, other: Point) -> Point:
...
def __add__(self, other: Scalar) -> Scalar:
...
def __neg__(self) -> Point:
...
def __bool__(self) -> bool:
...
# rust/src/zcash_primitives/pallas/mod.rs

View File

@ -81,6 +81,8 @@ trezor.crypto.der
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 @@ if not utils.BITCOIN_ONLY:
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,14 +781,56 @@ if not utils.BITCOIN_ONLY:
import apps.webauthn.resident_credentials
apps.zcash
import apps.zcash
apps.zcash.approver
import apps.zcash.approver
apps.zcash.f4jumble
import apps.zcash.f4jumble
apps.zcash.get_address
import apps.zcash.get_address
apps.zcash.get_viewing_key
import apps.zcash.get_viewing_key
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.debug
import apps.zcash.orchard.debug
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
import apps.zcash.unified_addresses
apps.zcash.unified
import apps.zcash.unified
# generate full alphabet
a

View File

@ -202,7 +202,10 @@ class BasicApprover(Approver):
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

View File

@ -371,6 +371,9 @@ 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.tx_sighash = None
tx_req.serialized.zcash_shielding_seed = None
# Data sanitizers

View File

@ -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)

View File

@ -181,6 +181,12 @@ def find_message_handler_module(msg_type: int) -> str:
if msg_type == MessageType.BinanceSignTx:
return "apps.binance.sign_tx"
if utils.ZCASH_SHIELDED: # zcash shielded
if msg_type == MessageType.ZcashGetViewingKey:
return "apps.zcash.get_viewing_key"
if msg_type == MessageType.ZcashGetAddress:
return "apps.zcash.get_address"
raise ValueError

View File

@ -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))

View File

@ -0,0 +1,79 @@
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 import Typecode, encode_address
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:
if not (msg.t_address_n or msg.z_address_n):
raise wire.DataError("t-address or z-address path expected")
coin = by_name(msg.coin_name)
if msg.z_address_n:
receivers = {}
receivers[Typecode.ORCHARD] = await get_raw_orchard_address(ctx, msg)
if msg.t_address_n:
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_address(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()

View File

@ -0,0 +1,46 @@
from typing import TYPE_CHECKING
from trezor import ui
from trezor.enums import ButtonRequestType
from trezor.messages import ZcashGetViewingKey, ZcashViewingKey
from trezor.ui.layouts import confirm_action
from apps.common import coininfo
from .orchard.keychain import with_keychain
from .unified import Typecode, encode_fvk, encode_ivk
if TYPE_CHECKING:
from trezor.wire import Context
from .orchard.keychain import OrchardKeychain
@with_keychain
async def get_viewing_key(
ctx: Context, msg: ZcashGetViewingKey, keychain: OrchardKeychain
) -> ZcashViewingKey:
await require_confirm_export_viewing_key(ctx, msg)
coin = coininfo.by_name(msg.coin_name)
fvk = keychain.derive(msg.z_address_n).full_viewing_key()
if msg.full: # Full Viewing Key
receivers = {Typecode.ORCHARD: fvk.to_bytes()}
key = encode_fvk(receivers, coin)
else: # Incoming Viewing Key
receivers = {Typecode.ORCHARD: fvk.incoming_viewing_key()}
key = encode_ivk(receivers, coin)
return ZcashViewingKey(key=key)
async def require_confirm_export_viewing_key(
ctx: Context, msg: ZcashGetViewingKey
) -> None:
key_type = "Full" if msg.full else "Incoming"
await confirm_action(
ctx,
"export_viewing_key",
"Confirm export",
description="Do you really want to export %s Viewing Key?" % key_type,
icon=ui.ICON_SEND,
icon_color=ui.GREEN,
br_code=ButtonRequestType.SignTx,
)

View File

@ -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
"""
@ -8,9 +8,11 @@ specification: https://zips.z.cash/zip-0244
from typing import TYPE_CHECKING
from trezor.crypto.hashlib import blake2b
from trezor.utils import HashWriter, empty_bytearray
from trezor.utils import ZCASH_SHIELDED, 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,8 +20,6 @@ from apps.bitcoin.writers import (
write_bytes_reversed,
write_tx_output,
write_uint8,
write_uint32,
write_uint64,
)
if TYPE_CHECKING:
@ -27,6 +27,10 @@ if TYPE_CHECKING:
from trezor.utils import Writer
from apps.common.coininfo import CoinInfo
from typing import Sequence
from enum import IntEnum
from .orchard.crypto.builder import Action
else:
IntEnum = object
def write_hash(w: Writer, hash: bytes) -> None:
@ -38,6 +42,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 +67,6 @@ class ZcashHasher:
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 +79,10 @@ class ZcashHasher:
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 +148,6 @@ class HeaderHasher:
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 +175,28 @@ class TransparentHasher:
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 +211,10 @@ class TransparentHasher:
"""
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 +239,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 +256,67 @@ 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()
class OrchardHasherState(IntEnum):
EMPTY = 0
ADDING_ACTIONS = 1
FINALIZED = 2
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 = OrchardHasherState.EMPTY
if ZCASH_SHIELDED:
def add_action(self, action: Action) -> None:
assert self.state in (
OrchardHasherState.EMPTY,
OrchardHasherState.ADDING_ACTIONS,
)
self.state = OrchardHasherState.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 == OrchardHasherState.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 = OrchardHasherState.FINALIZED
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 (OrchardHasherState.EMPTY, OrchardHasherState.FINALIZED)
return self.h.get_digest()

View File

@ -0,0 +1,140 @@
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_address, confirm_metadata
from trezor.ui.layouts.tt import Confirm, interact, raise_if_cancelled
from trezor.utils import chunks, chunks_intersperse, ensure
from apps.bitcoin.sign_tx.helpers import UiConfirm
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
class ConfirmOrchardInputsCountOverThreshold(UiConfirm):
def __init__(self, orchard_inputs_count):
self.orchard_inputs_count = orchard_inputs_count
def confirm_dialog(self, ctx: Context) -> Awaitable[Any]:
return confirm_metadata(
ctx,
"orchard_inputs_count_over_threshold",
"Warning",
"There are {}\nshielded inputs.",
str(self.orchard_inputs_count),
ButtonRequestType.SignTx,
)
def _format_amount(value: int, coin: CoinInfo) -> str:
return "%s %s" % (strings.format_amount(value, 8), coin.coin_shortcut)
class UiConfirmTransparentOutput(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, ButtonRequestType.ConfirmOutput
)
class UiConfirmOrchardOutput(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, ButtonRequestType.ConfirmOutput
)
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, br_code: ButtonRequestType
) -> 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",
br_code,
)
)
if result is SHOW_PAGINATED:
await confirm_address(
ctx,
"Confirm address",
full_address,
description=None,
)
else:
assert result is CONFIRMED
break

View File

View File

@ -0,0 +1,45 @@
import gc
from typing import TYPE_CHECKING
from trezor import protobuf
from trezor.crypto import aes
from trezor.crypto.hashlib import sha256
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, key: bytes) -> None:
self.key = key
self.state = EMPTY
def xor_message(self, msg: MessageType, index: int) -> None:
gc.collect()
cipher = aes(aes.ECB, self.key)
# compute mask
assert msg.MESSAGE_WIRE_TYPE is not None
mask_preimage = bytearray(32)
mask_preimage[0:2] = msg.MESSAGE_WIRE_TYPE.to_bytes(2, "big")
mask_preimage[2:6] = index.to_bytes(4, "little")
mask = cipher.encrypt(mask_preimage)
msg_digest = sha256(protobuf.dump_message_buffer(msg)).digest()
prp_input = xor(mask, msg_digest)
prp_output = cipher.encrypt(prp_input)
self.state = xor(self.state, prp_output)
gc.collect()
def check(self) -> None:
if self.state != EMPTY:
raise ProcessError("Message changed.")

View File

@ -0,0 +1,39 @@
from typing import TYPE_CHECKING
from trezor.crypto.pallas import Point, group_hash
if TYPE_CHECKING:
from trezor.crypto.pallas import Scalar
# 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)

View File

@ -0,0 +1,126 @@
import gc
from typing import TYPE_CHECKING
from trezor.crypto.pallas import Point, Scalar, scalar_from_i64
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:
gc.collect()
# 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()
# value commitment
v_net = input.note.value - output.value
rcv = rng.rcv()
cv_net = commit_value(rcv, v_net)
# note encryption
gc.collect()
encrypted_note = encrypt_note(
note,
output.memo,
cv_net,
cm_new,
output.ovk,
rng,
)
gc.collect()
action = 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,
)
gc.collect()
return action

View File

@ -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
<http://dx.doi.org/10.6028/NIST.SP.800-38G>
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)

View File

@ -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"
)

View File

@ -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) # ask != 0
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) # ask != 0
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) # ivk != 0
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()

View File

@ -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")

View File

@ -0,0 +1,120 @@
import gc
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) # esk != 0
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)
gc.collect()
return TransmittedNoteCiphertext(
epk_bytes=ephemeral_key,
enc_ciphertext=bytes(np),
out_ciphertext=bytes(op),
)

View File

@ -0,0 +1,40 @@
# 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
from .utils import xor
if TYPE_CHECKING:
from trezor.crypto.pallas import Scalar
from ..random import ActionShieldingRng
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, rng: ActionShieldingRng) -> 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 = xor(rng.spend_auth_T(), sk.to_bytes())
# - `rng.spend_auth_T()` randomizes the signature
# - xoring with bytes of `sk` makes `T` unpredictable for outside
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

View File

@ -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

View File

@ -0,0 +1,74 @@
from typing import Iterable
from trezor.crypto.hashlib import blake2b
from trezor.crypto.pallas import Fp, Scalar
def xor(a: bytes, b: bytes) -> bytes:
return bytes(x ^ y for x, y in zip(a, b))
# 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

View File

@ -0,0 +1,53 @@
# pyright: reportGeneralTypeIssues=false
import gc
if __debug__:
from trezor import log
def log_gc(label=""):
gc.collect()
log_gc_2(label)
def log_gc_2(label=""):
log.info(
__name__,
"GC[%s]: alloc: %d kb, free: %d kb (%d/1000)",
label,
gc.mem_alloc() // 1000, # type: ignore
gc.mem_free() // 1000, # type: ignore
(1000 * gc.mem_free()) // (gc.mem_free() + gc.mem_alloc()), # type: ignore
)
def trace_gc(x):
gc.collect()
log_gc_2("trace")
gc.collect()
return x
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

View File

@ -0,0 +1,109 @@
"""
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)
@staticmethod
def from_seed_and_coin(seed: bytes, coin: CoinInfo) -> "OrchardKeychain":
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

View File

@ -0,0 +1,109 @@
from micropython import const
from typing import TYPE_CHECKING
from trezor.crypto.hashlib import blake2b
from trezor.crypto.pallas import to_base, to_scalar
from trezor.utils import chunks
from .crypto.address import Address
from .crypto.keys import sk_to_ask
if TYPE_CHECKING:
from typing import Any, Iterable
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"ActionShieldSeed", outlen=32)
h.update(self.seed)
h.update(i.to_bytes(4, "little"))
return ActionShieldingRng(h.digest())
def shuffle_inputs(self, inputs: list[int | None]) -> None:
rng = self._blake2b_ctr_mode_rng(personal=b"Inps_Permutation")
_shuffle(inputs, rng)
def shuffle_outputs(self, outputs: list[int | None]) -> None:
rng = self._blake2b_ctr_mode_rng(personal=b"Outs_Permutation")
_shuffle(outputs, rng)
def _blake2b_ctr_mode_rng(self, personal: bytes) -> Iterable[int]:
i = 0
while True:
h = blake2b(personal=personal, outlen=64)
h.update(self.seed)
h.update(i.to_bytes(4, "little"))
digest = h.digest()
for chunk in chunks(digest, 4):
yield int.from_bytes(chunk, "little")
i += 1
MAX = const(0xFFFF_FFFF)
def _sample_uniform(n, rng):
"""Samples unifomly an element of `range(n)`."""
while True:
wide = next(rng) * n
high = wide >> 32
low = wide & MAX
if low <= MAX - n or low <= MAX - (MAX - n) % n:
return high
def _shuffle(x: list[Any], rng) -> None:
# Fisher-Yates shuffle
for i in range(len(x) - 1, 0, -1):
j = _sample_uniform(i + 1, rng)
x[i], x[j] = x[j], x[i]
class ActionShieldingRng:
def __init__(self, seed: bytes) -> None:
self.seed = seed
def random(self, dst: bytes, outlen: int = 64) -> bytes:
h = blake2b(personal=b"ActionExpandSeed", 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"dummy_d", 11)
ivk = to_scalar(self.random(b"dummy_ivk"))
return Address.from_ivk(d, ivk)
def ock(self) -> bytes:
return self.random(b"dummy_ock", 32)
def op(self) -> bytes:
return self.random(b"dummy_op", 64)
def rseed_old(self) -> bytes:
return self.random(b"dummy_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"dummy_rho"))
def spend_auth_T(self) -> bytes:
return self.random(b"spend_auth_T", 32)

View File

@ -0,0 +1,300 @@
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
from ..hasher import ZcashHasher
from ..layout import ConfirmOrchardInputsCountOverThreshold
from .accumulator import MessageAccumulator
from .crypto import builder, redpallas
from .crypto.address import Address
from .crypto.note import Note
from .debug import watch_gc_async
from .keychain import OrchardKeychain
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 .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
MAX_SILENT_ORCHARD_INPUTS = const(8)
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:
def __init__(
self,
tx_info: TxInfo,
seed: bytes,
approver: ZcashApprover,
coin: CoinInfo,
tx_req: TxRequest,
) -> None:
assert tx_req.serialized is not None # typing
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 = OrchardKeychain.from_seed_and_coin(seed, coin)
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
account = tx_info.tx.account
assert account is not None # typing
key_path = [
32 | HARDENED, # ZIP-32 constant
coin.slip44 | HARDENED, # purpose
account | HARDENED, # account
]
self.key_node = self.keychain.derive(key_path)
self.msg_acc = MessageAccumulator(
self.keychain.derive_slip21(
[b"Zcash Orchard", b"Message Accumulator"],
).key()
)
self.rng = None
@skip_if_empty
async def process_inputs(self) -> None:
await self.check_orchard_inputs_count()
for i in range(self.inputs_count):
txi = await self.get_input(i)
self.msg_acc.xor_message(txi, i) # add message to the accumulator
self.approver.add_orchard_input(txi)
def check_orchard_inputs_count(self) -> Awaitable[None]: # type: ignore [awaitable-is-generator]
if self.inputs_count > MAX_SILENT_ORCHARD_INPUTS:
yield ConfirmOrchardInputsCountOverThreshold(self.inputs_count)
@skip_if_empty
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 the 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
async def compute_digest(self) -> None:
# derive 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__, "shielding 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)
log.info(__name__, "end shielding")
# check that message accumulator is empty
self.msg_acc.check()
# hash orchard footer
assert self.tx_info.tx.orchard_anchor 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:
assert self.tx_info.tx.orchard_anchor is not None # typing
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 the 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 the accumulator
if output_is_internal(txo):
fvk = fvk.internal()
address = fvk.address(0)
else:
assert txo.address is not None # typing
receivers = unified.decode_address(txo.address, self.coin)
address = receivers.get(unified.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()
self.set_sighash(sighash)
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):
if j is None:
continue
rng = self.rng.for_action(i)
rsk = redpallas.randomize(ask, rng.alpha())
signature = redpallas.sign_spend_auth(rsk, sighash, rng)
await self.set_serialized_signature(i, signature, sig_type)
def set_sighash(self, sighash: bytes) -> None:
assert self.tx_req.serialized is not None
self.tx_req.serialized.tx_sighash = sighash
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

View File

@ -1,19 +1,24 @@
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.utils import ZCASH_SHIELDED, ensure
from trezor.wire import DataError, ProcessError
from apps.bitcoin import scripts
from apps.bitcoin.common import ecdsa_sign
from apps.bitcoin.sign_tx.bitcoinlike import Bitcoinlike
from apps.common.paths import HARDENED
from apps.common.writers import write_compact_size, write_uint32_le
from . import unified_addresses
from . import unified
from .approver import ZcashApprover
from .hasher import ZcashHasher
from .unified_addresses import Typecode
from .unified import Typecode
if ZCASH_SHIELDED:
from .orchard.signer import OrchardSigner
if TYPE_CHECKING:
from typing import Sequence
@ -29,6 +34,7 @@ if TYPE_CHECKING:
)
from apps.bitcoin.keychain import Keychain
OVERWINTERED = const(0x8000_0000)
@ -44,8 +50,25 @@ class Zcash(Bitcoinlike):
if tx.version != 5:
raise DataError("Expected transaction version 5.")
assert approver is None
approver = ZcashApprover(tx, coin)
super().__init__(tx, keychain, coin, approver)
if ZCASH_SHIELDED:
self.orchard = OrchardSigner(
self.tx_info,
keychain.seed,
approver,
coin,
self.tx_req,
)
self.tx_info.wallet_path.attribute = [
44 | HARDENED, # BIP-44 constant
coin.slip44 | HARDENED,
tx.account | HARDENED,
]
def create_sig_hasher(self, tx: SignTx | PrevTx) -> ZcashHasher:
return ZcashHasher(tx)
@ -54,6 +77,16 @@ class Zcash(Bitcoinlike):
# so this should never be called.
raise NotImplementedError
async def step1_process_inputs(self):
await super().step1_process_inputs()
if ZCASH_SHIELDED:
await self.orchard.process_inputs()
async def step2_approve_outputs(self):
await super().step2_approve_outputs()
if ZCASH_SHIELDED:
await self.orchard.approve_outputs()
async def step3_verify_inputs(self) -> None:
# Replacement transactions are not supported.
@ -64,9 +97,31 @@ class Zcash(Bitcoinlike):
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):
if ZCASH_SHIELDED:
# 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
if ZCASH_SHIELDED:
write_compact_size(self.serialized_tx, self.orchard.actions_count)
else:
write_compact_size(self.serialized_tx, 0)
async def step6_sign_segwit_inputs(self):
# transparent inputs were signed in step 4
if ZCASH_SHIELDED:
await self.orchard.sign_inputs()
async def sign_nonsegwit_input(self, i_sign: int) -> None:
await self.sign_nonsegwit_bip143_input(i_sign)
@ -110,25 +165,27 @@ class Zcash(Bitcoinlike):
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
if txo.address is not None and txo.address[0] == "u":
assert txo.script_type is OutputScriptType.PAYTOADDRESS
receivers = unified_addresses.decode(txo.address, self.coin)
receivers = unified.decode_address(txo.address, self.coin)
if Typecode.P2PKH in receivers:
ensure(txo.script_type is OutputScriptType.PAYTOADDRESS)
pubkeyhash = receivers[Typecode.P2PKH]
return scripts.output_script_p2pkh(pubkeyhash)
if Typecode.P2SH in receivers:
ensure(txo.script_type is OutputScriptType.PAYTOSCRIPTHASH)
scripthash = receivers[Typecode.P2SH]
return scripts.output_script_p2sh(scripthash)
raise DataError("Unified address does not include a transparent receiver.")
# transparent addresses
return super().output_derive_script(txo)
def set_serialized_signature(self, i: int, signature: bytes) -> None:
super().set_serialized_signature(i, signature)
assert self.tx_req.serialized is not None
if ZCASH_SHIELDED:
self.tx_req.serialized.signature_type = ZcashSignatureType.TRANSPARENT

View File

@ -4,6 +4,7 @@ unified addresses according to the ZIP-316.
see: https://zips.z.cash/zip-0316
"""
import gc
from typing import TYPE_CHECKING
from trezor.crypto.bech32 import Encoding, bech32_decode, bech32_encode, convertbits
@ -29,7 +30,7 @@ class Typecode(IntEnum):
ORCHARD = 0x03
def receiver_length(typecode: int) -> int | None:
def address_receiver_length(typecode: int) -> int | None:
"""Byte length of a receiver."""
if typecode in (Typecode.P2PKH, Typecode.P2SH):
return 20
@ -38,21 +39,62 @@ def receiver_length(typecode: int) -> int | None:
return None
def prefix(coin: CoinInfo) -> str:
"""Prefix for a unified address."""
def fvk_prefix(coin: CoinInfo) -> str:
"""Prefix for a unified Full Viewing Key."""
if coin.coin_name == "Zcash":
return "u"
return "uview"
if coin.coin_name == "Zcash Testnet":
return "utest"
return "uviewtest"
raise ValueError
def padding(hrp: str) -> bytes:
def ivk_prefix(coin: CoinInfo) -> str:
"""Prefix for a unified Incoming Viewing Key."""
if coin.coin_name == "Zcash":
return "uivk"
if coin.coin_name == "Zcash Testnet":
return "uivktest"
raise ValueError
def padded(hrp: str) -> bytes:
assert len(hrp) <= 16
return hrp.encode() + bytes(16 - len(hrp))
def encode(receivers: dict[Typecode, bytes], coin: CoinInfo) -> str:
def encode_address(receivers: dict[Typecode, bytes], coin: CoinInfo) -> str:
if coin.coin_name == "Zcash":
hrp = "u"
elif coin.coin_name == "Zcash Testnet":
hrp = "utest"
else:
raise ValueError
for code, receiver in receivers.items():
assert len(receiver) == address_receiver_length(code)
return encode(receivers, hrp)
def encode_fvk(receivers: dict[Typecode, bytes], coin: CoinInfo) -> str:
if coin.coin_name == "Zcash":
hrp = "uview"
elif coin.coin_name == "Zcash Testnet":
hrp = "uviewtest"
else:
raise ValueError
return encode(receivers, hrp)
def encode_ivk(receivers: dict[Typecode, bytes], coin: CoinInfo) -> str:
if coin.coin_name == "Zcash":
hrp = "uivk"
elif coin.coin_name == "Zcash Testnet":
hrp = "uivktest"
else:
raise ValueError
return encode(receivers, hrp)
def encode(receivers: dict[Typecode, bytes], hrp: str) -> str:
# multiple transparent receivers forbidden
assert not (Typecode.P2PKH in receivers and Typecode.P2SH in receivers)
# at least one shielded address must be present
@ -70,35 +112,43 @@ def encode(receivers: dict[Typecode, bytes], coin: CoinInfo) -> str:
receivers_list.sort()
for (typecode, raw_bytes) in receivers_list:
length = receiver_length(typecode) or len(raw_bytes)
length = len(raw_bytes)
write_compact_size(w, typecode)
write_compact_size(w, length)
write_bytes_fixed(w, raw_bytes, length)
hrp = prefix(coin)
write_bytes_fixed(w, padding(hrp), 16)
write_bytes_fixed(w, padded(hrp), 16)
f4jumble(memoryview(w))
converted = convertbits(w, 8, 5)
return bech32_encode(hrp, converted, Encoding.BECH32M)
def decode(addr_str: str, coin: CoinInfo) -> dict[int, bytes]:
def decode_address(addr_str: str, coin: CoinInfo) -> dict[int, bytes]:
if coin.coin_name == "Zcash":
expected_hrp = "u"
elif coin.coin_name == "Zcash Testnet":
expected_hrp = "utest"
else:
raise ValueError
(hrp, data, encoding) = bech32_decode(addr_str, max_bech_len=1000)
if (hrp, data, encoding) == (None, None, None):
raise DataError("Bech32m decoding failed.")
assert hrp is not None # to satisfy typecheckers
assert data is not None # to satisfy typecheckers
assert encoding is not None # to satisfy typecheckers
if hrp != prefix(coin):
if hrp != expected_hrp:
raise DataError("Unexpected address prefix.")
if encoding != Encoding.BECH32M:
raise DataError("Bech32m encoding required.")
gc.collect()
decoded = bytearray(convertbits(data, 5, 8, False))
gc.collect()
f4unjumble(memoryview(decoded))
# check trailing padding bytes
if decoded[-16:] != padding(hrp):
if decoded[-16:] != padded(hrp):
raise DataError("Invalid padding bytes")
r = BufferReader(decoded[:-16])
@ -117,7 +167,7 @@ def decode(addr_str: str, coin: CoinInfo) -> dict[int, bytes]:
length = read_compact_size(r)
# if the typecode of the receiver is known, then verify receiver length
expected_length = receiver_length(typecode)
expected_length = address_receiver_length(typecode)
if expected_length is not None and length != expected_length:
raise DataError("Unexpected receiver length")

View File

@ -0,0 +1,68 @@
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

View File

@ -231,3 +231,9 @@ if not utils.BITCOIN_ONLY:
WebAuthnCredentials = 801
WebAuthnAddResidentCredential = 802
WebAuthnRemoveResidentCredential = 803
ZcashGetAddress = 900
ZcashAddress = 901
ZcashGetViewingKey = 902
ZcashViewingKey = 903
ZcashOrchardInput = 906
ZcashOrchardOutput = 907

View File

@ -10,3 +10,6 @@ TXEXTRADATA = 4
TXORIGINPUT = 5
TXORIGOUTPUT = 6
TXPAYMENTREQ = 7
TXORCHARDOUTPUT = 8
TXORCHARDINPUT = 9
NO_OP = 10

View File

@ -0,0 +1,6 @@
# Automatically generated by pb2py
# fmt: off
# isort:skip_file
TRANSPARENT = 0
ORCHARD_SPEND_AUTH = 3

View File

@ -249,6 +249,12 @@ if TYPE_CHECKING:
WebAuthnCredentials = 801
WebAuthnAddResidentCredential = 802
WebAuthnRemoveResidentCredential = 803
ZcashGetAddress = 900
ZcashAddress = 901
ZcashGetViewingKey = 902
ZcashViewingKey = 903
ZcashOrchardInput = 906
ZcashOrchardOutput = 907
class FailureType(IntEnum):
UnexpectedMessage = 1
@ -296,6 +302,10 @@ if TYPE_CHECKING:
WipeCodeFirst = 4
WipeCodeSecond = 5
class ZcashSignatureType(IntEnum):
TRANSPARENT = 0
ORCHARD_SPEND_AUTH = 3
class InputScriptType(IntEnum):
SPENDADDRESS = 0
SPENDMULTISIG = 1
@ -332,6 +342,9 @@ if TYPE_CHECKING:
TXORIGINPUT = 5
TXORIGOUTPUT = 6
TXPAYMENTREQ = 7
TXORCHARDOUTPUT = 8
TXORCHARDINPUT = 9
NO_OP = 10
class CardanoDerivationType(IntEnum):
LEDGER = 0

View File

@ -57,6 +57,7 @@ if TYPE_CHECKING:
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,118 @@ if TYPE_CHECKING:
def is_type_of(cls, msg: Any) -> TypeGuard["HDNodeType"]:
return isinstance(msg, cls)
class ZcashGetViewingKey(protobuf.MessageType):
coin_name: "str"
z_address_n: "list[int]"
full: "bool"
def __init__(
self,
*,
z_address_n: "list[int] | None" = None,
coin_name: "str | None" = None,
full: "bool | None" = None,
) -> None:
pass
@classmethod
def is_type_of(cls, msg: Any) -> TypeGuard["ZcashGetViewingKey"]:
return isinstance(msg, cls)
class ZcashViewingKey(protobuf.MessageType):
key: "str"
def __init__(
self,
*,
key: "str",
) -> None:
pass
@classmethod
def is_type_of(cls, msg: Any) -> TypeGuard["ZcashViewingKey"]:
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 +707,10 @@ if TYPE_CHECKING:
branch_id: "int | None"
amount_unit: "AmountUnit"
decred_staking_ticket: "bool"
orchard_inputs_count: "int"
orchard_outputs_count: "int"
orchard_anchor: "bytes | None"
account: "int"
def __init__(
self,
@ -609,6 +726,10 @@ if TYPE_CHECKING:
branch_id: "int | None" = None,
amount_unit: "AmountUnit | None" = None,
decred_staking_ticket: "bool | None" = None,
orchard_inputs_count: "int | None" = None,
orchard_outputs_count: "int | None" = None,
orchard_anchor: "bytes | None" = None,
account: "int | None" = None,
) -> None:
pass
@ -994,6 +1115,9 @@ if TYPE_CHECKING:
signature_index: "int | None"
signature: "bytes | None"
serialized_tx: "bytes | None"
signature_type: "ZcashSignatureType | None"
zcash_shielding_seed: "bytes | None"
tx_sighash: "bytes | None"
def __init__(
self,
@ -1001,6 +1125,9 @@ if TYPE_CHECKING:
signature_index: "int | None" = None,
signature: "bytes | None" = None,
serialized_tx: "bytes | None" = None,
signature_type: "ZcashSignatureType | None" = None,
zcash_shielding_seed: "bytes | None" = None,
tx_sighash: "bytes | None" = None,
) -> None:
pass

View File

@ -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:])

View File

@ -4,27 +4,24 @@ from apps.zcash.f4jumble import f4jumble, f4unjumble
@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin")
class TestZcashF4jumble(unittest.TestCase):
def test_f4jumble(self):
#source: https://github.com/zcash/librustzcash/blob/main/components/f4jumble/src/test_vectors.rs
TEST_VECTORS = [
{'jumbled': unhexlify('0304d029141b995da5387c125970673504d6c764d91ea6c082123770c7139ccd88ee27368cd0c0921a0444c8e5858d22'),
'normal': unhexlify('5d7a8f739a2d9e945b0ce152a8049e294c4d6e66b164939daffa2ef6ee6921481cdd86b3cc4318d9614fc820905d042b')},
{'jumbled': unhexlify('5271fa3321f3adbcfb075196883d542b438ec6339176537daf859841fe6a56222bff76d1662b5509a9e1079e446eeedd2e683c31aae3ee1851d7954328526be1'),
'normal': unhexlify('b1ef9ca3f24988c7b3534201cfb1cd8dbf69b8250c18ef41294ca97993db546c1fe01f7e9c8e36d6a5e29d4e30a73594bf5098421c69378af1e40f64e125946f')},
{'jumbled': unhexlify('498cf1b1ba6f4577effe64151d67469adc30acc325e326207e7d78487085b4162669f82f02f9774c0cc26ae6e1a76f1e266c6a9a8a2f4ffe8d2d676b1ed71cc47195a3f19208998f7d8cdfc0b74d2a96364d733a62b4273c77d9828aa1fa061588a7c4c88dd3d3dde02239557acfaad35c55854f4541e1a1b3bc8c17076e7316'),
'normal': unhexlify('62c2fa7b2fecbcb64b6968912a6381ce3dc166d56a1d62f5a8d7551db5fd9313e8c7203d996af7d477083756d59af80d06a745f44ab023752cb5b406ed8985e18130ab33362697b0e4e4c763ccb8f676495c222f7fba1e31defa3d5a57efc2e1e9b01a035587d5fb1a38e01d94903d3c3e0ad3360c1d3710acd20b183e31d49f')},
{'jumbled': unhexlify('7508a3a146714f229db91b543e240633ed57853f6451c9db6d64c6e86af1b88b28704f608582c53c51ce7d5b8548827a971d2b98d41b7f6258655902440cd66ee11e84dbfac7d2a43696fd0468810a3d9637c3fa58e7d2d341ef250fa09b9fb71a78a41d389370138a55ea58fcde779d714a04e0d30e61dc2d8be0da61cd684509'),
'normal': unhexlify('25c9a138f49b1a537edcf04be34a9851a7af9db6990ed83dd64af3597c04323ea51b0052ad8084a8b9da948d320dadd64f5431e61ddf658d24ae67c22c8d1309131fc00fe7f235734276d38d47f1e191e00c7a1d48af046827591e9733a97fa6b679f3dc601d008285edcbdae69ce8fc1be4aac00ff2711ebd931de518856878f7')},
{'jumbled': unhexlify('5139912fe8b95492c12731995a0f4478dbeb81ec36653a21bc80d673f3c6a0feef70b6c566f9d34bb726c098648382d105afb19b2b8486b73cbd47a17a0d2d1fd593b14bb9826c5d114b850c6f0cf3083a6f61e38e42713a37ef7997ebd2b376c8a410d797b3932e5a6e39e726b2894ce79604b4ae3c00acaea3be2c1dfe697fa644755102cf9ad78794d0594585494fe38ab56fa6ef3271a68a33481015adf3944c115311421a7dc3ce73ef2abf47e18a6aca7f9dd25a85ce8dbd6f1ad89c8d'),
'normal': unhexlify('3476f21a482ec9378365c8f7393c94e2885315eb4671098b79535e790fe53e29fef2b3766697ac32b4f473f468a008e72389fc03880d780cb07fcfaabe3f1a84b27db59a4a153d882d2b2103596555ed9494c6ac893c49723833ec8926c1039586a7afcf4a0d9c731e985d99589c8bb838e8aaf745533ed9e8ae3a1cd074a51a20da8aba18d1dbebbc862ded42435e92476930d069896cff30eb414f727b89e001afa2fb8dc3436d75a4a6f26572504b192232ecb9f0c02411e52596bc5e9045')}
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()

View File

@ -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()

View File

@ -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()

View File

@ -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.to_bytes() , 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()

View File

@ -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()

View File

@ -0,0 +1,50 @@
from common import *
from trezor.crypto.hashlib import blake2b
from trezor.crypto.pallas import to_scalar, Point, Scalar
from apps.zcash.orchard.crypto.generators import SPENDING_KEY_BASE as G
from apps.zcash.orchard.random import ActionShieldingRng
if not utils.BITCOIN_ONLY:
from apps.zcash.orchard.crypto.redpallas import sign_spend_auth
def H_star(x: bytes) -> Scalar:
digest = blake2b(personal=b"Zcash_RedPallasH", data=x).digest()
return to_scalar(digest)
def verify(signature, message, vk_):
R_, S_ = signature[:32], signature[32:]
R = Point(R_)
S = Scalar(S_)
vk = Point(vk_)
c = H_star(R_ + vk_ + message)
if R.to_bytes() != R_:
return False
if S.to_bytes() != S_:
return False
return ((-S) * G + R + c * vk).is_identity()
@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin")
class TestZcashRedPallas(unittest.TestCase):
def test_redpallas(self):
message = b"hello"
sk = Scalar(32 * b"\x01")
vk = sk * G
rng = ActionShieldingRng(32 * b"\x00")
sig = sign_spend_auth(sk, message, rng)
self.assertEqual(verify(sig, message, vk.to_bytes()), True)
print()
print("vk:", list(vk.to_bytes()))
print("message:", list(message))
print("sig:", list(sig))
print("===")
print(list(vk.to_bytes()), ",", list(sig), ", &", list(message))
print("===")
if __name__ == "__main__":
unittest.main()

View File

@ -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()

View File

@ -0,0 +1,37 @@
from common import *
from apps.zcash.orchard.random import BundleShieldingRng
@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin")
class TestZcashRedPallas(unittest.TestCase):
def test_zcash_shielding_rng(self):
print()
print()
seed = bytes(list(range(32)))
brng = BundleShieldingRng(seed)
f = lambda x: list(x)
print("seed", f(seed))
inps = list(range(100))
brng.shuffle_inputs(inps)
print("shuffled_inputs", inps)
outs = list(range(100))
brng.shuffle_outputs(outs)
print("shuffled_outputs", outs)
for i in [0]:
print()
rng = brng.for_action(i)
print("sub rng", i, f(rng.seed))
for attr in ["alpha", "rcv", "recipient", "ock", "op", "rseed_old", "rseed_new", "rho"]:
value = getattr(rng, attr)()
if hasattr(value, "to_bytes"):
value = value.to_bytes()
print(attr, f(value))
print()
if __name__ == "__main__":
unittest.main()

View File

@ -8,54 +8,47 @@ P2SH = unified_addresses.Typecode.P2SH
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 @@ COIN = coininfo.by_name("Zcash")
@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()

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
Add Zcash shielded transactions

View File

@ -48,6 +48,7 @@ from . import (
settings,
stellar,
tezos,
zcash,
with_client,
)
@ -389,6 +390,7 @@ cli.add_command(ripple.cli)
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)

View File

@ -0,0 +1,83 @@
# 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 <https://www.gnu.org/licenses/lgpl-3.0.html>.
import click
from .. import zcash, tools
from . import ChoiceType, with_client
NETWORKS = {
"mainnet": "Zcash",
"testnet": "Zcash Testnet",
}
@click.group(name="zcash")
def cli():
"""Zcash commands."""
@cli.command()
@click.option("-z", "--z-address", help="ZIP-32 Orchard derivation path.")
@click.option("-w", "--network", type=ChoiceType(NETWORKS), default="mainnet")
@click.option('-f', '--full', is_flag=True, help="Return **Full** Vieving Key.")
@with_client
def get_viewing_key(client, z_address, network, full):
"""
Get Zcash Unified Incoming Key.
Use --full flag to return **Full** Viewing Key.
**Incoming** Viewing Key is returned otherwise.
Example:
--------
$ trezorctl zcash get-viewing-key -f -z m/32h/133h/0h
"""
return zcash.get_viewing_key(client, tools.parse_path(z_address), network, full)
@cli.command()
@click.option("-t", "--t-address", help="BIP-32 path of a P2PKH transparent address.")
@click.option("-z", "--z-address", help="ZIP-32 Orchard derivation path.")
@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=ChoiceType(NETWORKS), default="mainnet")
@with_client
def get_address(client, t_address, z_address, diversifier_index, show_display, network):
"""
Get Zcash address.
Example:
--------
$ trezorctl zcash get-address -d -t m/44h/133h/0h/0/0 -z m/32h/133h/0h -j 0
"""
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"] = network
try:
return zcash.get_address(client, **kwargs)
except ValueError as e:
return str(e)

View File

@ -257,6 +257,12 @@ class MessageType(IntEnum):
WebAuthnCredentials = 801
WebAuthnAddResidentCredential = 802
WebAuthnRemoveResidentCredential = 803
ZcashGetAddress = 900
ZcashAddress = 901
ZcashGetViewingKey = 902
ZcashViewingKey = 903
ZcashOrchardInput = 906
ZcashOrchardOutput = 907
class FailureType(IntEnum):
@ -308,6 +314,11 @@ class PinMatrixRequestType(IntEnum):
WipeCodeSecond = 5
class ZcashSignatureType(IntEnum):
TRANSPARENT = 0
ORCHARD_SPEND_AUTH = 3
class InputScriptType(IntEnum):
SPENDADDRESS = 0
SPENDMULTISIG = 1
@ -348,6 +359,9 @@ class RequestType(IntEnum):
TXORIGINPUT = 5
TXORIGOUTPUT = 6
TXPAYMENTREQ = 7
TXORCHARDOUTPUT = 8
TXORCHARDINPUT = 9
NO_OP = 10
class CardanoDerivationType(IntEnum):
@ -933,6 +947,127 @@ class HDNodeType(protobuf.MessageType):
self.private_key = private_key
class ZcashGetViewingKey(protobuf.MessageType):
MESSAGE_WIRE_TYPE = 902
FIELDS = {
1: protobuf.Field("coin_name", "string", repeated=False, required=False),
2: protobuf.Field("z_address_n", "uint32", repeated=True, required=False),
3: protobuf.Field("full", "bool", repeated=False, required=False),
}
def __init__(
self,
*,
z_address_n: Optional[Sequence["int"]] = None,
coin_name: Optional["str"] = 'Zcash',
full: Optional["bool"] = True,
) -> None:
self.z_address_n: Sequence["int"] = z_address_n if z_address_n is not None else []
self.coin_name = coin_name
self.full = full
class ZcashViewingKey(protobuf.MessageType):
MESSAGE_WIRE_TYPE = 903
FIELDS = {
1: protobuf.Field("key", "string", repeated=False, required=True),
}
def __init__(
self,
*,
key: "str",
) -> None:
self.key = key
class ZcashGetAddress(protobuf.MessageType):
MESSAGE_WIRE_TYPE = 900
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 = 901
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 = 906
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 = 907
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 = 22
class MultisigRedeemScriptType(protobuf.MessageType):
MESSAGE_WIRE_TYPE = None
FIELDS = {
@ -1172,6 +1307,10 @@ 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_inputs_count", "uint32", repeated=False, required=False),
14: protobuf.Field("orchard_outputs_count", "uint32", repeated=False, required=False),
15: protobuf.Field("orchard_anchor", "bytes", repeated=False, required=False),
16: protobuf.Field("account", "uint32", repeated=False, required=False),
}
def __init__(
@ -1189,6 +1328,10 @@ class SignTx(protobuf.MessageType):
branch_id: Optional["int"] = None,
amount_unit: Optional["AmountUnit"] = AmountUnit.BITCOIN,
decred_staking_ticket: Optional["bool"] = False,
orchard_inputs_count: Optional["int"] = 0,
orchard_outputs_count: Optional["int"] = 0,
orchard_anchor: Optional["bytes"] = None,
account: Optional["int"] = 0,
) -> None:
self.outputs_count = outputs_count
self.inputs_count = inputs_count
@ -1202,6 +1345,10 @@ class SignTx(protobuf.MessageType):
self.branch_id = branch_id
self.amount_unit = amount_unit
self.decred_staking_ticket = decred_staking_ticket
self.orchard_inputs_count = orchard_inputs_count
self.orchard_outputs_count = orchard_outputs_count
self.orchard_anchor = orchard_anchor
self.account = account
class TxRequest(protobuf.MessageType):
@ -1659,6 +1806,9 @@ 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", "ZcashSignatureType", repeated=False, required=False),
5: protobuf.Field("zcash_shielding_seed", "bytes", repeated=False, required=False),
6: protobuf.Field("tx_sighash", "bytes", repeated=False, required=False),
}
def __init__(
@ -1667,10 +1817,16 @@ class TxRequestSerializedType(protobuf.MessageType):
signature_index: Optional["int"] = None,
signature: Optional["bytes"] = None,
serialized_tx: Optional["bytes"] = None,
signature_type: Optional["ZcashSignatureType"] = None,
zcash_shielding_seed: Optional["bytes"] = None,
tx_sighash: 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
self.tx_sighash = tx_sighash
class TransactionType(protobuf.MessageType):

View File

@ -0,0 +1,218 @@
# 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 <https://www.gnu.org/licenses/lgpl-3.0.html>.
from typing import TYPE_CHECKING
import logging
from . import exceptions, messages
from .messages import ZcashSignatureType as SigType
from .tools import expect
if TYPE_CHECKING:
from typing import Generator
from .client import TrezorClient
LOG = logging.getLogger(__name__)
@expect(messages.ZcashViewingKey, field="key")
def get_viewing_key(client: "TrezorClient", z_address_n: list[int], coin_name: str = "Zcash", full: bool = True) -> str:
"""Returns Zcash Unified Full Viewing Key."""
return client.call(
messages.ZcashGetViewingKey(
z_address_n=z_address_n,
coin_name=coin_name,
full=full,
)
)
@expect(messages.ZcashAddress, field="address")
def get_address(
client: "TrezorClient",
t_address_n: list[int] | None = None,
z_address_n: list[int] | None = None,
diversifier_index: int = 0,
show_display: bool = False,
coin_name: str = "Zcash",
):
"""
Returns a Zcash address.
"""
return client.call(
messages.ZcashGetAddress(
t_address_n=t_address_n or [],
z_address_n=z_address_n or [],
diversifier_index=diversifier_index,
show_display=show_display,
coin_name=coin_name,
)
)
EMPTY_ANCHOR = bytes.fromhex("ae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82f")
def sign_tx(
client: "TrezorClient",
inputs: list[messages.TxInput | messages.ZcashOrchardInput],
outputs: list[messages.TxOutput | messages.ZcashOrchardOutput],
coin_name: str = "Zcash",
version_group_id: int = 0x26A7270A, # protocol spec §7.1.2
branch_id: int = 0xC2D6D0B4, # https://zips.z.cash/zip-0252
expiry: int = 0,
account: int = 0,
anchor: bytes = EMPTY_ANCHOR,
) -> "Generator[None, bytes, (dict[int, bytes], bytes)]":
"""
Sign a Zcash transaction.
Parameters:
-----------
inputs: transaction inputs
outputs: transaction outputs
coin_name: coin name (currently "Zcash" or "Zcash Testnet")
version_group_id: 0x26A7270A by default
branch_id: 0xC2D6D0B4 by default
expiry: 0 by default
account: account number, from which is spent
third digit of ZIP-32 path. 0 by default
anchor: Orchard anchor
Example:
--------
protocol = zcash.sign_tx(
inputs=[
TxInput(...)
],
outputs=[
TxOutput(...),
ZcashOrchardOutput(...),
]
anchor=bytes.fromhex(...),
verbose=True,
)
shielding_seed = next(protocol)
... # run Orchard prover in parallel here
sighash = next(protocol)
signatures, serialized_tx = next(protocol)
"""
t_inputs = [x for x in inputs if type(x) in (messages.TxInput, messages.TxInputType)]
t_outputs = [x for x in outputs if type(x) in (messages.TxOutput, messages.TxOutputType)]
o_inputs = [x for x in inputs if isinstance(x, messages.ZcashOrchardInput)]
o_outputs = [x for x in outputs if isinstance(x, messages.ZcashOrchardOutput)]
msg = messages.SignTx(
inputs_count=len(t_inputs),
outputs_count=len(t_outputs),
coin_name=coin_name,
version=5,
version_group_id=version_group_id,
branch_id=branch_id,
expiry=expiry,
orchard_inputs_count=len(o_inputs),
orchard_outputs_count=len(o_outputs),
orchard_anchor=anchor,
account=account,
)
actions_count = (
max(len(o_outputs), len(o_inputs), 2)
if len(o_outputs) + len(o_inputs) > 0
else 0)
serialized_tx = b""
signatures = {
SigType.TRANSPARENT: [None] * len(t_inputs),
SigType.ORCHARD_SPEND_AUTH: dict(),
}
LOG.info("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.info("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 sig_type == SigType.TRANSPARENT:
LOG.info(f"T -> t signature {idx}")
if signatures[sig_type][idx] is not None:
raise ValueError(f"Transparent signature for index {idx} already filled")
elif sig_type == SigType.ORCHARD_SPEND_AUTH:
LOG.info(f"T -> o signature {idx}")
if signatures[sig_type].get(idx) is not None:
raise ValueError(f"Orchard signature for index {idx} already filled")
if idx >= actions_count:
raise IndexError(f"Orchard signature index out of range: {idx}")
else:
raise ValueError(f"Unknown signature type: {sig_type}.")
signatures[sig_type][idx] = sig
if res.serialized.zcash_shielding_seed is not None:
LOG.info("T -> shielding seed")
yield res.serialized.zcash_shielding_seed
if res.serialized.tx_sighash is not None:
LOG.info("T -> sighash")
yield res.serialized.tx_sighash
LOG.info("")
if res.request_type == R.TXFINISHED:
break
elif res.request_type == R.TXINPUT:
LOG.info("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.info("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.info("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.info("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")
yield (signatures, serialized_tx)

View File

@ -0,0 +1,277 @@
# 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 <https://www.gnu.org/licenses/lgpl-3.0.html>.
import pytest
from trezorlib import messages, zcash
from trezorlib.debuglink import TrezorClientDebugLink as Client
from trezorlib.exceptions import TrezorFailure
from trezorlib.messages import (
ButtonRequest,
OutputScriptType,
RequestType as T,
TxInputType,
TxOutputType,
TxRequest,
TxRequestDetailsType,
ZcashOrchardInput,
ZcashOrchardOutput,
ZcashSignatureType,
)
from trezorlib.tools import parse_path
from ..bitcoin.signtx import request_finished, request_input, request_output
# from ..bitcoin.signtx import request_finished, request_input, request_output
B = messages.ButtonRequestType
def request_orchard_input(i: int):
return TxRequest(
request_type=T.TXORCHARDINPUT,
details=messages.TxRequestDetailsType(request_index=i),
)
def request_orchard_output(i: int):
return TxRequest(
request_type=T.TXORCHARDOUTPUT,
details=messages.TxRequestDetailsType(request_index=i),
)
def request_no_op():
return TxRequest(request_type=T.NO_OP)
def test_z2t(client: Client) -> None:
t_out_0 = TxOutputType(
address_n=parse_path("m/44h/1h/0h/0/0"),
amount=990000,
script_type=OutputScriptType.PAYTOADDRESS,
)
# note a86810a5b052fb69b3c76887f381241cea060017314e9a9418876e02cda63c03
o_inp_0 = ZcashOrchardInput(
recipient=bytes.fromhex(
"160618e7e57eb29bfc1182108a933ae1dbf8ccc148d3cfa6c0a15a04de09e3c8844cd07be822332eaa2900"
),
value=1000000,
rho=bytes.fromhex(
"bd6361c35b8363554f9c0b3612e67f4c65beae3aa71305e84d3827137497563d"
),
rseed=bytes.fromhex(
"5c7ea612f6f66f50f961d7c98c2c60f3d0223c4ad6bdb876a3b8225bce526361"
),
)
anchor = bytes.fromhex(
"a6e3b9c237886caa6ca8614428dc9a0ca5e2e54691c68f682348a41f489abf1f"
)
expected_shielding_seed = bytes.fromhex(
"a3db28e1855c5bd8670f234150ea4d8f2d22a97662e5e3cf765aa6abf5b5579e"
)
expected_sighash = bytes.fromhex(
"791f8985d5c21e9738b4556274109aa1a468f57079d8cd1b3ae83bac371eabfd"
)
expected_serialized_tx = bytes.fromhex(
"050000800a27a726b4d0d6c200000000000000000001301b0f00000000001976a914a579388225827d9f2fe9014add644487808c695d88ac000002"
)
with client:
client.set_expected_responses(
[
request_orchard_input(0),
request_output(0),
ButtonRequest(code=B.SignTx),
request_no_op(), # shielding seed
request_orchard_input(0),
request_output(0),
request_finished(), # returns o-signature of o-input 0 in action 0
]
)
protocol = zcash.sign_tx(
client,
t_inputs=[],
t_outputs=[t_out_0],
o_inputs=[o_inp_0],
o_outputs=[],
anchor=anchor,
coin_name="Zcash Testnet",
)
shielding_seed = next(protocol)
assert shielding_seed == expected_shielding_seed
sighash = next(protocol)
assert sighash == expected_sighash
signatures, serialized_tx = next(protocol)
assert serialized_tx == expected_serialized_tx
assert signatures == {
ZcashSignatureType.TRANSPARENT: [],
ZcashSignatureType.ORCHARD_SPEND_AUTH: {
0: bytes.fromhex(
"6a5b6db66413490272cdcc55efca8c2d85ce493fa46a3624675e9760f78f98206761087f847266839c9d9534a5e39cedb90e628ee48c7fdc88089faf692ab42e"
),
},
}
# Accepted by network as fdab1e37ac3be83a1bcdd87970f568a4a19a10746255b438971ec2d585891f79
# link: https://sochain.com/tx/ZECTEST/fdab1e37ac3be83a1bcdd87970f568a4a19a10746255b438971ec2d585891f79
def test_z2z(client: Client) -> None:
# note 8eb09c03fcddd0011fcbeee17518d378f02c3be3cbf210c69fbae5c111da0e16
o_inp_0 = ZcashOrchardInput(
recipient=bytes.fromhex(
"160618e7e57eb29bfc1182108a933ae1dbf8ccc148d3cfa6c0a15a04de09e3c8844cd07be822332eaa2900"
),
value=1000000,
rho=bytes.fromhex(
"3bf2d24afd84071efbbf3175afba9971cc8aa21ea1da5629379e2d51b39aeb28"
),
rseed=bytes.fromhex(
"74e4ab56513d4587d716d864a9c6e05b289e67c16096a6a4df8737d8253f442d"
),
)
o_out_0 = ZcashOrchardOutput(
address=None,
amount=990000,
memo=None,
)
anchor = bytes.fromhex(
"a6e3b9c237886caa6ca8614428dc9a0ca5e2e54691c68f682348a41f489abf1f"
)
expected_shielding_seed = bytes.fromhex(
"e14ee85dac66bbc5dbc0e8cf9a73a5b5feba978dc8c96a7d8d83af971b43f943"
)
expected_sighash = bytes.fromhex(
"8b7a1b8bcae057c9389a7743d05e3ac45fd4088b1f4f34427903eae140f13196"
)
expected_serialized_tx = bytes.fromhex(
"050000800a27a726b4d0d6c200000000000000000000000002"
)
with client:
client.set_expected_responses(
[
request_orchard_input(0),
request_orchard_output(0),
ButtonRequest(code=B.SignTx),
request_no_op(), # shielding seed
request_orchard_input(0),
request_orchard_output(0),
request_finished(), # returns o-signature of o-input 0 in action 0
]
)
protocol = zcash.sign_tx(
client,
t_inputs=[],
t_outputs=[],
o_inputs=[o_inp_0],
o_outputs=[o_out_0],
anchor=anchor,
coin_name="Zcash Testnet",
)
shielding_seed = next(protocol)
assert shielding_seed == expected_shielding_seed
sighash = next(protocol)
assert sighash == expected_sighash
signatures, serialized_tx = next(protocol)
assert serialized_tx == expected_serialized_tx
assert signatures == {
ZcashSignatureType.TRANSPARENT: [],
ZcashSignatureType.ORCHARD_SPEND_AUTH: {
0: bytes.fromhex(
"23215cda85de918473b83f6f53a7817817286692a69ffd74c12468adeb4cc5a4dbb1990672b24bdd7c3f5ebee8f86a56c707493b9f5d34707bd639a3191a4c04"
),
},
}
# Accepted by network as 9631f140e1ea037942344f1f8b08d45fc43a5ed043779a38c957e0ca8b1b7a8b
# link: https://sochain.com/tx/ZECTEST/9631f140e1ea037942344f1f8b08d45fc43a5ed043779a38c957e0ca8b1b7a8b
def test_t2z(client: Client) -> None:
t_inp_0 = TxInputType(
address_n=parse_path("m/44h/1h/0h/0/0"),
amount=1000000,
prev_hash=bytes.fromhex(
"f81bfe926afee9f463f9c7ab0a68e29b78718b42798b48f4227094b3cbe8c3e7"
),
prev_index=0,
)
o_out_0 = ZcashOrchardOutput(
address=None,
amount=990000,
memo=None,
)
anchor = bytes.fromhex(
"a6e3b9c237886caa6ca8614428dc9a0ca5e2e54691c68f682348a41f489abf1f"
)
expected_shielding_seed = bytes.fromhex(
"a8f354bff75e1607f80868aaa408e776ce097f8adcbc5074ac603774cb9462e2"
)
expected_sighash = bytes.fromhex(
"c7a2978fe65e8d7742358f542ae2f031aa1a3441af161079237509ee4e74112d"
)
expected_serialized_tx = bytes.fromhex(
"050000800a27a726b4d0d6c2000000000000000001e7c3e8cbb3947022f4488b79428b71789be2680aabc7f963f4e9fe6a92fe1bf8000000006a473044022054e06e576036b6b83f7c676ed1e97810710a50eed52bd6e393ac93084a7a62b602201a5dcf95242b174a9510741692a8666b4f305abff7f2a0b153bd00470a77b66c0121030e669acac1f280d1ddf441cd2ba5e97417bf2689e4bbec86df4f831bf9f7ffd0ffffffff00000002"
)
with client:
client.set_expected_responses(
[
request_input(0),
request_orchard_output(0),
ButtonRequest(code=B.SignTx),
request_no_op(), # shielding seed
request_orchard_output(0),
request_input(0),
request_finished(), # t-signature {i}
]
)
protocol = zcash.sign_tx(
client,
t_inputs=[t_inp_0],
t_outputs=[],
o_inputs=[],
o_outputs=[o_out_0],
anchor=anchor,
coin_name="Zcash Testnet",
)
shielding_seed = next(protocol)
assert shielding_seed == expected_shielding_seed
sighash = next(protocol)
assert sighash == expected_sighash
signatures, serialized_tx = next(protocol)
assert serialized_tx == expected_serialized_tx
assert signatures == {
ZcashSignatureType.TRANSPARENT: [
bytes.fromhex(
"3044022054e06e576036b6b83f7c676ed1e97810710a50eed52bd6e393ac93084a7a62b602201a5dcf95242b174a9510741692a8666b4f305abff7f2a0b153bd00470a77b66c"
),
],
ZcashSignatureType.ORCHARD_SPEND_AUTH: {},
}
# Accepted by network as 57cb5b7194583d3d4073d8825668762872dd1b4b3ad88fed5e24bc26b500ea44
# link: https://sochain.com/tx/ZECTEST/57cb5b7194583d3d4073d8825668762872dd1b4b3ad88fed5e24bc26b500ea44