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:
parent
6311675119
commit
ac73bfaf1e
@ -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
|
||||
}
|
||||
|
||||
|
89
common/protob/messages-zcash.proto
Normal file
89
common/protob/messages-zcash.proto
Normal 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;
|
||||
}
|
@ -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];
|
||||
}
|
||||
|
1
core/.changelog.d/2472.added
Normal file
1
core/.changelog.d/2472.added
Normal file
@ -0,0 +1 @@
|
||||
Add Zcash shielded transactions
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
4
core/embed/rust/Cargo.lock
generated
4
core/embed/rust/Cargo.lock
generated
@ -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",
|
||||
|
@ -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"
|
||||
|
@ -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."""
|
||||
|
@ -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)?;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
35
core/src/apps/zcash/approver.py
Normal file
35
core/src/apps/zcash/approver.py
Normal 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))
|
79
core/src/apps/zcash/get_address.py
Normal file
79
core/src/apps/zcash/get_address.py
Normal 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()
|
46
core/src/apps/zcash/get_viewing_key.py
Normal file
46
core/src/apps/zcash/get_viewing_key.py
Normal 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,
|
||||
)
|
@ -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()
|
||||
|
140
core/src/apps/zcash/layout.py
Normal file
140
core/src/apps/zcash/layout.py
Normal 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
|
0
core/src/apps/zcash/orchard/__init__.py
Normal file
0
core/src/apps/zcash/orchard/__init__.py
Normal file
45
core/src/apps/zcash/orchard/accumulator.py
Normal file
45
core/src/apps/zcash/orchard/accumulator.py
Normal 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.")
|
0
core/src/apps/zcash/orchard/crypto/__init__.py
Normal file
0
core/src/apps/zcash/orchard/crypto/__init__.py
Normal file
39
core/src/apps/zcash/orchard/crypto/address.py
Normal file
39
core/src/apps/zcash/orchard/crypto/address.py
Normal 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)
|
126
core/src/apps/zcash/orchard/crypto/builder.py
Normal file
126
core/src/apps/zcash/orchard/crypto/builder.py
Normal 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
|
90
core/src/apps/zcash/orchard/crypto/ff1.py
Normal file
90
core/src/apps/zcash/orchard/crypto/ff1.py
Normal 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)
|
35
core/src/apps/zcash/orchard/crypto/generators.py
Executable file
35
core/src/apps/zcash/orchard/crypto/generators.py
Executable 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"
|
||||
)
|
114
core/src/apps/zcash/orchard/crypto/keys.py
Executable file
114
core/src/apps/zcash/orchard/crypto/keys.py
Executable 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()
|
96
core/src/apps/zcash/orchard/crypto/note.py
Normal file
96
core/src/apps/zcash/orchard/crypto/note.py
Normal 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")
|
120
core/src/apps/zcash/orchard/crypto/note_encryption.py
Executable file
120
core/src/apps/zcash/orchard/crypto/note_encryption.py
Executable 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),
|
||||
)
|
40
core/src/apps/zcash/orchard/crypto/redpallas.py
Normal file
40
core/src/apps/zcash/orchard/crypto/redpallas.py
Normal 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
|
59
core/src/apps/zcash/orchard/crypto/sinsemilla.py
Normal file
59
core/src/apps/zcash/orchard/crypto/sinsemilla.py
Normal 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
|
74
core/src/apps/zcash/orchard/crypto/utils.py
Normal file
74
core/src/apps/zcash/orchard/crypto/utils.py
Normal 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
|
53
core/src/apps/zcash/orchard/debug.py
Normal file
53
core/src/apps/zcash/orchard/debug.py
Normal 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
|
109
core/src/apps/zcash/orchard/keychain.py
Normal file
109
core/src/apps/zcash/orchard/keychain.py
Normal 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
|
109
core/src/apps/zcash/orchard/random.py
Normal file
109
core/src/apps/zcash/orchard/random.py
Normal 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)
|
300
core/src/apps/zcash/orchard/signer.py
Normal file
300
core/src/apps/zcash/orchard/signer.py
Normal 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
|
@ -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
|
||||
|
@ -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")
|
||||
|
68
core/src/trezor/crypto/mock_bip340.py
Normal file
68
core/src/trezor/crypto/mock_bip340.py
Normal 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
|
@ -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
|
||||
|
@ -10,3 +10,6 @@ TXEXTRADATA = 4
|
||||
TXORIGINPUT = 5
|
||||
TXORIGOUTPUT = 6
|
||||
TXPAYMENTREQ = 7
|
||||
TXORCHARDOUTPUT = 8
|
||||
TXORCHARDINPUT = 9
|
||||
NO_OP = 10
|
||||
|
6
core/src/trezor/enums/ZcashSignatureType.py
Normal file
6
core/src/trezor/enums/ZcashSignatureType.py
Normal file
@ -0,0 +1,6 @@
|
||||
# Automatically generated by pb2py
|
||||
# fmt: off
|
||||
# isort:skip_file
|
||||
|
||||
TRANSPARENT = 0
|
||||
ORCHARD_SPEND_AUTH = 3
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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:])
|
||||
|
@ -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()
|
||||
|
56
core/tests/test_apps.zcash.orchard.crypto.ff1.py
Normal file
56
core/tests/test_apps.zcash.orchard.crypto.ff1.py
Normal 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()
|
39
core/tests/test_apps.zcash.orchard.crypto.generators.py
Normal file
39
core/tests/test_apps.zcash.orchard.crypto.generators.py
Normal 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()
|
70
core/tests/test_apps.zcash.orchard.crypto.keys.py
Normal file
70
core/tests/test_apps.zcash.orchard.crypto.keys.py
Normal 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()
|
45
core/tests/test_apps.zcash.orchard.crypto.note_encryption.py
Normal file
45
core/tests/test_apps.zcash.orchard.crypto.note_encryption.py
Normal 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()
|
50
core/tests/test_apps.zcash.orchard.crypto.redpallas.py
Normal file
50
core/tests/test_apps.zcash.orchard.crypto.redpallas.py
Normal 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()
|
68
core/tests/test_apps.zcash.orchard.crypto.sinsemilla.py
Normal file
68
core/tests/test_apps.zcash.orchard.crypto.sinsemilla.py
Normal 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()
|
37
core/tests/test_apps.zcash.orchard.random.py
Normal file
37
core/tests/test_apps.zcash.orchard.random.py
Normal 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()
|
@ -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
1
python/.changelog.d/2472.added
Normal file
1
python/.changelog.d/2472.added
Normal file
@ -0,0 +1 @@
|
||||
Add Zcash shielded transactions
|
@ -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)
|
||||
|
83
python/src/trezorlib/cli/zcash.py
Normal file
83
python/src/trezorlib/cli/zcash.py
Normal 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)
|
@ -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):
|
||||
|
218
python/src/trezorlib/zcash.py
Normal file
218
python/src/trezorlib/zcash.py
Normal 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)
|
277
tests/device_tests/zcash/test_sign_shielded_tx.py
Normal file
277
tests/device_tests/zcash/test_sign_shielded_tx.py
Normal 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
|
Loading…
Reference in New Issue
Block a user