Skip to content

Commit

Permalink
feat(core): add Zcash shielded transactions
Browse files Browse the repository at this point in the history
  • Loading branch information
krnak committed Nov 17, 2022
1 parent 385157e commit a0344ab
Show file tree
Hide file tree
Showing 64 changed files with 3,824 additions and 186 deletions.
16 changes: 15 additions & 1 deletion common/protob/messages-bitcoin.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}

/**
Expand All @@ -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?
Expand All @@ -228,6 +236,9 @@ message TxRequest {
TXORIGINPUT = 5;
TXORIGOUTPUT = 6;
TXPAYMENTREQ = 7;
TXORCHARDOUTPUT = 8;
TXORCHARDINPUT = 9;
NO_OP = 10;
}
/**
* Structure representing request details
Expand All @@ -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
}
}

Expand Down Expand Up @@ -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 changes: 89 additions & 0 deletions common/protob/messages-zcash.proto
Original file line number Diff line number Diff line change
@@ -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;
}
8 changes: 8 additions & 0 deletions common/protob/messages.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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 change: 1 addition & 0 deletions core/.changelog.d/2472.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add Zcash shielded transactions
21 changes: 17 additions & 4 deletions core/SConscript.firmware
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ 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": EVERYTHING,
"ZCASH_SHIELDED": True,
}

CCFLAGS_MOD = ''
Expand Down Expand Up @@ -676,7 +676,13 @@ 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_fvk.py',
SOURCE_PY_DIR + 'apps/zcash/get_ivk.py',
])
)

SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/webauthn/*.py'))

Expand All @@ -685,6 +691,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'))

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_fvk.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/zcash/get_ivk.py'))

source_mpy = env.FrozenModule(source=SOURCE_PY, source_dir=SOURCE_PY_DIR, bitcoin_only=BITCOIN_ONLY)

source_mpyc = env.FrozenCFile(
Expand Down
15 changes: 14 additions & 1 deletion core/SConscript.unix
Original file line number Diff line number Diff line change
Expand Up @@ -632,7 +632,13 @@ 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_fvk.py',
SOURCE_PY_DIR + 'apps/zcash/get_ivk.py',
])
)

SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/webauthn/*.py'))

Expand All @@ -641,6 +647,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'))

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_fvk.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/zcash/get_ivk.py'))

source_mpy = env.FrozenModule(source=SOURCE_PY, source_dir=SOURCE_PY_DIR, bitcoin_only=BITCOIN_ONLY)

source_mpyc = env.FrozenCFile(
Expand Down
1 change: 0 additions & 1 deletion core/embed/firmware/memory_T.ld
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
5 changes: 2 additions & 3 deletions core/embed/rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 4 additions & 8 deletions core/embed/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ build = "build.rs"
default = ["model_tt"]
bitcoin_only = []
zcash_shielded = [
"micropython", "rand_core", "pasta_curves", "blake2b_simd",
"micropython", "pasta_curves", "blake2b_simd",
]
model_tt = ["touch"]
model_t1 = ["buttons"]
Expand Down Expand Up @@ -56,20 +56,16 @@ default_features = false
version = "0.2.4"
default_features = false

[dependencies.rand_core]
optional = true
version = "0.6.3"
default_features = false

[dependencies.blake2b_simd]
optional = true
version = "1"
default_features = false

[dependencies.pasta_curves]
optional = true
version = "0.4.0"
version = "0.4.1"
default-features = false
features = ["uninline-portable"]

# Build dependencies

Expand All @@ -93,4 +89,4 @@ path = "./blake2b_hal"

[patch.crates-io.pasta_curves]
git = "https://github.com/jarys/pasta_curves"
rev = "a4f755013aad344982383c9f5af362697d928325"
rev = "e11dfe4089d0da24483094a99cceb89f32974c17"
40 changes: 21 additions & 19 deletions core/embed/rust/src/zcash_primitives/pallas/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,14 @@ mod scalar;
#[no_mangle]
pub static mp_module_trezorpallas: Module = obj_module! {
Qstr::MP_QSTR___name__ => Qstr::MP_QSTR_trezorpallas.to_obj(),
/// # https://zips.z.cash/protocol/protocol.pdf#orchardkeycomponents
/// def to_base(x: bytes) -> Fp:
/// ...
/// """https://zips.z.cash/protocol/protocol.pdf#orchardkeycomponents"""
Qstr::MP_QSTR_to_base => obj_fn_1!(fp::to_base).as_obj(),
/// # https://zips.z.cash/protocol/protocol.pdf#orchardkeycomponents
/// def to_scalar(x: bytes) -> Scalar:
/// ...
/// """https://zips.z.cash/protocol/protocol.pdf#orchardkeycomponents"""
Qstr::MP_QSTR_to_scalar => obj_fn_1!(scalar::to_scalar).as_obj(),
/// # https://zips.z.cash/protocol/protocol.pdf#concretegrouphashpallasandvesta
/// def group_hash(domain: str, message: bytes) -> Point:
/// ...
/// """https://zips.z.cash/protocol/protocol.pdf#concretegrouphashpallasandvesta"""
Qstr::MP_QSTR_group_hash => obj_fn_2!(point::group_hash).as_obj(),
/// def scalar_from_i64(x: int) -> Scalar:
/// """Converts integer to Scalar."""
Expand All @@ -32,46 +29,51 @@ pub static mp_module_trezorpallas: Module = obj_module! {
/// def to_bytes(self) -> bytes:
/// ...
Qstr::MP_QSTR_Fp => (&fp::FP_TYPE).as_obj(),
/// class Scalar:
/// """Pallas scalar field."""
/// class Point:
/// """Pallas point."""
///
/// def __init__(self, repr: bytes) -> None:
/// ...
///
/// def to_bytes(self) -> bytes:
/// ...
///
/// def is_not_zero(self) -> bool:
/// def extract(self) -> Fp:
/// ...
///
/// def __mul__(self, other: Point) -> Point:
/// def is_identity(self) -> bool:
/// ...
///
/// def __add__(self, other: Scalar) -> Scalar:
/// def __add__(self, other: Point) -> Point:
/// ...
///
/// def __neg__(self) -> Point:
/// ...
Qstr::MP_QSTR_Scalar => (&scalar::SCALAR_TYPE).as_obj(),
/// class Point:
/// """Pallas point."""
Qstr::MP_QSTR_Point => (&point::POINT_TYPE).as_obj(),
/// PointOrScalar = TypeVar("PointOrScalar", Point, Scalar)
///
/// class Scalar:
/// """Pallas scalar field."""
///
/// def __init__(self, repr: bytes) -> None:
/// ...
///
/// def to_bytes(self) -> bytes:
/// ...
///
/// def extract(self) -> Fp:
/// def is_not_zero(self) -> bool:
/// ...
///
/// def is_identity(self) -> bool:
/// def __mul__(self, other: PointOrScalar) -> PointOrScalar:
/// ...
/// # if isinstance(other, Point) then isinstance(result, Point)
/// # if isinstance(other, Scalar) then isinstance(result, Scalar)
/// return other # just for typechecker! inner implementation differs
///
/// def __add__(self, other: Point) -> Point:
/// def __add__(self, other: Scalar) -> Scalar:
/// ...
///
/// def __neg__(self) -> Point:
/// def __neg__(self) -> Scalar:
/// ...
Qstr::MP_QSTR_Point => (&point::POINT_TYPE).as_obj(),
Qstr::MP_QSTR_Scalar => (&scalar::SCALAR_TYPE).as_obj(),
};
Loading

0 comments on commit a0344ab

Please sign in to comment.