diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 32469e4..7454bb0 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -17,18 +17,18 @@ jobs: image: zondax/rust-ci:latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: submodules: true - run: sudo apt-get update && sudo apt-get install -y libudev-dev libusb-1.0-0-dev - name: show versions run: | rustup show + - name: Install rustfmt + run: rustup component add rustfmt --toolchain nightly - name: rustfmt run: | - cargo fmt --version - cargo fmt -- --check - + cargo +nightly fmt -- --check - name: rust cache uses: Swatinem/rust-cache@v1 with: @@ -47,7 +47,7 @@ jobs: image: zondax/rust-ci:latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: submodules: true diff --git a/.github/workflows/rust_audit.yaml b/.github/workflows/rust_audit.yaml index 7ed659e..87d44d5 100644 --- a/.github/workflows/rust_audit.yaml +++ b/.github/workflows/rust_audit.yaml @@ -10,15 +10,12 @@ jobs: security_audit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: submodules: true - - run: cargo audit - # disabled until we can change directory - # see https://github.com/actions-rs/audit-check/issues/194 - # - name: Run audit - # uses: actions-rs/audit-check@v1 - # with: - # token: ${{ secrets.GITHUB_TOKEN }} + - name: Run audit + uses: actions-rs/audit-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/rust_periodic_audit.yaml b/.github/workflows/rust_periodic_audit.yaml index 31815ff..7f835bd 100644 --- a/.github/workflows/rust_periodic_audit.yaml +++ b/.github/workflows/rust_periodic_audit.yaml @@ -9,7 +9,7 @@ jobs: security_audit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: submodules: true diff --git a/Cargo.toml b/Cargo.toml index 9106869..4af4ceb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,10 +2,15 @@ resolver = "2" members = [ "ledger-zcash", - "zcash-hsmbuilder" + "ledger-zcash-builder" ] [profile.release] # Tell `rustc` to optimize for small code size. opt-level = "s" overflow-checks = true + +#[patch.crates-io] +#ledger-transport = { path = "../ledger-rs/ledger-transport" } +#ledger-zondax-generic = { path = "../ledger-rs/ledger-zondax-generic" } +#ledger-transport-hid = { path = "../ledger-rs/ledger-transport-hid" } diff --git a/LICENSE b/LICENSE index 4dca2a4..bdde658 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2022 Zondax GmbH + Copyright 2022 Zondax AG Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/ledger-zcash-builder/Cargo.toml b/ledger-zcash-builder/Cargo.toml new file mode 100644 index 0000000..42c7d94 --- /dev/null +++ b/ledger-zcash-builder/Cargo.toml @@ -0,0 +1,54 @@ +[package] +name = "ledger-zcash-builder" +description = "Library to build transactions for HSM apps" +version = "0.11.2" +license = "Apache-2.0" +authors = ["Zondax AG "] +homepage = "https://github.com/Zondax/ledger-zcash-rs" +repository = "https://github.com/Zondax/ledger-zcash-rs" +readme = "README.md" +categories = ["authentication", "cryptography"] +keywords = ["ledger", "nano", "apdu", "zcash"] +edition = "2021" +autobenches = false + +[lib] +name = "ledger_zcash_builder" + +[features] +default = ["zcash_proofs"] +bundled-prover = ["wagyu-zcash-parameters"] +local-prover = [] + +[dependencies] +tokio = { version = "1.38", features = ["sync"] } +educe = "0.5" +log = "0.4" +lazy_static = "1" +sha2 = "0.10.8" +serde_derive = "1" +serde = { version = "1", features = ["derive"] } +byteorder = "1.5" +cfg-if = "1.0.0" +hex = { version = "0.4", default-features = false } + +bellman = { version = "0.13", default-features = false, features = ["groth16"] } +blake2b_simd = "1" +bls12_381 = { version = "0.7" } +chacha20poly1305 = "0.9" +ff = "0.12" +group = "0.12" +jubjub = { version = "0.9", default-features = false } +pairing = { version = "0.22" } +rand = { version = "0.8.5", default-features = false } +rand_core = "0.6.4" +ripemd = "0.1" +secp256k1 = { version = "0.29" } + +#zcash +wagyu-zcash-parameters = { version = "0.2", optional = true } + +zcash_primitives = { version = "0.7", features = ["transparent-inputs"] } +zcash_proofs = { version = "0.7", features = ["multicore"], optional = true } +zcash_note_encryption = { version = "0.1", features = ["pre-zip-212"] } +thiserror = "1.0" diff --git a/zcash-hsmbuilder/README.md b/ledger-zcash-builder/README.md similarity index 100% rename from zcash-hsmbuilder/README.md rename to ledger-zcash-builder/README.md diff --git a/zcash-hsmbuilder/src/data.rs b/ledger-zcash-builder/src/data.rs similarity index 50% rename from zcash-hsmbuilder/src/data.rs rename to ledger-zcash-builder/src/data.rs index f8baa6f..6651a36 100644 --- a/zcash-hsmbuilder/src/data.rs +++ b/ledger-zcash-builder/src/data.rs @@ -1,3 +1,18 @@ +/******************************************************************************* +* (c) 2022-2024 Zondax AG +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ //! This module contains many of the data structures used in the crate and //! in conjunction with the HSM builder @@ -7,9 +22,9 @@ use neon_bridge::*; pub mod sighashdata; pub mod sighashdata_v4; pub mod sighashdata_v5; +use serde::{Deserialize, Serialize}; use sighashdata::TransactionDataSighash; - -use crate::zcash::primitives::{ +use zcash_primitives::{ keys::OutgoingViewingKey, legacy::Script, memo::MemoBytes as Memo, @@ -17,7 +32,6 @@ use crate::zcash::primitives::{ sapling::{redjubjub::Signature, Node, PaymentAddress, ProofGenerationKey, Rseed}, transaction::components::{Amount, OutPoint}, }; -use serde::{Deserialize, Serialize}; use crate::{ errors::Error, @@ -26,57 +40,53 @@ use crate::{ #[derive(Debug, Deserialize)] pub struct TinData { - pub path: [u32; 5], + pub path: [u32; 5], // Expected: Array of 5 u32 values #[serde(deserialize_with = "script_deserialize")] - pub address: Script, + pub address: Script, // Expected: Hex-encoded string representing a Script #[serde(deserialize_with = "amount_deserialize")] - pub value: Amount, + pub value: Amount, // Expected: u64 value representing an Amount } #[derive(Debug, Deserialize)] pub struct ToutData { #[serde(deserialize_with = "script_deserialize")] - pub address: Script, + pub address: Script, // Expected: Hex-encoded string representing a Script #[serde(deserialize_with = "amount_deserialize")] - pub value: Amount, + pub value: Amount, // Expected: u64 value representing an Amount } #[derive(Debug, Deserialize)] -pub struct ShieldedSpendData { - pub path: u32, +pub struct SaplingInData { + pub path: u32, // Expected: Single u32 value #[serde(deserialize_with = "s_address_deserialize")] - pub address: PaymentAddress, + pub address: PaymentAddress, // Expected: Hex-encoded string representing a PaymentAddress #[serde(deserialize_with = "amount_deserialize")] - pub value: Amount, + pub value: Amount, // Expected: u64 value representing an Amount } #[derive(Debug, Deserialize)] -pub struct ShieldedOutputData { +pub struct SaplingOutData { #[serde(deserialize_with = "s_address_deserialize")] - pub address: PaymentAddress, + pub address: PaymentAddress, // Expected: Hex-encoded string representing a PaymentAddress #[serde(deserialize_with = "amount_deserialize")] - pub value: Amount, - pub memo_type: u8, - #[serde(deserialize_with = "ovk_deserialize")] - pub ovk: Option, + pub value: Amount, // Expected: u64 value representing an Amount + pub memo_type: u8, // Expected: Single byte value + #[serde(deserialize_with = "ovk_deserialize", default)] + pub ovk: Option, // Expected: Optional hex-encoded string representing an OutgoingViewingKey } #[derive(Debug, Deserialize)] pub struct InitData { pub t_in: Vec, pub t_out: Vec, - pub s_spend: Vec, - pub s_output: Vec, + pub s_spend: Vec, + pub s_output: Vec, } impl InitData { pub fn to_hsm_bytes(&self) -> Vec { - let mut data = vec![ - self.t_in.len() as u8, - self.t_out.len() as u8, - self.s_spend.len() as u8, - self.s_output.len() as u8, - ]; + let mut data = + vec![self.t_in.len() as u8, self.t_out.len() as u8, self.s_spend.len() as u8, self.s_output.len() as u8]; for info in self.t_in.iter() { for p in info.path.iter() { @@ -126,16 +136,24 @@ impl HsmTxData { pub fn to_hsm_bytes(&self) -> Result, Error> { let mut data = Vec::new(); for t_data in self.t_script_data.iter() { - t_data.write(&mut data)?; + t_data + .write(&mut data) + .map_err(|_| Error::ReadWriteError)?; } for spend_old_data in self.s_spend_old_data.iter() { - spend_old_data.write(&mut data)?; + spend_old_data + .write(&mut data) + .map_err(|_| Error::ReadWriteError)?; } for spend_new_data in self.s_spend_new_data.iter() { - spend_new_data.write(&mut data)?; + spend_new_data + .write(&mut data) + .map_err(|_| Error::ReadWriteError)?; } for output_data in self.s_output_data.iter() { - output_data.write(&mut data)?; + output_data + .write(&mut data) + .map_err(|_| Error::ReadWriteError)?; } data.extend_from_slice(&self.tx_hash_data.to_bytes()); Ok(data) @@ -144,41 +162,41 @@ impl HsmTxData { #[derive(Debug, Deserialize)] pub struct TransparentInputBuilderInfo { - #[serde(deserialize_with = "outpoint_deserialize")] - pub outp: OutPoint, + #[serde(deserialize_with = "t_outpoint_deserialize")] + pub outp: OutPoint, // Expected: Hex-encoded string representing an OutPoint #[serde(deserialize_with = "t_pk_deserialize")] - pub pk: secp256k1::PublicKey, + pub pk: secp256k1::PublicKey, // Expected: Hex-encoded string representing a PublicKey #[serde(deserialize_with = "script_deserialize")] - pub address: Script, + pub address: Script, // Expected: Hex-encoded string representing a Script #[serde(deserialize_with = "amount_deserialize")] - pub value: Amount, + pub value: Amount, // Expected: u64 value representing an Amount } #[derive(Debug, Deserialize)] pub struct TransparentOutputBuilderInfo { #[serde(deserialize_with = "script_deserialize")] - pub address: Script, - //26 + pub address: Script, // Expected: Hex-encoded string representing a Script + // 26 #[serde(deserialize_with = "amount_deserialize")] - pub value: Amount, //8 + pub value: Amount, // 8 } #[derive(Deserialize)] pub struct SpendBuilderInfo { #[serde(deserialize_with = "pgk_deserialize")] - pub proofkey: ProofGenerationKey, + pub proofkey: ProofGenerationKey, // Expected: Hex-encoded string representing a ProofGenerationKey #[serde(deserialize_with = "fr_deserialize")] - pub rcv: jubjub::Fr, + pub rcv: jubjub::Fr, // Expected: Hex-encoded string representing a Fr #[serde(deserialize_with = "fr_deserialize")] - pub alpha: jubjub::Fr, + pub alpha: jubjub::Fr, // Expected: Hex-encoded string representing a Fr #[serde(deserialize_with = "s_address_deserialize")] - pub address: PaymentAddress, + pub address: PaymentAddress, // Expected: Hex-encoded string representing a PaymentAddress #[serde(deserialize_with = "amount_deserialize")] - pub value: Amount, + pub value: Amount, // Expected: u64 value representing an Amount #[serde(deserialize_with = "merkle_path_deserialize")] - pub witness: MerklePath, + pub witness: MerklePath, // Expected: Hex-encoded string representing a MerklePath #[serde(deserialize_with = "rseed_deserialize")] - pub rseed: Rseed, + pub rseed: Rseed, // Expected: Hex-encoded string representing a Rseed } /// An outgoing viewing key @@ -188,25 +206,26 @@ pub struct HashSeed(pub [u8; 32]); #[derive(Debug, Deserialize)] pub struct OutputBuilderInfo { #[serde(deserialize_with = "fr_deserialize")] - pub rcv: jubjub::Fr, + pub rcv: jubjub::Fr, // Expected: Hex-encoded string representing a Fr #[serde(deserialize_with = "rseed_deserialize")] - pub rseed: Rseed, - #[serde(deserialize_with = "ovk_deserialize")] - pub ovk: Option, + pub rseed: Rseed, // Expected: Hex-encoded string representing a Rseed + #[serde(deserialize_with = "ovk_deserialize", default)] + pub ovk: Option, // Expected: Optional hex-encoded string representing an OutgoingViewingKey #[serde(deserialize_with = "s_address_deserialize")] - pub address: PaymentAddress, + pub address: PaymentAddress, // Expected: Hex-encoded string representing a PaymentAddress #[serde(deserialize_with = "amount_deserialize")] - pub value: Amount, - #[serde(deserialize_with = "memo_deserialize")] - pub memo: Option, - #[serde(deserialize_with = "hashseed_deserialize")] - pub hash_seed: Option, + pub value: Amount, // Expected: u64 value representing an Amount + #[serde(deserialize_with = "memo_deserialize", default)] + pub memo: Option, // Expected: Optional hex-encoded string representing a Memo + // #[serde(deserialize_with = "hashseed_deserialize")] + #[serde(deserialize_with = "hashseed_deserialize", default)] + pub hash_seed: Option, // Expected: Optional hex-encoded string representing a HashSeed } #[derive(Debug, Deserialize)] pub struct TransactionSignatures { #[serde(deserialize_with = "t_sig_deserialize")] - pub transparent_sigs: Vec, + pub transparent_sigs: Vec, // Expected: List of hex-encoded strings representing secp256k1::ecdsa::Signature #[serde(deserialize_with = "s_sig_deserialize")] - pub spend_sigs: Vec, + pub sapling_sigs: Vec, // Expected: List of hex-encoded strings representing Signature } diff --git a/zcash-hsmbuilder/src/data/neon_bridge.rs b/ledger-zcash-builder/src/data/neon_bridge.rs similarity index 64% rename from zcash-hsmbuilder/src/data/neon_bridge.rs rename to ledger-zcash-builder/src/data/neon_bridge.rs index d8e4a67..832a27c 100644 --- a/zcash-hsmbuilder/src/data/neon_bridge.rs +++ b/ledger-zcash-builder/src/data/neon_bridge.rs @@ -1,11 +1,9 @@ use std::str::*; -use crate::HashSeed; use group::GroupEncoding; use jubjub::{Fr, SubgroupPoint}; use serde::{de::Error, Deserialize, Deserializer, Serializer}; - -use crate::zcash::primitives::{ +use zcash_primitives::{ keys::OutgoingViewingKey, legacy::Script, memo::MemoBytes as Memo, @@ -14,7 +12,15 @@ use crate::zcash::primitives::{ transaction::components::{Amount, OutPoint}, }; -pub fn outpoint_deserialize<'de, D>(deserializer: D) -> Result +use crate::HashSeed; + +/** + * Deserializes a transaction outpoint from a hex-encoded string. + * + * @param deserializer - The deserializer instance. + * @returns A result containing the deserialized `OutPoint` or an error. + */ +pub fn t_outpoint_deserialize<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { @@ -24,6 +30,12 @@ where OutPoint::read(&bytes[..]).map_err(D::Error::custom) } +/** + * Deserializes a public key from a hex-encoded string. + * + * @param deserializer - The deserializer instance. + * @returns A result containing the deserialized `secp256k1::PublicKey` or an error. + */ pub fn t_pk_deserialize<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, @@ -32,6 +44,12 @@ where secp256k1::PublicKey::from_str(&str).map_err(D::Error::custom) } +/** + * Deserializes a script from a hex-encoded string. + * + * @param deserializer - The deserializer instance. + * @returns A result containing the deserialized `Script` or an error. + */ pub fn script_deserialize<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, @@ -42,6 +60,12 @@ where Script::read(&bytes[..]).map_err(D::Error::custom) } +/** + * Deserializes an amount from a u64 value. + * + * @param deserializer - The deserializer instance. + * @returns A result containing the deserialized `Amount` or an error. + */ pub fn amount_deserialize<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, @@ -54,6 +78,12 @@ where } } +/** + * Deserializes a field element `Fr` from a hex-encoded string. + * + * @param deserializer - The deserializer instance. + * @returns A result containing the deserialized `Fr` or an error. + */ pub fn fr_deserialize<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, @@ -69,6 +99,12 @@ where } } +/** + * Deserializes a `ProofGenerationKey` from a hex-encoded string. + * + * @param deserializer - The deserializer instance. + * @returns A result containing the deserialized `ProofGenerationKey` or an error. + */ pub fn pgk_deserialize<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, @@ -78,22 +114,25 @@ where hex::decode_to_slice(&str, &mut bytes).map_err(D::Error::custom)?; let mut akb = [0u8; 32]; - akb.copy_from_slice(&bytes[0..32]); + akb.copy_from_slice(&bytes[0 .. 32]); let mut nskb = [0u8; 32]; - nskb.copy_from_slice(&bytes[32..64]); + nskb.copy_from_slice(&bytes[32 .. 64]); let ak = SubgroupPoint::from_bytes(&akb); let nsk = jubjub::Fr::from_bytes(&nskb); if ak.is_some().into() && nsk.is_some().into() { - Ok(ProofGenerationKey { - ak: ak.unwrap(), - nsk: nsk.unwrap(), - }) + Ok(ProofGenerationKey { ak: ak.unwrap(), nsk: nsk.unwrap() }) } else { Err(D::Error::custom("error deserializing to pgk")) } } +/** + * Deserializes a `PaymentAddress` from a hex-encoded string. + * + * @param deserializer - The deserializer instance. + * @returns A result containing the deserialized `PaymentAddress` or an error. + */ pub fn s_address_deserialize<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, @@ -110,6 +149,12 @@ where } } +/** + * Deserializes an `OutgoingViewingKey` from an optional hex-encoded string. + * + * @param deserializer - The deserializer instance. + * @returns A result containing the deserialized `Option` or an error. + */ pub fn ovk_deserialize<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, @@ -124,6 +169,12 @@ where } } +/** + * Deserializes a `Memo` from a hex-encoded string, handling empty or invalid formats. + * + * @param deserializer - The deserializer instance. + * @returns A result containing the deserialized `Option` or an error. + */ pub fn memo_deserialize<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, @@ -143,6 +194,12 @@ where } } +/** + * Deserializes a `MerklePath` for a `Node` from a hex-encoded string. + * + * @param deserializer - The deserializer instance. + * @returns A result containing the deserialized `MerklePath` or an error. + */ pub fn merkle_path_deserialize<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, @@ -157,6 +214,12 @@ where Ok(path) } +/** + * Deserializes a `Rseed` from a hex-encoded string. + * + * @param deserializer - The deserializer instance. + * @returns A result containing the deserialized `Rseed` or an error. + */ pub fn rseed_deserialize<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, @@ -168,7 +231,13 @@ where Ok(rseed) } -pub fn t_sig_deserialize<'de, D>(deserializer: D) -> Result, D::Error> +/** + * Deserializes a list of `secp256k1::ecdsa::Signature` from a list of hex-encoded strings. + * + * @param deserializer - The deserializer instance. + * @returns A result containing the deserialized list of `secp256k1::ecdsa::Signature` or an error. + */ +pub fn t_sig_deserialize<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { @@ -180,19 +249,23 @@ where let mut v = Vec::new(); for item in str.iter() { if item.len() != 128 { - return Err(D::Error::custom( - "not enough bytes deserializing to transparent sig", - )); + return Err(D::Error::custom("not enough bytes deserializing to transparent sig")); } let mut bytes = [0u8; 64]; hex::decode_to_slice(item, &mut bytes).map_err(D::Error::custom)?; - let s = secp256k1::Signature::from_compact(&bytes).map_err(D::Error::custom)?; + let s = secp256k1::ecdsa::Signature::from_compact(&bytes).map_err(D::Error::custom)?; v.push(s); } Ok(v) } } +/** + * Deserializes a list of `Signature` from a list of hex-encoded strings. + * + * @param deserializer - The deserializer instance. + * @returns A result containing the deserialized list of `Signature` or an error. + */ pub fn s_sig_deserialize<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, @@ -205,9 +278,7 @@ where let mut v = Vec::new(); for item in str.iter().take(n) { if item.len() != 128 { - return Err(D::Error::custom( - "not enough bytes deserializing to transparent sig", - )); + return Err(D::Error::custom("not enough bytes deserializing to transparent sig")); } let mut bytes = [0u8; 64]; hex::decode_to_slice(item, &mut bytes).map_err(D::Error::custom)?; @@ -221,6 +292,12 @@ where } } +/** + * Deserializes an optional `HashSeed` from an optional hex-encoded string. + * + * @param deserializer - The deserializer instance. + * @returns A result containing the deserialized `Option` or an error. + */ pub fn hashseed_deserialize<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, diff --git a/zcash-hsmbuilder/src/data/sighashdata.rs b/ledger-zcash-builder/src/data/sighashdata.rs similarity index 80% rename from zcash-hsmbuilder/src/data/sighashdata.rs rename to ledger-zcash-builder/src/data/sighashdata.rs index c6803f6..e6d2b61 100644 --- a/zcash-hsmbuilder/src/data/sighashdata.rs +++ b/ledger-zcash-builder/src/data/sighashdata.rs @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2022 Zondax GmbH +* (c) 2022 Zondax AG * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,21 +17,17 @@ use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams}; use byteorder::*; use ff::PrimeField; use group::GroupEncoding; - -use crate::{ - data::sighashdata_v4, - data::sighashdata_v5, - hsmauth, - zcash::primitives::{ - consensus, - transaction::{ - self, - components::{sapling, sprout, transparent}, - TransactionData, TxDigests, - }, +use zcash_primitives::{ + consensus, + transaction::{ + self, + components::{sapling, sprout, transparent}, + TransactionData, TxDigests, }, }; +use crate::{data::sighashdata_v4, data::sighashdata_v5, hsmauth}; + pub const SIGHASH_NONE: u8 = 0x02; pub const SIGHASH_SINGLE: u8 = 0x03; pub const SIGHASH_MASK: u8 = 0x1f; @@ -106,16 +102,40 @@ impl TransactionDataSighashV5 { // header_digest fields data.extend_from_slice(&self.header_pre_digest.version); data.extend_from_slice(&self.header_pre_digest.version_group_id); - data.extend_from_slice(&self.header_pre_digest.consensus_branch_id); + data.extend_from_slice( + &self + .header_pre_digest + .consensus_branch_id, + ); data.extend_from_slice(&self.header_pre_digest.lock_time); data.extend_from_slice(&self.header_pre_digest.expiry_height); // transparent_digest fields - data.extend_from_slice(&self.transparent_pre_digest.prevouts_digest); - data.extend_from_slice(&self.transparent_pre_digest.sequence_digest); - data.extend_from_slice(&self.transparent_pre_digest.outputs_digest); + data.extend_from_slice( + &self + .transparent_pre_digest + .prevouts_digest, + ); + data.extend_from_slice( + &self + .transparent_pre_digest + .sequence_digest, + ); + data.extend_from_slice( + &self + .transparent_pre_digest + .outputs_digest, + ); // sapling_digest fields - data.extend_from_slice(&self.sapling_pre_digest.sapling_spends_digest); - data.extend_from_slice(&self.sapling_pre_digest.sapling_outputs_digest); + data.extend_from_slice( + &self + .sapling_pre_digest + .sapling_spends_digest, + ); + data.extend_from_slice( + &self + .sapling_pre_digest + .sapling_outputs_digest, + ); data.extend_from_slice(&self.sapling_pre_digest.value_balance); // orchard_digest data.extend_from_slice(&self.orchard_digest); @@ -133,8 +153,9 @@ enum SigHashVersion { } impl SigHashVersion { + //noinspection RsNonExhaustiveMatch fn from_tx(tx: &TransactionData) -> Self { - use crate::zcash::primitives::transaction::TxVersion; + use zcash_primitives::transaction::TxVersion; match tx.version() { TxVersion::Sprout(_) => SigHashVersion::Sprout, TxVersion::Overwinter => SigHashVersion::Overwinter, @@ -156,7 +177,7 @@ where SigHashVersion::NU5 => sighashdata_v5::signature_hash_input_data_v5(tx, hash_type), SigHashVersion::Overwinter | SigHashVersion::Sapling => { sighashdata_v4::signature_hash_input_data_v4(tx, hash_type) - } + }, SigHashVersion::Sprout => unimplemented!(), } } diff --git a/zcash-hsmbuilder/src/data/sighashdata_v4.rs b/ledger-zcash-builder/src/data/sighashdata_v4.rs similarity index 79% rename from zcash-hsmbuilder/src/data/sighashdata_v4.rs rename to ledger-zcash-builder/src/data/sighashdata_v4.rs index 8adae00..6af1b14 100644 --- a/zcash-hsmbuilder/src/data/sighashdata_v4.rs +++ b/ledger-zcash-builder/src/data/sighashdata_v4.rs @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2022 Zondax GmbH +* (c) 2022 Zondax AG * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,23 +17,20 @@ use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams}; use byteorder::*; use ff::PrimeField; use group::GroupEncoding; +use zcash_primitives::{ + consensus, + transaction::{ + self, + components::{sapling, sprout, transparent}, + TransactionData, + }, +}; use crate::data::sighashdata::{ - TransactionDataSighash, TransactionDataSighashV4, OVERWINTER_VERSION_GROUP_ID, - SAPLING_TX_VERSION, SAPLING_VERSION_GROUP_ID, SIGHASH_ANYONECANPAY, SIGHASH_MASK, SIGHASH_NONE, - SIGHASH_SINGLE, -}; -use crate::{ - hsmauth, - zcash::primitives::{ - consensus, - transaction::{ - self, - components::{sapling, sprout, transparent}, - TransactionData, - }, - }, + TransactionDataSighash, TransactionDataSighashV4, OVERWINTER_VERSION_GROUP_ID, SAPLING_TX_VERSION, + SAPLING_VERSION_GROUP_ID, SIGHASH_ANYONECANPAY, SIGHASH_MASK, SIGHASH_NONE, SIGHASH_SINGLE, }; +use crate::hsmauth; const ZCASH_SIGHASH_PERSONALIZATION_PREFIX: &[u8; 12] = b"ZcashSigHash"; const ZCASH_PREVOUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashPrevoutHash"; @@ -45,9 +42,11 @@ const ZCASH_SHIELDED_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashSOutputHas macro_rules! write_u32 { ($h:expr, $value:expr, $tmp:expr) => { - //LittleEndian::write_u32(&mut $tmp[..4],$value); - (&mut $tmp[..4]).write_u32::($value).unwrap(); - $h.copy_from_slice(&$tmp[..4]); + // LittleEndian::write_u32(&mut $tmp[..4],$value); + (&mut $tmp[.. 4]) + .write_u32::($value) + .unwrap(); + $h.copy_from_slice(&$tmp[.. 4]); }; } @@ -75,7 +74,8 @@ fn prevout_hash_v4(vins: &[transparent::TxIn]) fn sequence_hash_v4(vins: &[transparent::TxIn]) -> Blake2bHash { let mut data = Vec::with_capacity(vins.len() * 4); for t_in in vins { - data.write_u32::(t_in.sequence).unwrap(); + data.write_u32::(t_in.sequence) + .unwrap(); } Blake2bParams::new() .hash_length(32) @@ -137,9 +137,7 @@ where .hash(&data) } -fn shielded_outputs_hash_v4( - shielded_outputs: &[sapling::OutputDescription], -) -> Blake2bHash { +fn shielded_outputs_hash_v4(shielded_outputs: &[sapling::OutputDescription]) -> Blake2bHash { let mut data = Vec::with_capacity(shielded_outputs.len() * 948); for s_out in shielded_outputs { s_out.write_v4(&mut data).unwrap(); @@ -165,7 +163,7 @@ where write_u32!(txdata_sighash.header, header, tmp); write_u32!(txdata_sighash.version_id, version_group_id, tmp); - //transparent data + // transparent data // replace vin and vout with empty slices // if we don't have the bundle if let Some((vin, vout)) = tx @@ -173,11 +171,7 @@ where .map(|b| (b.vin.as_slice(), b.vout.as_slice())) .or(Some((&[], &[]))) { - update_data!( - txdata_sighash.prevoutshash, - hash_type & SIGHASH_ANYONECANPAY == 0, - prevout_hash_v4(vin) - ); //true for sighash_all + update_data!(txdata_sighash.prevoutshash, hash_type & SIGHASH_ANYONECANPAY == 0, prevout_hash_v4(vin)); // true for sighash_all update_data!( txdata_sighash.sequencehash, @@ -185,47 +179,52 @@ where && (hash_type & SIGHASH_MASK) != SIGHASH_SINGLE && (hash_type & SIGHASH_MASK) != SIGHASH_NONE, sequence_hash_v4(vin) - ); //true for sighash_all + ); // true for sighash_all - if (hash_type & SIGHASH_MASK) != SIGHASH_SINGLE - && (hash_type & SIGHASH_MASK) != SIGHASH_NONE - { + if (hash_type & SIGHASH_MASK) != SIGHASH_SINGLE && (hash_type & SIGHASH_MASK) != SIGHASH_NONE { txdata_sighash .outputshash - .copy_from_slice(outputs_hash_v4(vout).as_ref()); //true for sighash all + .copy_from_slice(outputs_hash_v4(vout).as_ref()); // true for sighash all - //TODO: single output hash? SIGHASH_SINGLE + // TODO: single output hash? SIGHASH_SINGLE } else { - txdata_sighash.outputshash.copy_from_slice(&[0; 32]); + txdata_sighash + .outputshash + .copy_from_slice(&[0; 32]); }; } - //sprout data + // sprout data update_data!( txdata_sighash.joinsplitshash, - !tx.sprout_bundle().map_or(true, |b| b.joinsplits.is_empty()), + !tx.sprout_bundle() + .map_or(true, |b| b.joinsplits.is_empty()), { let bundle = tx.sprout_bundle().unwrap(); - joinsplits_hash_v4( - tx.consensus_branch_id(), - &bundle.joinsplits, - &bundle.joinsplit_pubkey, - ) + joinsplits_hash_v4(tx.consensus_branch_id(), &bundle.joinsplits, &bundle.joinsplit_pubkey) } ); - //sapling data + // sapling data update_data!( txdata_sighash.shieldedspendhash, !tx.sapling_bundle() .map_or(true, |b| b.shielded_spends.is_empty()), - shielded_spends_hash_v4(&tx.sapling_bundle().unwrap().shielded_spends) + shielded_spends_hash_v4( + &tx.sapling_bundle() + .unwrap() + .shielded_spends + ) ); update_data!( txdata_sighash.shieldedoutputhash, !tx.sapling_bundle() .map_or(true, |b| b.shielded_outputs.is_empty()), - shielded_outputs_hash_v4(&tx.sapling_bundle().unwrap().shielded_outputs) + shielded_outputs_hash_v4( + &tx.sapling_bundle() + .unwrap() + .shielded_outputs + ) ); write_u32!(txdata_sighash.lock_time, tx.lock_time(), tmp); diff --git a/zcash-hsmbuilder/src/data/sighashdata_v5.rs b/ledger-zcash-builder/src/data/sighashdata_v5.rs similarity index 69% rename from zcash-hsmbuilder/src/data/sighashdata_v5.rs rename to ledger-zcash-builder/src/data/sighashdata_v5.rs index c8806c9..acf51b0 100644 --- a/zcash-hsmbuilder/src/data/sighashdata_v5.rs +++ b/ledger-zcash-builder/src/data/sighashdata_v5.rs @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2022 Zondax GmbH +* (c) 2022 Zondax AG * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,27 +13,28 @@ * See the License for the specific language governing permissions and * limitations under the License. ********************************************************************************/ +use std::borrow::Borrow; +use std::io::Write; + use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams, State}; use byteorder::*; use ff::PrimeField; use group::GroupEncoding; -use std::borrow::Borrow; -use std::io::Write; +use zcash_primitives::{ + consensus, + transaction::{ + self, + components::{sapling, sprout, transparent, Amount}, + TransactionData, TxVersion, + }, +}; use crate::{ data::sighashdata::{ - TransactionDataSighash, TransactionDataSighashV5, SIGHASH_ANYONECANPAY, SIGHASH_MASK, - SIGHASH_NONE, SIGHASH_SINGLE, + TransactionDataSighash, TransactionDataSighashV5, SIGHASH_ANYONECANPAY, SIGHASH_MASK, SIGHASH_NONE, + SIGHASH_SINGLE, }, hsmauth, - zcash::primitives::{ - consensus, - transaction::{ - self, - components::{sapling, sprout, transparent, Amount}, - TransactionData, TxVersion, - }, - }, }; /// TxId tree root personalization @@ -101,7 +102,7 @@ fn hasher(personal: &[u8; 16]) -> State { /// In the case that no inputs are provided, this produces a default /// hash from just the personalization string. pub(crate) fn transparent_prevout_hash_v5( - vin: &[transparent::TxIn], + vin: &[transparent::TxIn] ) -> Blake2bHash { let mut h = hasher(ZCASH_PREVOUTS_HASH_PERSONALIZATION_V5); for t_in in vin { @@ -113,11 +114,12 @@ pub(crate) fn transparent_prevout_hash_v5( - vin: &[transparent::TxIn], + vin: &[transparent::TxIn] ) -> Blake2bHash { let mut h = hasher(ZCASH_SEQUENCE_HASH_PERSONALIZATION_V5); for t_in in vin { - h.write_u32::(t_in.sequence).unwrap(); + h.write_u32::(t_in.sequence) + .unwrap(); } h.finalize() } @@ -126,9 +128,7 @@ pub(crate) fn transparent_sequence_hash_v5>( - vout: &[T], -) -> Blake2bHash { +pub(crate) fn transparent_outputs_hash_v5>(vout: &[T]) -> Blake2bHash { let mut h = hasher(ZCASH_OUTPUTS_HASH_PERSONALIZATION_V5); for t_out in vout { t_out.borrow().write(&mut h).unwrap(); @@ -137,12 +137,15 @@ pub(crate) fn transparent_outputs_hash_v5>( } /// Write disjoint parts of each Sapling shielded spend to a pair of hashes: -/// * \[nullifier*\] - personalized with ZCASH_SAPLING_SPENDS_COMPACT_HASH_PERSONALIZATION_V5 -/// * \[(cv, anchor, rk, zkproof)*\] - personalized with ZCASH_SAPLING_SPENDS_NONCOMPACT_HASH_PERSONALIZATION_V5 +/// * \[nullifier*\] - personalized with +/// ZCASH_SAPLING_SPENDS_COMPACT_HASH_PERSONALIZATION_V5 +/// * \[(cv, anchor, rk, zkproof)*\] - personalized with +/// ZCASH_SAPLING_SPENDS_NONCOMPACT_HASH_PERSONALIZATION_V5 /// -/// Then, hash these together personalized by ZCASH_SAPLING_SPENDS_HASH_PERSONALIZATION_V5 +/// Then, hash these together personalized by +/// ZCASH_SAPLING_SPENDS_HASH_PERSONALIZATION_V5 pub(crate) fn hash_sapling_spends_v5( - shielded_spends: &[sapling::SpendDescription], + shielded_spends: &[sapling::SpendDescription] ) -> Blake2bHash { let mut h = hasher(ZCASH_SAPLING_SPENDS_HASH_PERSONALIZATION_V5); if !shielded_spends.is_empty() { @@ -150,45 +153,60 @@ pub(crate) fn hash_sapling_spends_v5( let mut nh = hasher(ZCASH_SAPLING_SPENDS_NONCOMPACT_HASH_PERSONALIZATION_V5); for s_spend in shielded_spends { // we build the hash of nullifiers separately for compact blocks. - ch.write_all(s_spend.nullifier.as_ref()).unwrap(); - - nh.write_all(&s_spend.cv.to_bytes()).unwrap(); - nh.write_all(&s_spend.anchor.to_repr()).unwrap(); - nh.write_all(&s_spend.rk.0.to_bytes()).unwrap(); + ch.write_all(s_spend.nullifier.as_ref()) + .unwrap(); + + nh.write_all(&s_spend.cv.to_bytes()) + .unwrap(); + nh.write_all(&s_spend.anchor.to_repr()) + .unwrap(); + nh.write_all(&s_spend.rk.0.to_bytes()) + .unwrap(); } let compact_digest = ch.finalize(); - h.write_all(compact_digest.as_bytes()).unwrap(); + h.write_all(compact_digest.as_bytes()) + .unwrap(); let noncompact_digest = nh.finalize(); - h.write_all(noncompact_digest.as_bytes()).unwrap(); + h.write_all(noncompact_digest.as_bytes()) + .unwrap(); } h.finalize() } /// Write disjoint parts of each Sapling shielded output as 3 separate hashes: -/// * \[(cmu, epk, enc_ciphertext\[..52\])*\] personalized with ZCASH_SAPLING_OUTPUTS_COMPACT_HASH_PERSONALIZATION_V5 -/// * \[enc_ciphertext\[52..564\]*\] (memo ciphertexts) personalized with ZCASH_SAPLING_OUTPUTS_MEMOS_HASH_PERSONALIZATION_V5 -/// * \[(cv, enc_ciphertext\[564..\], out_ciphertext, zkproof)*\] personalized with ZCASH_SAPLING_OUTPUTS_NONCOMPACT_HASH_PERSONALIZATION_V5 +/// * \[(cmu, epk, enc_ciphertext\[..52\])*\] personalized with +/// ZCASH_SAPLING_OUTPUTS_COMPACT_HASH_PERSONALIZATION_V5 +/// * \[enc_ciphertext\[52..564\]*\] (memo ciphertexts) personalized with +/// ZCASH_SAPLING_OUTPUTS_MEMOS_HASH_PERSONALIZATION_V5 +/// * \[(cv, enc_ciphertext\[564..\], out_ciphertext, zkproof)*\] personalized +/// with ZCASH_SAPLING_OUTPUTS_NONCOMPACT_HASH_PERSONALIZATION_V5 /// -/// Then, hash these together personalized with ZCASH_SAPLING_OUTPUTS_HASH_PERSONALIZATION_V5 -pub(crate) fn hash_sapling_outputs_v5( - shielded_outputs: &[sapling::OutputDescription], -) -> Blake2bHash { +/// Then, hash these together personalized with +/// ZCASH_SAPLING_OUTPUTS_HASH_PERSONALIZATION_V5 +pub(crate) fn hash_sapling_outputs_v5(shielded_outputs: &[sapling::OutputDescription]) -> Blake2bHash { let mut h = hasher(ZCASH_SAPLING_OUTPUTS_HASH_PERSONALIZATION_V5); if !shielded_outputs.is_empty() { let mut ch = hasher(ZCASH_SAPLING_OUTPUTS_COMPACT_HASH_PERSONALIZATION_V5); let mut mh = hasher(ZCASH_SAPLING_OUTPUTS_MEMOS_HASH_PERSONALIZATION_V5); let mut nh = hasher(ZCASH_SAPLING_OUTPUTS_NONCOMPACT_HASH_PERSONALIZATION_V5); for s_out in shielded_outputs { - ch.write_all(s_out.cmu.to_repr().as_ref()).unwrap(); - ch.write_all(s_out.ephemeral_key.as_ref()).unwrap(); - ch.write_all(&s_out.enc_ciphertext[..52]).unwrap(); - - mh.write_all(&s_out.enc_ciphertext[52..564]).unwrap(); - - nh.write_all(&s_out.cv.to_bytes()).unwrap(); - nh.write_all(&s_out.enc_ciphertext[564..]).unwrap(); - nh.write_all(&s_out.out_ciphertext).unwrap(); + ch.write_all(s_out.cmu.to_repr().as_ref()) + .unwrap(); + ch.write_all(s_out.ephemeral_key.as_ref()) + .unwrap(); + ch.write_all(&s_out.enc_ciphertext[.. 52]) + .unwrap(); + + mh.write_all(&s_out.enc_ciphertext[52 .. 564]) + .unwrap(); + + nh.write_all(&s_out.cv.to_bytes()) + .unwrap(); + nh.write_all(&s_out.enc_ciphertext[564 ..]) + .unwrap(); + nh.write_all(&s_out.out_ciphertext) + .unwrap(); } let ch_fin = ch.finalize(); @@ -215,31 +233,35 @@ fn hash_header_txid_data_v5( ) -> Blake2bHash { let mut h = hasher(ZCASH_HEADERS_HASH_PERSONALIZATION_V5); - h.write_u32::(version.header()).unwrap(); + h.write_u32::(version.header()) + .unwrap(); h.write_u32::(version.version_group_id()) .unwrap(); h.write_u32::(consensus_branch_id.into()) .unwrap(); - h.write_u32::(lock_time).unwrap(); - h.write_u32::(expiry_height.into()).unwrap(); + h.write_u32::(lock_time) + .unwrap(); + h.write_u32::(expiry_height.into()) + .unwrap(); h.finalize() } -//todo: delete, just for testing +// todo: delete, just for testing fn hash_transparent_txid_data(t_digests: Option) -> Blake2bHash { let mut h = hasher(ZCASH_TRANSPARENT_HASH_PERSONALIZATION_V5); if let Some(d) = t_digests { - h.write_all(d.prevouts_digest.as_slice()).unwrap(); - h.write_all(d.sequence_digest.as_slice()).unwrap(); - h.write_all(d.outputs_digest.as_slice()).unwrap(); + h.write_all(d.prevouts_digest.as_slice()) + .unwrap(); + h.write_all(d.sequence_digest.as_slice()) + .unwrap(); + h.write_all(d.outputs_digest.as_slice()) + .unwrap(); } h.finalize() } -fn hash_sapling_txid_data( - bundle: Option<&sapling::Bundle>, -) -> Blake2bHash { +fn hash_sapling_txid_data(bundle: Option<&sapling::Bundle>) -> Blake2bHash { let mut h = hasher(ZCASH_SAPLING_HASH_PERSONALIZATION_V5); if let Some(b) = bundle { h.write_all(hash_sapling_spends_v5(&b.shielded_spends).as_bytes()) @@ -248,7 +270,8 @@ fn hash_sapling_txid_data( h.write_all(hash_sapling_outputs_v5(&b.shielded_outputs).as_bytes()) .unwrap(); - h.write_all(&b.value_balance.to_i64_le_bytes()).unwrap(); + h.write_all(&b.value_balance.to_i64_le_bytes()) + .unwrap(); } h.finalize() } @@ -267,19 +290,31 @@ where let mut tmp = [0; 8]; // header_digest = BLAKE2b-256 hash of the following values - // version || version_group_id || consensus_branch_id || lock_time || expiry_height + // version || version_group_id || consensus_branch_id || lock_time || + // expiry_height let header = tx.version().header().to_le_bytes(); - let version_group_id = tx.version().version_group_id().to_le_bytes(); + let version_group_id = tx + .version() + .version_group_id() + .to_le_bytes(); let consensus_branch_id = u32::from(tx.consensus_branch_id()).to_le_bytes(); let lock_time = tx.lock_time().to_le_bytes(); let expiry_height = u32::from(tx.expiry_height()).to_le_bytes(); // header_digest fields txdata_sighash.header_pre_digest.version = header; - txdata_sighash.header_pre_digest.version_group_id = version_group_id; - txdata_sighash.header_pre_digest.consensus_branch_id = consensus_branch_id; - txdata_sighash.header_pre_digest.lock_time = lock_time; - txdata_sighash.header_pre_digest.expiry_height = expiry_height; + txdata_sighash + .header_pre_digest + .version_group_id = version_group_id; + txdata_sighash + .header_pre_digest + .consensus_branch_id = consensus_branch_id; + txdata_sighash + .header_pre_digest + .lock_time = lock_time; + txdata_sighash + .header_pre_digest + .expiry_height = expiry_height; let binding_in = [].to_vec(); let vin = match &tx.transparent_bundle() { @@ -293,30 +328,28 @@ where None => &binding_out, }; - //transparent_digest fields + // transparent_digest fields let mut prevouts_digest = [0u8; 32]; let mut sequence_digest = [0u8; 32]; let mut outputs_digest = [0u8; 32]; prevouts_digest.copy_from_slice(transparent_prevout_hash_v5(vin).as_bytes()); sequence_digest.copy_from_slice(transparent_sequence_hash_v5(vin).as_bytes()); outputs_digest.copy_from_slice(transparent_outputs_hash_v5(vout).as_bytes()); - txdata_sighash.transparent_pre_digest.prevouts_digest = prevouts_digest; - txdata_sighash.transparent_pre_digest.sequence_digest = sequence_digest; - txdata_sighash.transparent_pre_digest.outputs_digest = outputs_digest; + txdata_sighash + .transparent_pre_digest + .prevouts_digest = prevouts_digest; + txdata_sighash + .transparent_pre_digest + .sequence_digest = sequence_digest; + txdata_sighash + .transparent_pre_digest + .outputs_digest = outputs_digest; let sapling_binding_in = [].to_vec(); let sapling_binding_out = [].to_vec(); let (shielded_spends, shielded_outputs, vb) = match &tx.sapling_bundle() { - Some(z_tx) => ( - &z_tx.shielded_spends, - &z_tx.shielded_outputs, - z_tx.value_balance, - ), - None => ( - &sapling_binding_in, - &sapling_binding_out, - Amount::from_u64(0).unwrap(), - ), + Some(z_tx) => (&z_tx.shielded_spends, &z_tx.shielded_outputs, z_tx.value_balance), + None => (&sapling_binding_in, &sapling_binding_out, Amount::from_u64(0).unwrap()), }; // sapling_digest fields @@ -328,9 +361,15 @@ where sapling_outputs_digest.copy_from_slice(hash_sapling_outputs_v5(shielded_outputs).as_bytes()); value_balance.copy_from_slice(&vb.to_i64_le_bytes()); - txdata_sighash.sapling_pre_digest.sapling_spends_digest = sapling_spends_digest; - txdata_sighash.sapling_pre_digest.sapling_outputs_digest = sapling_outputs_digest; - txdata_sighash.sapling_pre_digest.value_balance = value_balance; + txdata_sighash + .sapling_pre_digest + .sapling_spends_digest = sapling_spends_digest; + txdata_sighash + .sapling_pre_digest + .sapling_outputs_digest = sapling_outputs_digest; + txdata_sighash + .sapling_pre_digest + .value_balance = value_balance; // empty orchard digest txdata_sighash diff --git a/ledger-zcash-builder/src/errors.rs b/ledger-zcash-builder/src/errors.rs new file mode 100644 index 0000000..f67bdda --- /dev/null +++ b/ledger-zcash-builder/src/errors.rs @@ -0,0 +1,89 @@ +use bellman::VerificationError; +/******************************************************************************* +* (c) 2022-2024 Zondax AG +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ +use thiserror::Error as ThisError; + +#[derive(ThisError, Debug, PartialEq)] +pub enum Error { + #[error("Anchor mismatch (anchors for all spends must be equal)")] + AnchorMismatch, + #[error("Failed to create bindingSig")] + BindingSig, + #[error("Change is negative")] + ChangeIsNegative, + #[error("Invalid address")] + InvalidAddress, + #[error("Incorrect format of address")] + InvalidAddressFormat, + #[error("Incorrect hash of address")] + InvalidAddressHash, + #[error("Invalid amount")] + InvalidAmount, + #[error("No change address specified or discoverable")] + NoChangeAddress, + #[error("Failed to create Sapling spend proof")] + SpendProof, + #[error("Missing Sapling spend signature(s)")] + MissingSpendSig, + #[error("Failed to get Sapling spend signature")] + SpendSig, + #[error("Sapling spend signature failed to verify")] + InvalidSpendSig, + #[error("No Sapling spend signatures")] + NoSpendSig, + #[error("Failed to sign transparent inputs")] + TransparentSig, + #[error("Failed to build complete transaction")] + Finalization, + #[error("Not enough shielded outputs for transaction")] + MinShieldedOutputs, + #[error("Builder does not have any keys set")] + BuilderNoKeys, + #[error("Error writing/reading bytes to/from vector")] + #[from(std::io::Error)] + ReadWriteError, + #[error("Error: either OVK or hash_seed should be some")] + InvalidOVKHashSeed, + #[error("Error: operation not available after authorization")] + AlreadyAuthorized, + #[error("Error: operation not available without authorization")] + Unauthorized, + #[error("Error: authorization status unknown")] + UnknownAuthorization, +} + +#[derive(ThisError, Debug)] +pub enum ProverError { + #[error("Failed to generate spend proof")] + SpendProof, + #[error("Failed to generate output proof")] + OutputProof, + #[error("Failed to generate binding signature")] + BindingSig, + #[error("Invalid Diversifier")] + InvalidDiversifier, + #[error("Synthesis error {:?}", .0)] + #[from(bellman::SynthesisError)] + Synthesis(bellman::SynthesisError), + #[error("Verification error {:?}", .0)] + #[from(VerificationError)] + Verification(VerificationError), + #[error("Invalid balance")] + InvalidBalance, // Add more specific error variants as needed + #[error("Error writing/reading bytes to/from vector")] + #[from(std::io::Error)] + ReadWriteError, +} diff --git a/ledger-zcash-builder/src/lib.rs b/ledger-zcash-builder/src/lib.rs new file mode 100644 index 0000000..d35b678 --- /dev/null +++ b/ledger-zcash-builder/src/lib.rs @@ -0,0 +1,70 @@ +/******************************************************************************* +* (c) 2022-2024 Zondax AG +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ +//! This library provides tools for building and proving Zcash transactions +//! for hardware security modules (HSMs). It includes functionality for +//! handling various cryptographic operations and transaction components +//! specific to the Zcash protocol. + +#![allow( + dead_code, + unused_imports, + unused_mut, + unused_variables, + clippy::too_many_arguments, + clippy::result_unit_err, + deprecated +)] + +use blake2b_simd::Params as Blake2bParams; +use data::*; +use errors::Error; +use group::{cofactor::CofactorCurveAffine, GroupEncoding}; +use jubjub::AffinePoint; +use rand::RngCore; +use rand_core::OsRng; +use txbuilder::SaplingMetadata; +use zcash_primitives::{ + consensus::{self, Parameters, TestNetwork}, + keys::OutgoingViewingKey, + legacy::Script, + memo::MemoBytes as Memo, + merkle_tree::{IncrementalWitness, MerklePath}, + sapling::{redjubjub::Signature, Node, PaymentAddress, ProofGenerationKey, Rseed}, + transaction::{ + components::{Amount, OutPoint, TxOut}, + Transaction, + }, +}; + +mod prover; + +/// Module containing error types and handling for the library. +pub mod errors; + +/// Module containing data structures and utilities for transaction building. +pub mod data; + +/// Module providing the transaction building logic. +pub mod txbuilder; + +/// Module providing transaction proving capabilities. +pub mod txprover; + +// Re exports +/// Re-exporting the `Builder` and `hsmauth` from `txbuilder` for easier access. +pub use crate::txbuilder::{hsmauth, Builder}; +/// Re-exporting `LocalTxProver` from `txprover` for easier access. +pub use crate::txprover::LocalTxProver; diff --git a/zcash-hsmbuilder/src/prover.rs b/ledger-zcash-builder/src/prover.rs similarity index 70% rename from zcash-hsmbuilder/src/prover.rs rename to ledger-zcash-builder/src/prover.rs index 9a044e2..6074302 100644 --- a/zcash-hsmbuilder/src/prover.rs +++ b/ledger-zcash-builder/src/prover.rs @@ -1,22 +1,20 @@ +/******************************************************************************* +* (c) 2022-2024 Zondax AG +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ use std::ops::{AddAssign, Neg}; -use crate::zcash::{ - primitives::{ - constants::{ - SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR, - VALUE_COMMITMENT_VALUE_GENERATOR, - }, - merkle_tree::MerklePath, - sapling::{ - prover::TxProver, - redjubjub::{PrivateKey, PublicKey, Signature}, - Diversifier, Node, Note, PaymentAddress, ProofGenerationKey, Rseed, ValueCommitment, - }, - transaction::components::Amount, - }, - proofs::circuit::sapling::{Output, Spend}, -}; - use bellman::{ gadgets::multipack, groth16::{create_random_proof, verify_proof, Parameters, PreparedVerifyingKey, Proof}, @@ -28,6 +26,19 @@ use group::GroupEncoding; use pairing::Engine; use rand::RngCore; use rand_core::OsRng; +use zcash_primitives::{ + constants::{SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR, VALUE_COMMITMENT_VALUE_GENERATOR}, + merkle_tree::MerklePath, + sapling::{ + prover::TxProver, + redjubjub::{PrivateKey, PublicKey, Signature}, + Diversifier, Node, Note, PaymentAddress, ProofGenerationKey, Rseed, ValueCommitment, + }, + transaction::components::Amount, +}; +use zcash_proofs::circuit::sapling::{Output, Spend}; + +use crate::errors::ProverError; fn compute_value_balance_hsm(value: Amount) -> Option { // Compute the absolute value (failing if -i64::MAX is @@ -65,10 +76,7 @@ impl SaplingProvingContext { /// Construct a new context to be used with a single transaction. pub fn new() -> Self { - SaplingProvingContext { - bsk: jubjub::Fr::zero(), - cv_sum: jubjub::ExtendedPoint::identity(), - } + SaplingProvingContext { bsk: jubjub::Fr::zero(), cv_sum: jubjub::ExtendedPoint::identity() } } /// Create the value commitment, re-randomized key, and proof for a Sapling @@ -86,19 +94,18 @@ impl SaplingProvingContext { proving_key: &Parameters, verifying_key: &PreparedVerifyingKey, rcv: jubjub::Fr, - ) -> Result<(Proof, jubjub::ExtendedPoint, PublicKey), ()> { + ) -> Result<(Proof, jubjub::ExtendedPoint, PublicKey), ProverError> { + log::info!("spend_proof"); // Initialize secure RNG let mut rng = OsRng; // We create the randomness of the value commitment - /* - let mut buf = [0u8;64]; - - rng.fill_bytes(&mut buf); - - let rcv = Fr::from_bytes_wide(&buf); - - */ + // let mut buf = [0u8;64]; + // + // rng.fill_bytes(&mut buf); + // + // let rcv = Fr::from_bytes_wide(&buf); + // // Accumulate the value commitment randomness in the context { let mut tmp = rcv; @@ -109,16 +116,15 @@ impl SaplingProvingContext { } // Construct the value commitment - let value_commitment = ValueCommitment { - value, - randomness: rcv, - }; + let value_commitment = ValueCommitment { value, randomness: rcv }; // Construct the viewing key let viewing_key = proof_generation_key.to_viewing_key(); // Construct the payment address with the viewing key / diversifier - let payment_address = viewing_key.to_payment_address(diversifier).ok_or(())?; + let payment_address = viewing_key + .to_payment_address(diversifier) + .ok_or(ProverError::InvalidDiversifier)?; // This is the result of the re-randomization, we compute it for the caller let rk = PublicKey(proof_generation_key.ak.into()).randomize(ar, SPENDING_KEY_GENERATOR); @@ -126,7 +132,9 @@ impl SaplingProvingContext { // Let's compute the nullifier while we have the position let note = Note { value, - g_d: diversifier.g_d().expect("was a valid diversifier before"), + g_d: diversifier + .g_d() + .expect("was a valid diversifier before"), pk_d: *payment_address.pk_d(), rseed, }; @@ -149,8 +157,7 @@ impl SaplingProvingContext { }; // Create proof - let proof = - create_random_proof(instance, proving_key, &mut rng).expect("proving should not fail"); + let proof = create_random_proof(instance, proving_key, &mut rng).map_err(ProverError::Synthesis)?; // Try to verify the proof: // Construct public input for circuit @@ -170,7 +177,7 @@ impl SaplingProvingContext { } public_input[4] = anchor; - // Add the nullifier through multiscalar packing + // Add the nullifier through multi-scalar packing { let nullifier = multipack::bytes_to_bits_le(&nullifier.0); let nullifier = multipack::compute_multipacking(&nullifier); @@ -182,7 +189,10 @@ impl SaplingProvingContext { } // Verify the proof - verify_proof(verifying_key, &proof, &public_input[..]).map_err(|_| ())?; + verify_proof(verifying_key, &proof, &public_input[..]).map_err(|e| { + log::error!("Proof verification failed with {}", e.to_string()); + ProverError::Verification(e) + })?; // Compute value commitment let value_commitment: jubjub::ExtendedPoint = value_commitment.commitment().into(); @@ -204,21 +214,19 @@ impl SaplingProvingContext { value: u64, proving_key: &Parameters, rcv: jubjub::Fr, - ) -> (Proof, jubjub::ExtendedPoint) { + ) -> Result<(Proof, jubjub::ExtendedPoint), ProverError> { // Initialize secure RNG let mut rng = OsRng; // We construct ephemeral randomness for the value commitment. This // randomness is not given back to the caller, but the synthetic // blinding factor `bsk` is accumulated in the context. - /* - let mut buf = [0u8;64]; - - rng.fill_bytes(&mut buf); - - let rcv = Fr::from_bytes_wide(&buf); - - */ + // let mut buf = [0u8;64]; + // + // rng.fill_bytes(&mut buf); + // + // let rcv = Fr::from_bytes_wide(&buf); + // // Accumulate the value commitment randomness in the context { let mut tmp = rcv.neg(); // Outputs subtract from the total. @@ -229,10 +237,7 @@ impl SaplingProvingContext { } // Construct the value commitment for the proof instance - let value_commitment = ValueCommitment { - value, - randomness: rcv, - }; + let value_commitment = ValueCommitment { value, randomness: rcv }; // We now have a full witness for the output proof. let instance = Output { @@ -243,21 +248,26 @@ impl SaplingProvingContext { }; // Create proof - let proof = - create_random_proof(instance, proving_key, &mut rng).expect("proving should not fail"); + let proof = create_random_proof(instance, proving_key, &mut rng).map_err(ProverError::Synthesis)?; // Compute the actual value commitment let value_commitment: jubjub::ExtendedPoint = value_commitment.commitment().into(); - // Accumulate the value commitment in the context. We do this to check internal consistency. + // Accumulate the value commitment in the context. We do this to check internal + // consistency. self.cv_sum -= value_commitment; // Outputs subtract from the total. - (proof, value_commitment) + Ok((proof, value_commitment)) } - /// Create the bindingSig for a Sapling transaction. All calls to spend_proof() - /// and output_proof() must be completed before calling this function. - pub fn binding_sig(&self, value_balance: Amount, sighash: &[u8; 32]) -> Result { + /// Create the bindingSig for a Sapling transaction. All calls to + /// spend_proof() and output_proof() must be completed before calling + /// this function. + pub fn binding_sig( + &self, + value_balance: Amount, + sig_hash: &[u8; 32], + ) -> Result { // Initialize secure RNG let mut rng = OsRng; @@ -272,28 +282,24 @@ impl SaplingProvingContext { // against our derived bvk. { // Compute value balance - let value_balance = compute_value_balance_hsm(value_balance).ok_or(())?; + let value_balance = compute_value_balance_hsm(value_balance).ok_or(ProverError::InvalidBalance)?; // Subtract value_balance from cv_sum to get final bvk let final_bvk = self.cv_sum - value_balance; // The result should be the same, unless the provided valueBalance is wrong. if bvk.0 != final_bvk { - return Err(()); + return Err(ProverError::InvalidBalance); } } // Construct signature message let mut data_to_be_signed = [0u8; 64]; - data_to_be_signed[0..32].copy_from_slice(&bvk.0.to_bytes()); - data_to_be_signed[32..64].copy_from_slice(&sighash[..]); + data_to_be_signed[0 .. 32].copy_from_slice(&bvk.0.to_bytes()); + data_to_be_signed[32 .. 64].copy_from_slice(&sig_hash[..]); // Sign - Ok(bsk.sign( - &data_to_be_signed, - &mut rng, - VALUE_COMMITMENT_RANDOMNESS_GENERATOR, - )) + Ok(bsk.sign(&data_to_be_signed, &mut rng, VALUE_COMMITMENT_RANDOMNESS_GENERATOR)) } } diff --git a/zcash-hsmbuilder/src/txbuilder.rs b/ledger-zcash-builder/src/txbuilder.rs similarity index 51% rename from zcash-hsmbuilder/src/txbuilder.rs rename to ledger-zcash-builder/src/txbuilder.rs index 1984ea6..bd798fd 100644 --- a/zcash-hsmbuilder/src/txbuilder.rs +++ b/ledger-zcash-builder/src/txbuilder.rs @@ -1,3 +1,18 @@ +/******************************************************************************* +* (c) 2022-2024 Zondax AG +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ //! Structs for building transactions. use std::sync::mpsc; use std::sync::mpsc::Sender; @@ -6,36 +21,31 @@ use std::{ marker::PhantomData, }; -use crate::zcash::primitives::transaction::builder::Progress; -use crate::zcash::{ - note_encryption::NoteEncryption, - primitives::{ - consensus::{self, BranchId}, - constants::SPENDING_KEY_GENERATOR, - keys::OutgoingViewingKey, - legacy::{Script, TransparentAddress}, - memo::MemoBytes as Memo, - merkle_tree::MerklePath, - sapling::{ - note_encryption::sapling_note_encryption, - redjubjub::{PublicKey, Signature}, - util::generate_random_rseed, - Diversifier, Node, Note, PaymentAddress, ProofGenerationKey, Rseed, - }, - transaction::{ - self, - components::{ - amount::DEFAULT_FEE, sapling, transparent, Amount, OutPoint, TxIn, TxOut, - GROTH_PROOF_SIZE, - }, - sighash::{signature_hash, SignableInput, SIGHASH_ALL}, - txid::TxIdDigester, - Authorization, Transaction, TransactionData, TxDigests, TxVersion, Unauthorized, - }, - }, -}; use group::GroupEncoding; use rand::{rngs::OsRng, CryptoRng, RngCore}; +use zcash_note_encryption::NoteEncryption; +use zcash_primitives::transaction::builder::Progress; +use zcash_primitives::{ + consensus::{self, BranchId}, + constants::SPENDING_KEY_GENERATOR, + keys::OutgoingViewingKey, + legacy::{Script, TransparentAddress}, + memo::MemoBytes as Memo, + merkle_tree::MerklePath, + sapling::{ + note_encryption::sapling_note_encryption, + redjubjub::{PublicKey, Signature}, + util::generate_random_rseed, + Diversifier, Node, Note, PaymentAddress, ProofGenerationKey, Rseed, + }, + transaction::{ + self, + components::{amount::DEFAULT_FEE, sapling, transparent, Amount, OutPoint, TxIn, TxOut, GROTH_PROOF_SIZE}, + sighash::{signature_hash, SignableInput, SIGHASH_ALL}, + txid::TxIdDigester, + Authorization, Transaction, TransactionData, TxDigests, TxVersion, Unauthorized, + }, +}; use crate::{ data::{sighashdata::signature_hash_input_data, HashSeed, HsmTxData}, @@ -52,8 +62,8 @@ use hsmauth::MixedAuthorization; const DEFAULT_TX_EXPIRY_DELTA: u32 = 20; -/// If there are any shielded inputs, always have at least two shielded outputs, padding -/// with dummy outputs if necessary. See https://github.com/zcash/zcash/issues/3615 +/// If there are any shielded inputs, always have at least two shielded outputs, +/// padding with dummy outputs if necessary. See https://github.com/zcash/zcash/issues/3615 const MIN_SHIELDED_OUTPUTS: usize = 2; /// Generates a [`Transaction`] from its inputs and outputs. @@ -76,35 +86,48 @@ pub struct Builder Builder { - /// Creates a new [`Builder`] targeted for inclusion in the block with the given height, - /// using default values for general transaction fields and the default OS random. + /// Creates a new [`Builder`] targeted for inclusion in the block with the + /// given height, using default values for general transaction fields + /// and the default OS random. /// /// # Default values /// - /// The expiry height will be set to the given height plus the default transaction - /// expiry delta (20 blocks). + /// The expiry height will be set to the given height plus the default + /// transaction expiry delta (20 blocks). /// /// The fee will be set to the default fee (0.0001 ZEC). - pub fn new(params: P, height: u32) -> Self { + pub fn new( + params: P, + height: u32, + ) -> Self { Builder::new_with_rng(params, height, OsRng) } - pub fn new_with_fee(params: P, height: u32, fee: u64) -> Self { + pub fn new_with_fee( + params: P, + height: u32, + fee: u64, + ) -> Self { Builder::new_with_fee_rng(params, height, OsRng, fee) } } impl Builder { - /// Creates a new [`Builder`] targeted for inclusion in the block with the given height - /// and randomness source, using default values for general transaction fields. + /// Creates a new [`Builder`] targeted for inclusion in the block with the + /// given height and randomness source, using default values for general + /// transaction fields. /// /// # Default values /// - /// The expiry height will be set to the given height plus the default transaction - /// expiry delta (20 blocks). + /// The expiry height will be set to the given height plus the default + /// transaction expiry delta (20 blocks). /// /// The fee will be set to the default fee (0.0001 ZEC). - pub fn new_with_rng(params: P, height: u32, rng: R) -> Self { + pub fn new_with_rng( + params: P, + height: u32, + rng: R, + ) -> Self { Self { rng, params, @@ -121,7 +144,12 @@ impl Builder Self { + pub fn new_with_fee_rng( + params: P, + height: u32, + rng: R, + fee: u64, + ) -> Self { let mut this = Self::new_with_rng(params, height, rng); this.fee = Amount::from_u64(fee).unwrap(); @@ -133,13 +161,15 @@ impl Builder where P: consensus::Parameters, R: RngCore + CryptoRng, - A: transaction::Authorization, + A: Authorization, A::TransparentAuth: Clone, A::SaplingAuth: Clone, { /// Retrieve the [`TransactionData`] of the current builder state pub fn transaction_data(&self) -> Option> { - let optionals = self.cached_tx_version.zip(self.cached_branchid); + let optionals = self + .cached_tx_version + .zip(self.cached_branchid); optionals.map(|(cached_tx_version, consensus_branch_id)| { TransactionData::from_parts( cached_tx_version, @@ -161,26 +191,26 @@ where R: RngCore + CryptoRng, TA: Clone + transaction::sighash::TransparentAuthorizingContext, SA: Clone + sapling::Authorization, - A: transaction::Authorization, + A: Authorization, { + //noinspection RsNonExhaustiveMatch /// Retrieve the sighash of the current builder state - fn signature_hash(&self) -> [u8; 32] { - let data = self.transaction_data().expect("consensus branch id set"); + fn signature_hash(&self) -> Option<[u8; 32]> { + let data = self.transaction_data()?; let txid_parts = data.digest(TxIdDigester); let sighash = match data.version() { TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling => { transaction::sighash_v4::v4_signature_hash(&data, &SignableInput::Shielded) - } - TxVersion::Zip225 => transaction::sighash_v5::v5_signature_hash( - &data, - &SignableInput::Shielded, - &txid_parts, - ), + }, + TxVersion::Zip225 => { + transaction::sighash_v5::v5_signature_hash(&data, &SignableInput::Shielded, &txid_parts) + }, }; + let mut array = [0; 32]; - array.copy_from_slice(&sighash.as_ref()[..32]); - array + array.copy_from_slice(&sighash.as_ref()[.. 32]); + Some(array) } } @@ -203,39 +233,33 @@ impl, - alpha: jubjub::Fr, //get from ledger - proofkey: ProofGenerationKey, //get from ledger - rcv: jubjub::Fr, //get from ledger + alpha: jubjub::Fr, // get from ledger + proofkey: ProofGenerationKey, // get from ledger + rcv: jubjub::Fr, // get from ledger ) -> Result<(), Error> { + log::info!("Adding Sapling spend"); // Consistency check: all anchors must equal the first one let cmu = Node::new(note.cmu().into()); if let Some(anchor) = self.anchor { let path_root: bls12_381::Scalar = merkle_path.root(cmu).into(); if path_root != anchor { + log::error!("Anchor mismatch"); return Err(Error::AnchorMismatch); } } else { self.anchor = Some(merkle_path.root(cmu).into()) } - self.sapling_bundle().value_balance += - Amount::from_u64(note.value).map_err(|_| Error::InvalidAmount)?; + self.sapling_bundle().value_balance += Amount::from_u64(note.value).map_err(|_| Error::InvalidAmount)?; - let description = SpendDescriptionInfo { - diversifier, - note, - alpha, - merkle_path, - proofkey, - rcv, - }; + let description = SpendDescriptionInfo { diversifier, note, alpha, merkle_path, proofkey, rcv }; self.spends.push(description); @@ -267,19 +291,13 @@ impl> { fn empty_transparent_bundle() -> transparent::Bundle { - transparent::Bundle { - vin: vec![], - vout: vec![], - authorization: Default::default(), - } + transparent::Bundle { vin: vec![], vout: vec![], authorization: Default::default() } } /// Retrieve the transaction's transparent bundle /// /// Will initialize an empty one if not present - fn transparent_bundle( - &mut self, - ) -> &mut transparent::Bundle { + fn transparent_bundle(&mut self) -> &mut transparent::Bundle { self.transparent_bundle .get_or_insert_with(|| Self::empty_transparent_bundle()) } @@ -291,6 +309,8 @@ impl Result<(), Error> { + log::info!("add_transparent_input"); + if coin.value.is_negative() { return Err(Error::InvalidAmount); } @@ -300,25 +320,23 @@ impl return Err(Error::InvalidAddressFormat), } let bundle = self.transparent_bundle(); - //TxIn is made like this to trick the compiler + // TxIn is made like this to trick the compiler // in assigning the correct Authorization generic // parameter, since `vin` uses the primitives' Unauthorized // whilst we use the one in hsmauth let vin = TxIn::new(utxo); - bundle.vin.push(TxIn { - script_sig: vin.script_sig, - sequence: vin.sequence, - prevout: vin.prevout, - }); + bundle + .vin + .push(TxIn { script_sig: vin.script_sig, sequence: vin.sequence, prevout: vin.prevout }); bundle .authorization .inputs @@ -328,39 +346,42 @@ impl Result<(), Error> { + pub fn add_transparent_output( + &mut self, + to: Script, + value: Amount, + ) -> Result<(), Error> { + log::info!("add_transparent_output"); if value.is_negative() { return Err(Error::InvalidAmount); } - self.transparent_bundle().vout.push(TxOut { - value, - script_pubkey: to, - }); + self.transparent_bundle() + .vout + .push(TxOut { value, script_pubkey: to }); Ok(()) } } impl - Builder< - P, - R, - MixedAuthorization, - > + Builder> { - /// Prepares a transaction to be transmitted to the HSM from the configured spends and outputs. + /// Prepares a transaction to be transmitted to the HSM from the configured + /// spends and outputs. /// - /// Upon success, returns the structure that can be serialized in in the format understood by the HSM - /// and subsequently transmitted via the appropriate method. + /// Upon success, returns the structure that can be serialized in the + /// format understood by the HSM and subsequently transmitted via the + /// appropriate method. /// - /// After having retrieved the signatures from the HSM and having applied them with the appropriate - /// methods of the builder, it's possible to retrieve the final signature using [`Builder::finalize`] + /// After having retrieved the signatures from the HSM and having applied + /// them with the appropriate methods of the builder, it's possible to + /// retrieve the final signature using [`Builder::finalize`] /// - /// `consensus_branch_id` must be valid for the block height that this transaction is - /// targeting. An invalid `consensus_branch_id` will *not* result in an error from - /// this function, and instead will generate a transaction that will be rejected by - /// the network. + /// `consensus_branch_id` must be valid for the block height that this + /// transaction is targeting. An invalid `consensus_branch_id` will + /// *not* result in an error from this function, and instead will + /// generate a transaction that will be rejected by the network. pub fn build( &mut self, consensus_branch_id: consensus::BranchId, @@ -372,85 +393,102 @@ impl pub fn build_with_progress_notifier( &mut self, - consensus_branch_id: consensus::BranchId, + consensus_branch_id: BranchId, tx_version: Option, prover: &impl HsmTxProver, - progress_notifier: Option>, + progress_notifier: Option>, ) -> Result { - self.cached_branchid.replace(consensus_branch_id); + log::info!("build_with_progress_notifier"); + self.cached_branchid + .replace(consensus_branch_id); let tx_version = match tx_version { Some(v) => v, None => TxVersion::suggested_for_branch(consensus_branch_id), }; - self.cached_tx_version.replace(tx_version); - // + self.cached_tx_version + .replace(tx_version); + // Consistency checks - // // Valid change - let change = self + let sapling_value = self .sapling_bundle .as_ref() .map(|bundle| bundle.value_balance) - .unwrap_or(Amount::zero()) - - self.fee - + self - .transparent_bundle - .as_ref() - .map(|bundle| { - bundle - .authorization - .inputs - .iter() - .map(|input| input.coin.value) - //poor man's .sum - .fold(Amount::zero(), |x, acc| (x + acc).unwrap()) - }) - .unwrap_or(Amount::zero()) - - self - .transparent_bundle - .as_ref() - .map(|bundle| { - bundle - .vout - .iter() - .map(|output| output.value) - .fold(Amount::zero(), |x, acc| (x + acc).unwrap()) - }) - .unwrap_or(Amount::zero()); + .unwrap_or(Amount::zero()); + let input_value = self + .transparent_bundle + .as_ref() + .map(|bundle| { + bundle + .authorization + .inputs + .iter() + .map(|input| input.coin.value) + // poor man's .sum + .fold(Amount::zero(), |x, acc| (x + acc).unwrap()) + }) + .unwrap_or(Amount::zero()); + let output_value = self + .transparent_bundle + .as_ref() + .map(|bundle| { + bundle + .vout + .iter() + .map(|output| output.value) + .fold(Amount::zero(), |x, acc| (x + acc).unwrap()) + }) + .unwrap_or(Amount::zero()); + + log::debug!("Sapling value: {:?}", sapling_value); + log::debug!("Input value: {:?}", input_value); + log::debug!("Output value: {:?}", output_value); + log::debug!("Fee: {:?}", self.fee); + + let change = sapling_value + input_value + output_value - self.fee; let change = change.unwrap(); if change.is_negative() { + log::error!("Change is negative {:?}", change); return Err(Error::ChangeIsNegative); } - // // Change output - // - if change.is_positive() { // Send change to the specified change address. If no change address // was set, then error as Ledger otherwise needs to give keys and randomness. + log::error!("No change address"); return Err(Error::NoChangeAddress); } - // // Record initial positions of spends and outputs // - let spends: Vec<_> = self.spends.clone().into_iter().enumerate().collect(); - let mut outputs: Vec<_> = self.outputs.clone().into_iter().enumerate().collect(); + let spends: Vec<_> = self + .spends + .clone() + .into_iter() + .enumerate() + .collect(); + let mut outputs: Vec<_> = self + .outputs + .clone() + .into_iter() + .enumerate() + .collect(); - // // Sapling spends and outputs // - //let mut ctx: ::SaplingProvingContext = SaplingProvingContext::new(); + // let mut ctx: ::SaplingProvingContext = + // SaplingProvingContext::new(); let mut ctx = prover.new_sapling_proving_context(); // Pad Sapling outputs if !spends.is_empty() && outputs.len() < MIN_SHIELDED_OUTPUTS { - return Err(Error::MinShieldedOuputs); + log::error!("Not enough shielded outputs"); + return Err(Error::MinShieldedOutputs); } // Record if we'll need a binding signature @@ -461,15 +499,16 @@ impl // Create Sapling SpendDescriptions if !spends.is_empty() { - let anchor = self.anchor.expect("anchor was set if spends were added"); + let anchor = self + .anchor + .expect("anchor was set if spends were added"); for (_, spend) in spends.into_iter() { let proof_generation_key = spend.proofkey.clone(); - let nullifier = spend.note.nf( - &proof_generation_key.to_viewing_key(), - spend.merkle_path.position, - ); + let nullifier = spend + .note + .nf(&proof_generation_key.to_viewing_key(), spend.merkle_path.position); let (zkproof, cv, rk) = prover .spend_proof( @@ -483,7 +522,7 @@ impl spend.merkle_path.clone(), spend.rcv, ) - .map_err(|()| Error::SpendProof)?; + .map_err(|_| Error::SpendProof)?; // Update progress and send a notification on the channel progress += 1; @@ -518,24 +557,22 @@ impl let _ = sender.send(Progress::new(progress, None)); } - self.sapling_bundle().shielded_outputs.push(output_desc); + self.sapling_bundle() + .shielded_outputs + .push(output_desc); } - // // Signatures -- everything but the signatures must already have been added. - // - // Add a binding signature if needed if binding_sig_needed { - let signature_hash = self.signature_hash(); + let signature_hash = self + .signature_hash() + .ok_or(Error::BindingSig)?; + self.binding_sig = Some( prover - .binding_sig( - &mut ctx, - self.sapling_bundle().value_balance, - &signature_hash, - ) - .map_err(|()| Error::BindingSig)?, + .binding_sig(&mut ctx, self.sapling_bundle().value_balance, &signature_hash) + .map_err(|_| Error::BindingSig)?, ); } else { self.binding_sig = None; @@ -588,7 +625,7 @@ where R: RngCore + CryptoRng, SA: sapling::Authorization + Clone, { - ///convenience wrapper to switch transparent bundle associated parameter + /// convenience wrapper to switch transparent bundle associated parameter fn with_transparent_bundle( self, bundle: Option>, @@ -624,33 +661,30 @@ where } } - /// Attempt to apply the signatures for the transparent components of the transaction + //noinspection RsNonExhaustiveMatch + /// Attempt to apply the signatures for the transparent components of the + /// transaction pub fn add_signatures_transparent( self, - signatures: Vec, //get from ledger + signatures: Vec, // get from ledger ) -> Result>, Error> { - let tx_data = self.transaction_data().expect("consensus branch id set"); - let transparent::Bundle { - vin, - vout, - authorization, - } = match (self.transparent_bundle.as_ref(), signatures.len()) { - (None, 0) => return Ok(self.with_transparent_bundle(None)), - (None, _) => return Err(Error::TranspararentSig), - //this check takes into account also when we have no inputs - // since we don't have inputs we also get 0 signatures - // and below the other if will take care of skipping the - // signature verifications etc - (Some(bundle), n) if n != bundle.authorization.inputs.len() => { - log::error!( - "Transparent signatures necessary #{}, got #{}", - bundle.authorization.inputs.len(), - n - ); - return Err(Error::TranspararentSig); - } - (Some(bundle), _) => bundle, - }; + let tx_data = self + .transaction_data() + .expect("consensus branch id set"); + let transparent::Bundle { vin, vout, authorization } = + match (self.transparent_bundle.as_ref(), signatures.len()) { + (None, 0) => return Ok(self.with_transparent_bundle(None)), + (None, _) => return Err(Error::TransparentSig), + // this check takes into account also when we have no inputs + // since we don't have inputs we also get 0 signatures + // and below the other if will take care of skipping the + // signature verifications etc + (Some(bundle), n) if n != bundle.authorization.inputs.len() => { + log::error!("Transparent signatures necessary #{}, got #{}", bundle.authorization.inputs.len(), n); + return Err(Error::TransparentSig); + }, + (Some(bundle), _) => bundle, + }; let mut bundle: transparent::Bundle = transparent::Bundle { vin: Vec::with_capacity(vin.len()), @@ -666,22 +700,19 @@ where .zip(vin.iter()) .enumerate() { - //1) generate the signature message + // 1) generate the signature message // to verify the signature against let sighash = match tx_data.version() { TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling => { - transaction::sighash_v4::v4_signature_hash( - &tx_data, - &SignableInput::Transparent { - hash_type: SIGHASH_ALL, - index: i, - value: info.coin.value, - script_pubkey: &info.coin.script_pubkey, - // for p2pkh, always the same as script_pubkey - script_code: &info.coin.script_pubkey, - }, - ) - } + transaction::sighash_v4::v4_signature_hash(&tx_data, &SignableInput::Transparent { + hash_type: SIGHASH_ALL, + index: i, + value: info.coin.value, + script_pubkey: &info.coin.script_pubkey, + // for p2pkh, always the same as script_pubkey + script_code: &info.coin.script_pubkey, + }) + }, TxVersion::Zip225 => { let txid_parts = tx_data.digest(TxIdDigester); transaction::sighash_v5::v5_signature_hash( @@ -696,15 +727,19 @@ where }, &txid_parts, ) - } + }, }; let msg = secp256k1::Message::from_slice(sighash.as_ref()).expect("32 bytes"); - //2) verify signature - if authorization.secp.verify(&msg, &sig, &info.pubkey).is_err() { + // 2) verify signature + if authorization + .secp + .verify_ecdsa(&msg, &sig, &info.pubkey) + .is_err() + { log::error!("Error verifying transparent sig #{}", i); - return Err(Error::TranspararentSig); + return Err(Error::TransparentSig); } // Signature has to have "SIGHASH_ALL" appended to it @@ -712,14 +747,11 @@ where sig_bytes.extend(&[SIGHASH_ALL]); // save P2PKH scriptSig - let script_sig = - Script::default() << &sig_bytes[..] << &info.pubkey.serialize()[..]; - - bundle.vin.push(TxIn { - prevout: vin.prevout.clone(), - script_sig, - sequence: vin.sequence, - }) + let script_sig = Script::default() << &sig_bytes[..] << &info.pubkey.serialize()[..]; + + bundle + .vin + .push(TxIn { prevout: vin.prevout.clone(), script_sig, sequence: vin.sequence }) } } @@ -733,7 +765,7 @@ where R: RngCore + CryptoRng, TA: transparent::Authorization + transaction::sighash::TransparentAuthorizingContext + Clone, { - ///convenience wrapper to switch transparent bundle associated parameter + /// convenience wrapper to switch transparent bundle associated parameter fn with_sapling_bundle( self, bundle: Option>, @@ -768,30 +800,23 @@ where cached_tx_version, } } - /// Attempt to apply the signatures for the shielded components of the transaction + /// Attempt to apply the signatures for the shielded components of the + /// transaction pub fn add_signatures_spend( self, - signatures: Vec, //get from ledger + signatures: Vec, // get from ledger ) -> Result>, Error> { - let sapling::Bundle { - shielded_spends, - shielded_outputs, - value_balance, - .. - } = match (self.sapling_bundle.as_ref(), signatures.len()) { - (None, 0) => return Ok(self.with_sapling_bundle(None)), - (None, _) => return Err(Error::NoSpendSig), - //if we have no inputs and no signatures were passed this succeeds - (Some(_), n) if n != self.spends.len() => { - log::error!( - "Sapling signatures necessary #{}, got #{}", - self.spends.len(), - n - ); - return Err(Error::MissingSpendSig); - } - (Some(bundle), _) => bundle, - }; + let sapling::Bundle { shielded_spends, shielded_outputs, value_balance, .. } = + match (self.sapling_bundle.as_ref(), signatures.len()) { + (None, 0) => return Ok(self.with_sapling_bundle(None)), + (None, _) => return Err(Error::NoSpendSig), + // if we have no inputs and no signatures were passed this succeeds + (Some(_), n) if n != self.spends.len() => { + log::error!("Sapling signatures necessary #{}, got #{}", self.spends.len(), n); + return Err(Error::MissingSpendSig); + }, + (Some(bundle), _) => bundle, + }; let Self { spends, .. } = &self; @@ -800,7 +825,7 @@ where shielded_outputs: shielded_outputs.clone(), value_balance: *value_balance, authorization: sapling::Authorized { - //if we reach here without binding sig it's an error + // if we reach here without binding sig it's an error // since if we had spends or outputs (so no binding sig needed) // we would have returned already from the method binding_sig: self.binding_sig.ok_or_else(|| { @@ -812,10 +837,12 @@ where let mut all_signatures_valid: bool = true; - //if we have no spends we can just skip + // if we have no spends we can just skip // applying the signatures and verifying if !spends.is_empty() { - let sighash = self.signature_hash(); + let sighash = self + .signature_hash() + .ok_or(Error::InvalidSpendSig)?; let p_g = SPENDING_KEY_GENERATOR; for (i, ((spend_auth_sig, spendinfo), spend)) in signatures @@ -829,8 +856,8 @@ where let message = { let mut array = [0; 64]; - array[..32].copy_from_slice(&rk.0.to_bytes()); - array[32..].copy_from_slice(&sighash[..]); + array[.. 32].copy_from_slice(&rk.0.to_bytes()); + array[32 ..].copy_from_slice(&sighash[..]); array }; @@ -846,17 +873,19 @@ where rk, zkproof: spend.zkproof, }; - sapling_bundle.shielded_spends.push(spend); - } - /* - let mut spends: Vec<_> = self.spends.clone().into_iter().enumerate().collect(); - let mut all_signatures_valid: bool = true; - for (i, (_, spend)) in spends.into_iter().enumerate() { - let rk = PublicKey(spend.proofkey.ak.into()).randomize(spend.alpha,SPENDING_KEY_GENERATOR); - all_signatures_valid &= rk.verify(&self.sighash, &sign[i], p_g); - self.mtx.shielded_spends[i].spend_auth_sig = Some(sign[i]); + sapling_bundle + .shielded_spends + .push(spend); } - */ + // let mut spends: Vec<_> = + // self.spends.clone().into_iter().enumerate().collect(); + // let mut all_signatures_valid: bool = true; + // for (i, (_, spend)) in spends.into_iter().enumerate() { + // let rk = PublicKey(spend.proofkey.ak.into()).randomize(spend. + // alpha,SPENDING_KEY_GENERATOR); all_signatures_valid + // &= rk.verify(&self.sighash, &sign[i], p_g); + // self.mtx.shielded_spends[i].spend_auth_sig = Some(sign[i]); + // } } match all_signatures_valid { @@ -866,7 +895,7 @@ where this.outputs = vec![]; Ok(this) - } + }, false => Err(Error::InvalidSpendSig), } } @@ -875,9 +904,12 @@ where impl Builder> { - /// Retrieve [`TransactionData`] parametrized with [`transaction::Authorized`] + /// Retrieve [`TransactionData`] parametrized with + /// [`transaction::Authorized`] fn transaction_data_authorized(&self) -> Option> { - let optionals = self.cached_tx_version.zip(self.cached_branchid); + let optionals = self + .cached_tx_version + .zip(self.cached_branchid); optionals.map(|(cached_tx_version, consensus_branch_id)| { TransactionData::from_parts( cached_tx_version, @@ -892,306 +924,311 @@ impl }) } - /// Finalize the transaction, after having obtained all the signatures from the the HSM. + /// Finalize the transaction, after having obtained all the signatures from + /// the the HSM. /// - /// Upon success, returns a tuple containing the final transaction, and the [`TransactionMetadata`] - /// generated during the build process. + /// Upon success, returns a tuple containing the final transaction, and the + /// [`TransactionMetadata`] generated during the build process. pub fn finalize(mut self) -> Result<(Transaction, SaplingMetadata), Error> { let tx_data = self .transaction_data_authorized() .ok_or(Error::Finalization)?; - let tx = tx_data.freeze().map_err(|_| Error::Finalization)?; + let tx = tx_data + .freeze() + .map_err(|_| Error::Finalization)?; let mut tx_meta = SaplingMetadata::new(); - tx_meta.spend_indices = (0..self.spends.len()).collect(); - tx_meta.output_indices = (0..self.outputs.len()).collect(); + tx_meta.spend_indices = (0 .. self.spends.len()).collect(); + tx_meta.output_indices = (0 .. self.outputs.len()).collect(); Ok((tx, tx_meta)) } - /// Same as finalize, except serialized to the format understood by the JavaScript users + /// Same as finalize, except serialized to the format understood by the + /// JavaScript users pub fn finalize_js(&mut self) -> Result, Error> { let txdata = self .transaction_data_authorized() .ok_or(Error::Finalization)?; - let tx = txdata.freeze().map_err(|_| Error::Finalization)?; + let tx = txdata + .freeze() + .map_err(|_| Error::Finalization)?; let mut v = Vec::new(); - tx.write(&mut v)?; + tx.write(&mut v) + .map_err(|_| Error::ReadWriteError)?; Ok(v) } } -/* -#[cfg(test)] -mod tests { - use ff::{Field, PrimeField}; - use rand_core::OsRng; - use std::marker::PhantomData; - - use super::{Builder, Error}; - use crate::zcash::primitives::{ - *, - consensus::*, - consensus::TestNetwork, - legacy::TransparentAddress, - merkle_tree::{CommitmentTree, IncrementalWitness}, - primitives::Rseed, - prover::*, - sapling::Node, - transaction::components::Amount, - zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}, - transaction::{ - components::{amount::DEFAULT_FEE, OutputDescription, SpendDescription, TxOut}, - signature_hash_data, Transaction, TransactionData, SIGHASH_ALL, - }, - }; - use crate::zcash::primitives::primitives::ProofGenerationKey; - use jubjub::{SubgroupPoint, ExtendedPoint}; - use crate::zcash::primitives::keys::OutgoingViewingKey; - use crate::zcash::primitives::redjubjub::PublicKey; - - #[test] - fn fails_on_negative_output() { - let extsk = ExtendedSpendingKey::master(&[]); - let extfvk = ExtendedFullViewingKey::from(&extsk); - let ovk = extfvk.fvk.ovk; - let to = extfvk.default_address().unwrap().1; - let mut builder = Builder::::new(0); - assert_eq!( - builder.add_sapling_output(Some(ovk), to, Amount::from_i64(-1).unwrap(), None), - Err(Error::InvalidAmount) - ); - } - - #[test] - fn binding_sig_absent_if_no_shielded_spend_or_output() { - use crate::consensus::{NetworkUpgrade, Parameters}; - use crate::transaction::{ - builder::{self, TransparentInputs}, - TransactionData, - }; - - let sapling_activation_height = - TestNetwork::activation_height(NetworkUpgrade::Sapling).unwrap(); - - // Create a builder with 0 fee, so we can construct t outputs - let mut builder = Builder:: { - rng: OsRng, - height: sapling_activation_height, - mtx: TransactionData::new(), - fee: Amount::zero(), - anchor: None, - spends: vec![], - outputs: vec![], - transparent_inputs: TransparentInputs::default(), - change_address: None, - phantom: PhantomData, - sighash: [0u8;32] - }; - - // Create a tx with only t output. No binding_sig should be present - builder - .add_transparent_output(&TransparentAddress::PublicKey([0; 20]), Amount::zero()) - .unwrap(); -/* there is not public MockTxProver - let (tx, _) = builder - .build(consensus::BranchId::Sapling, &MockTxProver) - .unwrap(); - // No binding signature, because only t input and outputs - assert!(tx.binding_sig.is_none()); - - */ - } - - #[test] - fn binding_sig_present_if_shielded_spend() { - let extsk = ExtendedSpendingKey::master(&[]); - let extfvk = ExtendedFullViewingKey::from(&extsk); - let to = extfvk.default_address().unwrap().1; - - let mut rng = OsRng; - - let note1 = to - .create_note(50000, Rseed::BeforeZip212(jubjub::Fr::one())) //)) - .unwrap(); - let cmu1 = Node::new(note1.cmu().to_repr()); - let mut tree = CommitmentTree::new(); - tree.append(cmu1).unwrap(); - let witness1 = IncrementalWitness::from_tree(&tree); - - let mut builder = Builder::::new(0); - - // Create a tx with a sapling spend. binding_sig should be present - builder - .add_sapling_spend( - *to.diversifier(), - note1.clone(), - witness1.path().unwrap(), - jubjub::Fr::one(), - ProofGenerationKey{ak:SubgroupPoint::default(),nsk:jubjub::Fr::one()}, - PublicKey(ExtendedPoint::default()), - Some(OutgoingViewingKey([0xaa;32])) - ) - .unwrap(); - - builder - .add_transparent_output(&TransparentAddress::PublicKey([0; 20]), Amount::zero()) - .unwrap(); - - // Expect a binding signature error, because our inputs aren't valid, but this shows - // that a binding signature was attempted - assert_eq!( - builder.build(consensus::BranchId::Sapling, &MockTxProver), - Err(Error::BindingSig) - ); - } - - #[test] - fn fails_on_negative_transparent_output() { - let mut builder = Builder::::new(0); - assert_eq!( - builder.add_transparent_output( - &TransparentAddress::PublicKey([0; 20]), - Amount::from_i64(-1).unwrap(), - ), - Err(Error::InvalidAmount) - ); - } - - #[test] - fn fails_on_negative_change() { - let mut rng = OsRng; - - // Just use the master key as the ExtendedSpendingKey for this test - let extsk = ExtendedSpendingKey::master(&[]); - - // Fails with no inputs or outputs - // 0.0001 t-ZEC fee - { - let builder = Builder::::new(0); - assert_eq!( - builder.build(consensus::BranchId::Sapling, &MockTxProver), - Err(Error::ChangeIsNegative(Amount::from_i64(-10000).unwrap())) - ); - } - - let extfvk = ExtendedFullViewingKey::from(&extsk); - let ovk = Some(extfvk.fvk.ovk); - let to = extfvk.default_address().unwrap().1; - - // Fail if there is only a Sapling output - // 0.0005 z-ZEC out, 0.0001 t-ZEC fee - { - let mut builder = Builder::::new(0); - builder - .add_sapling_output( - ovk.clone(), - to.clone(), - Amount::from_u64(50000).unwrap(), - None, - ) - .unwrap(); - assert_eq!( - builder.build(consensus::BranchId::Sapling, &MockTxProver), - Err(Error::ChangeIsNegative(Amount::from_i64(-60000).unwrap())) - ); - } - - // Fail if there is only a transparent output - // 0.0005 t-ZEC out, 0.0001 t-ZEC fee - { - let mut builder = Builder::::new(0); - builder - .add_transparent_output( - &TransparentAddress::PublicKey([0; 20]), - Amount::from_u64(50000).unwrap(), - ) - .unwrap(); - assert_eq!( - builder.build(consensus::BranchId::Sapling, &MockTxProver), - Err(Error::ChangeIsNegative(Amount::from_i64(-60000).unwrap())) - ); - } - - let note1 = to - .create_note(59999, Rseed::BeforeZip212(jubjub::Fr::random(&mut rng))) - .unwrap(); - let cmu1 = Node::new(note1.cmu().to_repr()); - let mut tree = CommitmentTree::new(); - tree.append(cmu1).unwrap(); - let mut witness1 = IncrementalWitness::from_tree(&tree); - - // Fail if there is insufficient input - // 0.0003 z-ZEC out, 0.0002 t-ZEC out, 0.0001 t-ZEC fee, 0.00059999 z-ZEC in - { - let mut builder = Builder::::new(0); - builder - .add_sapling_spend( - extsk.clone(), - *to.diversifier(), - note1.clone(), - witness1.path().unwrap(), - ) - .unwrap(); - builder - .add_sapling_output( - ovk.clone(), - to.clone(), - Amount::from_u64(30000).unwrap(), - None, - ) - .unwrap(); - builder - .add_transparent_output( - &TransparentAddress::PublicKey([0; 20]), - Amount::from_u64(20000).unwrap(), - ) - .unwrap(); - assert_eq!( - builder.build(consensus::BranchId::Sapling, &MockTxProver), - Err(Error::ChangeIsNegative(Amount::from_i64(-1).unwrap())) - ); - } - - let note2 = to - .create_note(1, Rseed::BeforeZip212(jubjub::Fr::random(&mut rng))) - .unwrap(); - let cmu2 = Node::new(note2.cmu().to_repr()); - tree.append(cmu2).unwrap(); - witness1.append(cmu2).unwrap(); - let witness2 = IncrementalWitness::from_tree(&tree); - - // Succeeds if there is sufficient input - // 0.0003 z-ZEC out, 0.0002 t-ZEC out, 0.0001 t-ZEC fee, 0.0006 z-ZEC in - // - // (Still fails because we are using a MockTxProver which doesn't correctly - // compute bindingSig.) - { - let mut builder = Builder::::new(0); - builder - .add_sapling_spend( - extsk.clone(), - *to.diversifier(), - note1, - witness1.path().unwrap(), - ) - .unwrap(); - builder - .add_sapling_spend(extsk, *to.diversifier(), note2, witness2.path().unwrap()) - .unwrap(); - builder - .add_sapling_output(ovk, to, Amount::from_u64(30000).unwrap(), None) - .unwrap(); - builder - .add_transparent_output( - &TransparentAddress::PublicKey([0; 20]), - Amount::from_u64(20000).unwrap(), - ) - .unwrap(); - assert_eq!( - builder.build(consensus::BranchId::Sapling, &MockTxProver), - Err(Error::BindingSig) - ) - } - } -} -*/ +// #[cfg(test)] +// mod tests { +// use ff::{Field, PrimeField}; +// use rand_core::OsRng; +// use std::marker::PhantomData; +// +// use super::{Builder, Error}; +// use crate::zcash::primitives::{ +// , +// consensus::*, +// consensus::TestNetwork, +// legacy::TransparentAddress, +// merkle_tree::{CommitmentTree, IncrementalWitness}, +// primitives::Rseed, +// prover::*, +// sapling::Node, +// transaction::components::Amount, +// zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}, +// transaction::{ +// components::{amount::DEFAULT_FEE, OutputDescription, SpendDescription, +// TxOut}, signature_hash_data, Transaction, TransactionData, SIGHASH_ALL, +// }, +// }; +// use crate::zcash::primitives::primitives::ProofGenerationKey; +// use jubjub::{SubgroupPoint, ExtendedPoint}; +// use crate::zcash::primitives::keys::OutgoingViewingKey; +// use crate::zcash::primitives::redjubjub::PublicKey; +// +// #[test] +// fn fails_on_negative_output() { +// let extsk = ExtendedSpendingKey::master(&[]); +// let extfvk = ExtendedFullViewingKey::from(&extsk); +// let ovk = extfvk.fvk.ovk; +// let to = extfvk.default_address().unwrap().1; +// let mut builder = Builder::::new(0); +// assert_eq!( +// builder.add_sapling_output(Some(ovk), to, Amount::from_i64(-1).unwrap(), +// None), Err(Error::InvalidAmount) +// ); +// } +// +// #[test] +// fn binding_sig_absent_if_no_shielded_spend_or_output() { +// use crate::consensus::{NetworkUpgrade, Parameters}; +// use crate::transaction::{ +// builder::{self, TransparentInputs}, +// TransactionData, +// }; +// +// let sapling_activation_height = +// TestNetwork::activation_height(NetworkUpgrade::Sapling).unwrap(); +// +// Create a builder with 0 fee, so we can construct t outputs +// let mut builder = Builder:: { +// rng: OsRng, +// height: sapling_activation_height, +// mtx: TransactionData::new(), +// fee: Amount::zero(), +// anchor: None, +// spends: vec![], +// outputs: vec![], +// transparent_inputs: TransparentInputs::default(), +// change_address: None, +// phantom: PhantomData, +// sighash: [0u8;32] +// }; +// +// Create a tx with only t output. No binding_sig should be present +// builder +// .add_transparent_output(&TransparentAddress::PublicKey([0; 20]), +// Amount::zero()) .unwrap(); +// there is not public MockTxProver +// let (tx, _) = builder +// .build(consensus::BranchId::Sapling, &MockTxProver) +// .unwrap(); +// No binding signature, because only t input and outputs +// assert!(tx.binding_sig.is_none()); +// +// / +// } +// +// #[test] +// fn binding_sig_present_if_shielded_spend() { +// let extsk = ExtendedSpendingKey::master(&[]); +// let extfvk = ExtendedFullViewingKey::from(&extsk); +// let to = extfvk.default_address().unwrap().1; +// +// let mut rng = OsRng; +// +// let note1 = to +// .create_note(50000, Rseed::BeforeZip212(jubjub::Fr::one())) //)) +// .unwrap(); +// let cmu1 = Node::new(note1.cmu().to_repr()); +// let mut tree = CommitmentTree::new(); +// tree.append(cmu1).unwrap(); +// let witness1 = IncrementalWitness::from_tree(&tree); +// +// let mut builder = Builder::::new(0); +// +// Create a tx with a sapling spend. binding_sig should be present +// builder +// .add_sapling_spend( +// to.diversifier(), +// note1.clone(), +// witness1.path().unwrap(), +// jubjub::Fr::one(), +// ProofGenerationKey{ak:SubgroupPoint::default(),nsk:jubjub::Fr::one()}, +// PublicKey(ExtendedPoint::default()), +// Some(OutgoingViewingKey([0xaa;32])) +// ) +// .unwrap(); +// +// builder +// .add_transparent_output(&TransparentAddress::PublicKey([0; 20]), +// Amount::zero()) .unwrap(); +// +// Expect a binding signature error, because our inputs aren't valid, but this +// shows that a binding signature was attempted +// assert_eq!( +// builder.build(consensus::BranchId::Sapling, &MockTxProver), +// Err(Error::BindingSig) +// ); +// } +// +// #[test] +// fn fails_on_negative_transparent_output() { +// let mut builder = Builder::::new(0); +// assert_eq!( +// builder.add_transparent_output( +// &TransparentAddress::PublicKey([0; 20]), +// Amount::from_i64(-1).unwrap(), +// ), +// Err(Error::InvalidAmount) +// ); +// } +// +// #[test] +// fn fails_on_negative_change() { +// let mut rng = OsRng; +// +// Just use the master key as the ExtendedSpendingKey for this test +// let extsk = ExtendedSpendingKey::master(&[]); +// +// Fails with no inputs or outputs +// 0.0001 t-ZEC fee +// { +// let builder = Builder::::new(0); +// assert_eq!( +// builder.build(consensus::BranchId::Sapling, &MockTxProver), +// Err(Error::ChangeIsNegative(Amount::from_i64(-10000).unwrap())) +// ); +// } +// +// let extfvk = ExtendedFullViewingKey::from(&extsk); +// let ovk = Some(extfvk.fvk.ovk); +// let to = extfvk.default_address().unwrap().1; +// +// Fail if there is only a Sapling output +// 0.0005 z-ZEC out, 0.0001 t-ZEC fee +// { +// let mut builder = Builder::::new(0); +// builder +// .add_sapling_output( +// ovk.clone(), +// to.clone(), +// Amount::from_u64(50000).unwrap(), +// None, +// ) +// .unwrap(); +// assert_eq!( +// builder.build(consensus::BranchId::Sapling, &MockTxProver), +// Err(Error::ChangeIsNegative(Amount::from_i64(-60000).unwrap())) +// ); +// } +// +// Fail if there is only a transparent output +// 0.0005 t-ZEC out, 0.0001 t-ZEC fee +// { +// let mut builder = Builder::::new(0); +// builder +// .add_transparent_output( +// &TransparentAddress::PublicKey([0; 20]), +// Amount::from_u64(50000).unwrap(), +// ) +// .unwrap(); +// assert_eq!( +// builder.build(consensus::BranchId::Sapling, &MockTxProver), +// Err(Error::ChangeIsNegative(Amount::from_i64(-60000).unwrap())) +// ); +// } +// +// let note1 = to +// .create_note(59999, Rseed::BeforeZip212(jubjub::Fr::random(&mut rng))) +// .unwrap(); +// let cmu1 = Node::new(note1.cmu().to_repr()); +// let mut tree = CommitmentTree::new(); +// tree.append(cmu1).unwrap(); +// let mut witness1 = IncrementalWitness::from_tree(&tree); +// +// Fail if there is insufficient input +// 0.0003 z-ZEC out, 0.0002 t-ZEC out, 0.0001 t-ZEC fee, 0.00059999 z-ZEC in +// { +// let mut builder = Builder::::new(0); +// builder +// .add_sapling_spend( +// extsk.clone(), +// to.diversifier(), +// note1.clone(), +// witness1.path().unwrap(), +// ) +// .unwrap(); +// builder +// .add_sapling_output( +// ovk.clone(), +// to.clone(), +// Amount::from_u64(30000).unwrap(), +// None, +// ) +// .unwrap(); +// builder +// .add_transparent_output( +// &TransparentAddress::PublicKey([0; 20]), +// Amount::from_u64(20000).unwrap(), +// ) +// .unwrap(); +// assert_eq!( +// builder.build(consensus::BranchId::Sapling, &MockTxProver), +// Err(Error::ChangeIsNegative(Amount::from_i64(-1).unwrap())) +// ); +// } +// +// let note2 = to +// .create_note(1, Rseed::BeforeZip212(jubjub::Fr::random(&mut rng))) +// .unwrap(); +// let cmu2 = Node::new(note2.cmu().to_repr()); +// tree.append(cmu2).unwrap(); +// witness1.append(cmu2).unwrap(); +// let witness2 = IncrementalWitness::from_tree(&tree); +// +// Succeeds if there is sufficient input +// 0.0003 z-ZEC out, 0.0002 t-ZEC out, 0.0001 t-ZEC fee, 0.0006 z-ZEC in +// +// (Still fails because we are using a MockTxProver which doesn't correctly +// compute bindingSig.) +// { +// let mut builder = Builder::::new(0); +// builder +// .add_sapling_spend( +// extsk.clone(), +// to.diversifier(), +// note1, +// witness1.path().unwrap(), +// ) +// .unwrap(); +// builder +// .add_sapling_spend(extsk, *to.diversifier(), note2, witness2.path().unwrap()) +// .unwrap(); +// builder +// .add_sapling_output(ovk, to, Amount::from_u64(30000).unwrap(), None) +// .unwrap(); +// builder +// .add_transparent_output( +// &TransparentAddress::PublicKey([0; 20]), +// Amount::from_u64(20000).unwrap(), +// ) +// .unwrap(); +// assert_eq!( +// builder.build(consensus::BranchId::Sapling, &MockTxProver), +// Err(Error::BindingSig) +// ) +// } +// } +// } diff --git a/zcash-hsmbuilder/src/txbuilder/builder_data.rs b/ledger-zcash-builder/src/txbuilder/builder_data.rs similarity index 64% rename from zcash-hsmbuilder/src/txbuilder/builder_data.rs rename to ledger-zcash-builder/src/txbuilder/builder_data.rs index e0ea0b4..c8e6733 100644 --- a/zcash-hsmbuilder/src/txbuilder/builder_data.rs +++ b/ledger-zcash-builder/src/txbuilder/builder_data.rs @@ -1,28 +1,8 @@ -//! This module mostly contains data structures that are originally present in the -//! zcash_primitives crate but have been adapted to be HSM compatible +//! This module mostly contains data structures that are originally present in +//! the zcash_primitives crate but have been adapted to be HSM compatible use std::io::{self, Write}; -use crate::zcash::{ - note_encryption::NoteEncryption, - primitives::{ - consensus, - keys::OutgoingViewingKey, - legacy::{Script, TransparentAddress}, - memo::MemoBytes as Memo, - merkle_tree::MerklePath, - sapling::{ - note_encryption::sapling_note_encryption, Diversifier, Node, Note, PaymentAddress, - ProofGenerationKey, Rseed, - }, - transaction::{ - self, - components::{sapling, transparent, Amount, OutPoint, TxIn, TxOut, GROTH_PROOF_SIZE}, - sighash::{signature_hash, SignableInput, SIGHASH_ALL}, - TransactionData, - }, - }, -}; use chacha20poly1305::{ aead::{Aead, NewAead}, ChaCha20Poly1305, Key, Nonce, @@ -31,6 +11,23 @@ use group::{cofactor::CofactorGroup, GroupEncoding}; use jubjub::SubgroupPoint; use rand::{CryptoRng, RngCore}; use sha2::{Digest, Sha256}; +use zcash_note_encryption::NoteEncryption; +use zcash_primitives::{ + consensus, + keys::OutgoingViewingKey, + legacy::{Script, TransparentAddress}, + memo::MemoBytes as Memo, + merkle_tree::MerklePath, + sapling::{ + note_encryption::sapling_note_encryption, Diversifier, Node, Note, PaymentAddress, ProofGenerationKey, Rseed, + }, + transaction::{ + self, + components::{sapling, transparent, Amount, OutPoint, TxIn, TxOut, GROTH_PROOF_SIZE}, + sighash::{signature_hash, SignableInput, SIGHASH_ALL}, + TransactionData, + }, +}; use crate::{data::HashSeed, errors::Error, txbuilder::hsmauth, txprover::HsmTxProver}; @@ -41,15 +38,15 @@ const OUT_CIPHERTEXT_SIZE: usize = OUT_PLAINTEXT_SIZE + 16; #[derive(educe::Educe, Clone)] #[educe(Debug)] pub struct SpendDescriptionInfo { - //extsk: ExtendedSpendingKey, //change this to path in ledger + // extsk: ExtendedSpendingKey, //change this to path in ledger pub diversifier: Diversifier, pub note: Note, pub alpha: jubjub::Fr, - //get both from ledger and generate self + // get both from ledger and generate self pub merkle_path: MerklePath, #[educe(Debug(ignore))] pub proofkey: ProofGenerationKey, - //get from ledger + // get from ledger pub rcv: jubjub::Fr, } @@ -57,11 +54,11 @@ pub struct SpendDescriptionInfo { pub struct SaplingOutput { /// `None` represents the `ovk = ⊥` case. pub ovk: Option, - //get from ledger + // get from ledger pub to: PaymentAddress, pub note: Note, pub memo: Memo, - pub rcv: jubjub::Fr, //get from ledger + pub rcv: jubjub::Fr, // get from ledger pub hashseed: Option, } @@ -83,23 +80,11 @@ impl SaplingOutput { return Err(Error::InvalidAmount); } - //let rseed = generate_random_rseed::(height, rng); + // let rseed = generate_random_rseed::(height, rng); - let note = Note { - g_d, - pk_d: *to.pk_d(), - value: value.into(), - rseed, - }; + let note = Note { g_d, pk_d: *to.pk_d(), value: value.into(), rseed }; - Ok(SaplingOutput { - ovk, - to, - note, - memo: memo.unwrap_or_else(Memo::empty), - rcv, - hashseed, - }) + Ok(SaplingOutput { ovk, to, note, memo: memo.unwrap_or_else(Memo::empty), rcv, hashseed }) } pub fn build( @@ -108,25 +93,14 @@ impl SaplingOutput { ctx: &mut PR::SaplingProvingContext, rng: &mut R, params: &P, - ) -> crate::zcash::primitives::transaction::components::OutputDescription< - ::Proof, - > { - let mut encryptor = sapling_note_encryption::( - self.ovk, - self.note.clone(), - self.to.clone(), - self.memo, - rng, - ); - - let (zkproof, cv) = prover.output_proof( - ctx, - *encryptor.esk(), - self.to, - self.note.rcm(), - self.note.value, - self.rcv, - ); + ) -> transaction::components::OutputDescription<::Proof> + { + let mut encryptor = + sapling_note_encryption::(self.ovk, self.note.clone(), self.to.clone(), self.memo, rng); + + let (zkproof, cv) = prover + .output_proof(ctx, *encryptor.esk(), self.to, self.note.rcm(), self.note.value, self.rcv) + .expect("output proof"); let cmu = self.note.cmu(); @@ -136,17 +110,17 @@ impl SaplingOutput { } else { let seed = self.hashseed.unwrap().0; let mut randbytes = [0u8; 32 + OUT_PLAINTEXT_SIZE]; - for i in 0..3 { + for i in 0 .. 3 { let mut sha256 = Sha256::new(); sha256.update([i as u8]); sha256.update(seed); let h = sha256.finalize(); - randbytes[i * 32..(i + 1) * 32].copy_from_slice(&h); + randbytes[i * 32 .. (i + 1) * 32].copy_from_slice(&h); } - let ock = Key::from_slice(&randbytes[0..32]); + let ock = Key::from_slice(&randbytes[0 .. 32]); let out_ciphertext = ChaCha20Poly1305::new(ock) - .encrypt(Nonce::from_slice(&[0u8; 12]), &randbytes[32..]) + .encrypt(Nonce::from_slice(&[0u8; 12]), &randbytes[32 ..]) .unwrap(); assert_eq!(out_ciphertext.len(), OUT_CIPHERTEXT_SIZE); @@ -158,14 +132,7 @@ impl SaplingOutput { let ephemeral_key = encryptor.epk().to_bytes().into(); - crate::zcash::primitives::transaction::components::OutputDescription { - cv, - cmu, - ephemeral_key, - enc_ciphertext, - out_ciphertext, - zkproof, - } + transaction::components::OutputDescription { cv, cmu, ephemeral_key, enc_ciphertext, out_ciphertext, zkproof } } } @@ -187,63 +154,73 @@ impl SaplingMetadata { Default::default() } - /// Returns the index within the transaction of the [`SpendDescription`] corresponding - /// to the `n`-th call to [`crate::Builder::add_sapling_spend`]. + /// Returns the index within the transaction of the [`SpendDescription`] + /// corresponding to the `n`-th call to + /// [`crate::Builder::add_sapling_spend`]. /// - /// Note positions are randomized when building transactions for indistinguishability. - /// This means that the transaction consumer cannot assume that e.g. the first spend - /// they added (via the first call to [`crate::Builder::add_sapling_spend`]) is the first + /// Note positions are randomized when building transactions for + /// indistinguishability. This means that the transaction consumer + /// cannot assume that e.g. the first spend they added (via the first + /// call to [`crate::Builder::add_sapling_spend`]) is the first /// [`SpendDescription`] in the transaction. - pub fn spend_index(&self, n: usize) -> Option { + pub fn spend_index( + &self, + n: usize, + ) -> Option { self.spend_indices.get(n).copied() } - /// Returns the index within the transaction of the [`OutputDescription`] corresponding - /// to the `n`-th call to [`crate::Builder::add_sapling_output`]. + /// Returns the index within the transaction of the [`OutputDescription`] + /// corresponding to the `n`-th call to + /// [`crate::Builder::add_sapling_output`]. /// - /// Note positions are randomized when building transactions for indistinguishability. - /// This means that the transaction consumer cannot assume that e.g. the first output - /// they added (via the first call to [`crate::Builder::add_sapling_output`]) is the first + /// Note positions are randomized when building transactions for + /// indistinguishability. This means that the transaction consumer + /// cannot assume that e.g. the first output they added (via the first + /// call to [`crate::Builder::add_sapling_output`]) is the first /// [`OutputDescription`] in the transaction. - pub fn output_index(&self, n: usize) -> Option { + pub fn output_index( + &self, + n: usize, + ) -> Option { self.output_indices.get(n).copied() } } impl From for SaplingMetadata { - fn from(txmeta: sapling::builder::SaplingMetadata) -> Self { + fn from(tx_meta: sapling::builder::SaplingMetadata) -> Self { let mut spends = vec![]; let mut outputs = vec![]; let mut i = 0; - while let Some(ix) = txmeta.spend_index(i) { + while let Some(ix) = tx_meta.spend_index(i) { spends.push(ix); i += 1; } i = 0; - while let Some(ix) = txmeta.output_index(i) { + while let Some(ix) = tx_meta.output_index(i) { outputs.push(ix); i += 1; } - Self { - spend_indices: spends, - output_indices: outputs, - } + Self { spend_indices: spends, output_indices: outputs } } } #[derive(Clone)] pub struct NullifierInput { pub rcm_old: [u8; 32], - pub notepos: [u8; 8], + pub note_position: [u8; 8], } impl NullifierInput { - pub fn write(&self, mut writer: W) -> io::Result<()> { + pub fn write( + &self, + mut writer: W, + ) -> io::Result<()> { writer.write_all(&self.rcm_old)?; - writer.write_all(&self.notepos) + writer.write_all(&self.note_position) } } @@ -256,7 +233,10 @@ pub struct TransparentScriptData { } impl TransparentScriptData { - pub fn write(&self, mut writer: W) -> io::Result<()> { + pub fn write( + &self, + mut writer: W, + ) -> io::Result<()> { writer.write_all(&self.prevout)?; writer.write_all(&self.script_pubkey)?; writer.write_all(&self.value)?; @@ -274,9 +254,7 @@ pub struct SpendDescription { } impl SpendDescription { - pub fn from( - info: &sapling::SpendDescription, - ) -> SpendDescription { + pub fn from(info: &sapling::SpendDescription) -> SpendDescription { SpendDescription { cv: info.cv.to_bytes(), anchor: info.anchor.to_bytes(), @@ -286,7 +264,10 @@ impl SpendDescription { } } - pub fn write(&self, mut writer: W) -> io::Result<()> { + pub fn write( + &self, + mut writer: W, + ) -> io::Result<()> { writer.write_all(&self.cv)?; writer.write_all(&self.anchor)?; writer.write_all(&self.nullifier)?; @@ -306,16 +287,13 @@ pub struct OutputDescription { } impl - From< - &crate::zcash::primitives::transaction::components::OutputDescription< - ::Proof, - >, - > for OutputDescription + From<&transaction::components::OutputDescription<::Proof>> + for OutputDescription { fn from( - from: &crate::zcash::primitives::transaction::components::OutputDescription< + from: &transaction::components::OutputDescription< ::Proof, - >, + > ) -> Self { Self { cv: from.cv.to_bytes(), @@ -329,7 +307,10 @@ impl } impl OutputDescription { - pub fn write(&self, mut writer: W) -> io::Result<()> { + pub fn write( + &self, + mut writer: W, + ) -> io::Result<()> { writer.write_all(&self.cv)?; writer.write_all(&self.cmu)?; writer.write_all(&self.ephemeral_key)?; @@ -341,7 +322,7 @@ impl OutputDescription { /// Converts a zcash_primitives' SpendDescription to the HSM-compatible format pub fn spend_data_hms_fromtx( - input: &[sapling::SpendDescription], + input: &[sapling::SpendDescription] ) -> Vec { let mut data = Vec::new(); for info in input.iter() { @@ -353,7 +334,7 @@ pub fn spend_data_hms_fromtx( /// Converts a zcash_primitives' OutputDescription to the HSM-compatible format pub fn output_data_hsm_fromtx( - input: &[sapling::OutputDescription], + input: &[sapling::OutputDescription] ) -> Vec { let mut data = Vec::new(); for info in input.iter() { @@ -369,7 +350,7 @@ pub fn spend_old_data_fromtx(data: &[SpendDescriptionInfo]) -> Vec( let mut data = Vec::new(); for (i, (info, vin)) in inputs.iter().zip(vins).enumerate() { let mut prevout = [0u8; 36]; - prevout[0..32].copy_from_slice(vin.prevout.hash().as_ref()); - prevout[32..36].copy_from_slice(&vin.prevout.n().to_le_bytes()); + prevout[0 .. 32].copy_from_slice(vin.prevout.hash().as_ref()); + prevout[32 .. 36].copy_from_slice(&vin.prevout.n().to_le_bytes()); let mut script_pubkey = [0u8; 26]; - info.coin.script_pubkey.write(&mut script_pubkey[..])?; + info.coin + .script_pubkey + .write(&mut script_pubkey[..]) + .map_err(|_| Error::ReadWriteError)?; let mut value = [0u8; 8]; value.copy_from_slice(&info.coin.value.to_i64_le_bytes()); @@ -397,12 +381,7 @@ pub fn transparent_script_data_fromtx( let mut sequence = [0u8; 4]; sequence.copy_from_slice(&vin.sequence.to_le_bytes()); - let ts = TransparentScriptData { - prevout, - script_pubkey, - value, - sequence, - }; + let ts = TransparentScriptData { prevout, script_pubkey, value, sequence }; data.push(ts); } Ok(data) diff --git a/zcash-hsmbuilder/src/txbuilder/hsmauth.rs b/ledger-zcash-builder/src/txbuilder/hsmauth.rs similarity index 77% rename from zcash-hsmbuilder/src/txbuilder/hsmauth.rs rename to ledger-zcash-builder/src/txbuilder/hsmauth.rs index 5e9d587..c581299 100644 --- a/zcash-hsmbuilder/src/txbuilder/hsmauth.rs +++ b/ledger-zcash-builder/src/txbuilder/hsmauth.rs @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2018-2022 Zondax GmbH +* (c) 2018-2022 Zondax AG * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,10 @@ use std::marker::PhantomData; -use crate::zcash::primitives::transaction::{ +use zcash_primitives::transaction::{ self, components::{ - sapling::Authorization as SAuthorization, transparent::Authorization as TAuthorization, - GROTH_PROOF_SIZE, + sapling::Authorization as SAuthorization, transparent::Authorization as TAuthorization, GROTH_PROOF_SIZE, }, Authorization, Authorized, }; @@ -43,9 +42,9 @@ impl Authorization for MixedAuthorization< } pub mod sapling { - use crate::{ - txbuilder::SpendDescriptionInfo, zcash::primitives::transaction::components::sapling, - }; + use zcash_primitives::transaction::components::sapling; + + use crate::txbuilder::SpendDescriptionInfo; /// Unauthorized Sapling bundle - Similar to v0.5 /// @@ -53,7 +52,8 @@ pub mod sapling { /// where the associated AuthSig is not a private struct and has /// some necessary fields added. /// - /// This allows the [`sapling::SpendDescription`] to actually be instantiated + /// This allows the [`sapling::SpendDescription`] to actually be + /// instantiated #[derive(Debug, Default, Clone, Copy)] pub struct Unauthorized {} @@ -65,16 +65,15 @@ pub mod sapling { } pub mod transparent { - use crate::{ - errors::Error, - txbuilder::TransparentInputInfo, - zcash::primitives::transaction::{self, components::transparent, TransactionData}, - }; + use zcash_primitives::transaction::{self, components::transparent, TransactionData}; + + use crate::{errors::Error, txbuilder::TransparentInputInfo}; /// Unauthorized Transparent bundle - Similar to v0.5 /// /// This is a slight variation on [`transparent::builder::Unauthorized`] - /// where the authorization is not a private struct, thus can be constructed manually + /// where the authorization is not a private struct, thus can be constructed + /// manually #[derive(Debug, Clone)] pub struct Unauthorized { pub secp: secp256k1::Secp256k1, @@ -83,24 +82,23 @@ pub mod transparent { impl Default for Unauthorized { fn default() -> Self { - Self { - secp: secp256k1::Secp256k1::gen_new(), - inputs: vec![], - } + Self { secp: secp256k1::Secp256k1::gen_new(), inputs: vec![] } } } impl transparent::Authorization for Unauthorized { - type ScriptSig = - ::ScriptSig; + type ScriptSig = ::ScriptSig; } impl transaction::sighash::TransparentAuthorizingContext for Unauthorized { fn input_amounts(&self) -> Vec { - self.inputs.iter().map(|input| input.coin.value).collect() + self.inputs + .iter() + .map(|input| input.coin.value) + .collect() } - fn input_scriptpubkeys(&self) -> Vec { + fn input_scriptpubkeys(&self) -> Vec { self.inputs .iter() .map(|input| input.coin.script_pubkey.clone()) diff --git a/zcash-hsmbuilder/src/txprover.rs b/ledger-zcash-builder/src/txprover.rs similarity index 68% rename from zcash-hsmbuilder/src/txprover.rs rename to ledger-zcash-builder/src/txprover.rs index 101aa45..d66a85f 100644 --- a/zcash-hsmbuilder/src/txprover.rs +++ b/ledger-zcash-builder/src/txprover.rs @@ -1,26 +1,41 @@ +/******************************************************************************* +* (c) 2022-2024 Zondax AG +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ //! Abstractions over the proving system and parameters for ease of use. use std::path::Path; -use crate::{ - prover::SaplingProvingContext, - txbuilder::{OutputDescription, SpendDescription}, - zcash::{ - primitives::{ - merkle_tree::MerklePath, - sapling::{ - redjubjub::{PublicKey, Signature}, - Diversifier, Node, PaymentAddress, ProofGenerationKey, Rseed, - }, - transaction::components::{Amount, GROTH_PROOF_SIZE}, - }, - proofs::{default_params_folder, load_parameters, parse_parameters, ZcashParameters}, - }, -}; use bellman::groth16::{Parameters, PreparedVerifyingKey}; use bls12_381::Bls12; use ff::Field; use rand_core::OsRng; +use zcash_primitives::{ + merkle_tree::MerklePath, + sapling::{ + redjubjub::{PublicKey, Signature}, + Diversifier, Node, PaymentAddress, ProofGenerationKey, Rseed, + }, + transaction::components::{Amount, GROTH_PROOF_SIZE}, +}; +use zcash_proofs::{default_params_folder, load_parameters, parse_parameters, ZcashParameters}; + +use crate::{ + errors::ProverError, + prover::SaplingProvingContext, + txbuilder::{OutputDescription, SpendDescription}, +}; // Circuit names const SAPLING_SPEND_NAME: &str = "sapling-spend.params"; @@ -31,8 +46,8 @@ const SAPLING_SPEND_HASH: &str = "8270785a1a0d0bc77196f000ee6d221c9c9894f55307bd const SAPLING_OUTPUT_HASH: &str = "657e3d38dbb5cb5e7dd2970e8b03d69b4787dd907285b5a7f0790dcc8072f60bf593b32cc2d1c030e00ff5ae64bf84c5c3beb84ddc841d48264b4a171744d028"; const SPROUT_HASH: &str = "e9b238411bd6c0ec4791e9d04245ec350c9c5744f5610dfcce4365d5ca49dfefd5054e371842b3f88fa1b9d7e8e075249b3ebabd167fa8b0f3161292d36c180a"; -/// An implementation of [`HsmTxProver`] using Sapling Spend and Output parameters from -/// locally-accessible paths. +/// An implementation of [`HsmTxProver`] using Sapling Spend and Output +/// parameters from locally-accessible paths. pub struct LocalTxProver { spend_params: Parameters, spend_vk: PreparedVerifyingKey, @@ -46,7 +61,7 @@ impl LocalTxProver { /// /// ```should_panic /// use std::path::Path; - /// use zcash_hsmbuilder::LocalTxProver; + /// use ledger_zcash_builder::LocalTxProver; /// /// let tx_prover = LocalTxProver::new( /// Path::new("/path/to/sapling-spend.params"), @@ -56,20 +71,15 @@ impl LocalTxProver { /// /// # Panics /// - /// This function will panic if the paths do not point to valid parameter files with - /// the expected hashes. - pub fn new(spend_path: &Path, output_path: &Path) -> Self { - let ZcashParameters { - spend_params, - spend_vk, - output_params, - .. - } = load_parameters(spend_path, output_path, None); - LocalTxProver { - spend_params, - spend_vk, - output_params, - } + /// This function will panic if the paths do not point to valid parameter + /// files with the expected hashes. + pub fn new( + spend_path: &Path, + output_path: &Path, + ) -> Self { + let ZcashParameters { spend_params, spend_vk, output_params, .. } = + load_parameters(spend_path, output_path, None); + LocalTxProver { spend_params, spend_vk, output_params } } /// Creates a `LocalTxProver` using parameters specified as byte arrays. @@ -78,35 +88,34 @@ impl LocalTxProver { /// /// ```should_panic /// use std::path::Path; - /// use zcash_hsmbuilder::LocalTxProver; + /// use ledger_zcash_builder::LocalTxProver; /// /// let tx_prover = LocalTxProver::from_bytes(&[0u8], &[0u8]); /// ``` /// /// # Panics /// - /// This function will panic if the byte arrays do not contain valid parameters with - /// the expected hashes. - pub fn from_bytes(spend_param_bytes: &[u8], output_param_bytes: &[u8]) -> Self { + /// This function will panic if the byte arrays do not contain valid + /// parameters with the expected hashes. + pub fn from_bytes( + spend_param_bytes: &[u8], + output_param_bytes: &[u8], + ) -> Self { let p = parse_parameters(spend_param_bytes, output_param_bytes, None); - LocalTxProver { - spend_params: p.spend_params, - spend_vk: p.spend_vk, - output_params: p.output_params, - } + LocalTxProver { spend_params: p.spend_params, spend_vk: p.spend_vk, output_params: p.output_params } } - /// Attempts to create a `LocalTxProver` using parameters from the default local - /// location. + /// Attempts to create a `LocalTxProver` using parameters from the default + /// local location. /// - /// Returns `None` if any of the parameters cannot be found in the default local - /// location. + /// Returns `None` if any of the parameters cannot be found in the default + /// local location. /// /// # Examples /// /// ``` - /// use zcash_hsmbuilder::LocalTxProver; + /// use ledger_zcash_builder::LocalTxProver; /// /// match LocalTxProver::with_default_location() { /// Some(tx_prover) => (), @@ -116,17 +125,14 @@ impl LocalTxProver { /// /// # Panics /// - /// This function will panic if the parameters in the default local location do not - /// have the expected hashes. + /// This function will panic if the parameters in the default local location + /// do not have the expected hashes. #[cfg(feature = "local-prover")] #[cfg_attr(docsrs, doc(cfg(feature = "local-prover")))] pub fn with_default_location() -> Option { let params_dir = default_params_folder()?; let (spend_path, output_path) = if params_dir.exists() { - ( - params_dir.join(SAPLING_SPEND_NAME), - params_dir.join(SAPLING_OUTPUT_NAME), - ) + (params_dir.join(SAPLING_SPEND_NAME), params_dir.join(SAPLING_OUTPUT_NAME)) } else { return None; }; @@ -137,40 +143,36 @@ impl LocalTxProver { Some(LocalTxProver::new(&spend_path, &output_path)) } - /// Creates a `LocalTxProver` using Sapling parameters bundled inside the binary. + /// Creates a `LocalTxProver` using Sapling parameters bundled inside the + /// binary. /// - /// This requires the `bundled-prover` feature, which will increase the binary size by - /// around 50 MiB. + /// This requires the `bundled-prover` feature, which will increase the + /// binary size by around 50 MiB. #[cfg(feature = "bundled-prover")] #[cfg_attr(docsrs, doc(cfg(feature = "bundled-prover")))] pub fn bundled() -> Self { let (spend_buf, output_buf) = wagyu_zcash_parameters::load_sapling_parameters(); - let ZcashParameters { - spend_params, - spend_vk, - output_params, - .. - } = parse_parameters(&spend_buf[..], &output_buf[..], None); - - LocalTxProver { - spend_params, - spend_vk, - output_params, - } + let ZcashParameters { spend_params, spend_vk, output_params, .. } = + parse_parameters(&spend_buf[..], &output_buf[..], None); + + LocalTxProver { spend_params, spend_vk, output_params } } } /// HSM compatible version of [`crate::zcash::primitives::prover::TxProver`] pub trait HsmTxProver { - /// Type for persisting any necessary context across multiple Sapling proofs. + /// Type for persisting any necessary context across multiple Sapling + /// proofs. type SaplingProvingContext; + type Error: std::error::Error; + /// Instantiate a new Sapling proving context. fn new_sapling_proving_context(&self) -> Self::SaplingProvingContext; /// Create the value commitment, re-randomized key, and proof for a Sapling - /// [`SpendDescription`], while accumulating its value commitment randomness inside - /// the context for later use. + /// [`SpendDescription`], while accumulating its value commitment randomness + /// inside the context for later use. fn spend_proof( &self, ctx: &mut Self::SaplingProvingContext, @@ -182,11 +184,11 @@ pub trait HsmTxProver { anchor: bls12_381::Scalar, merkle_path: MerklePath, rcv: jubjub::Fr, - ) -> Result<([u8; GROTH_PROOF_SIZE], jubjub::ExtendedPoint, PublicKey), ()>; + ) -> Result<([u8; GROTH_PROOF_SIZE], jubjub::ExtendedPoint, PublicKey), Self::Error>; - /// Create the value commitment and proof for a Sapling [`OutputDescription`], - /// while accumulating its value commitment randomness inside the context for later - /// use. + /// Create the value commitment and proof for a Sapling + /// [`OutputDescription`], while accumulating its value commitment + /// randomness inside the context for later use. fn output_proof( &self, ctx: &mut Self::SaplingProvingContext, @@ -195,22 +197,24 @@ pub trait HsmTxProver { rcm: jubjub::Fr, value: u64, rcv: jubjub::Fr, - ) -> ([u8; GROTH_PROOF_SIZE], jubjub::ExtendedPoint); + ) -> Result<([u8; GROTH_PROOF_SIZE], jubjub::ExtendedPoint), Self::Error>; /// Create the `bindingSig` for a Sapling transaction. /// - /// All calls to [`HsmTxProver::spend_proof`] and [`HsmTxProver::output_proof`] - /// must be completed before calling this function. + /// All calls to [`HsmTxProver::spend_proof`] and + /// [`HsmTxProver::output_proof`] must be completed before calling this + /// function. fn binding_sig( &self, ctx: &mut Self::SaplingProvingContext, value_balance: Amount, sighash: &[u8; 32], - ) -> Result; + ) -> Result; } impl HsmTxProver for LocalTxProver { type SaplingProvingContext = SaplingProvingContext; + type Error = ProverError; fn new_sapling_proving_context(&self) -> Self::SaplingProvingContext { SaplingProvingContext::new() @@ -227,7 +231,7 @@ impl HsmTxProver for LocalTxProver { anchor: bls12_381::Scalar, merkle_path: MerklePath, rcv: jubjub::Fr, - ) -> Result<([u8; GROTH_PROOF_SIZE], jubjub::ExtendedPoint, PublicKey), ()> { + ) -> Result<([u8; GROTH_PROOF_SIZE], jubjub::ExtendedPoint, PublicKey), ProverError> { let (proof, cv, rk) = ctx.spend_proof( proof_generation_key, diversifier, @@ -244,7 +248,7 @@ impl HsmTxProver for LocalTxProver { let mut zkproof = [0u8; GROTH_PROOF_SIZE]; proof .write(&mut zkproof[..]) - .expect("should be able to serialize a proof"); + .map_err(|_| ProverError::ReadWriteError)?; Ok((zkproof, cv, rk)) } @@ -257,16 +261,15 @@ impl HsmTxProver for LocalTxProver { rcm: jubjub::Fr, value: u64, rcv: jubjub::Fr, - ) -> ([u8; GROTH_PROOF_SIZE], jubjub::ExtendedPoint) { - let (proof, cv) = - ctx.output_proof(esk, payment_address, rcm, value, &self.output_params, rcv); + ) -> Result<([u8; GROTH_PROOF_SIZE], jubjub::ExtendedPoint), ProverError> { + let (proof, cv) = ctx.output_proof(esk, payment_address, rcm, value, &self.output_params, rcv)?; let mut zkproof = [0u8; GROTH_PROOF_SIZE]; proof .write(&mut zkproof[..]) - .expect("should be able to serialize a proof"); + .map_err(|_| ProverError::OutputProof)?; - (zkproof, cv) + Ok((zkproof, cv)) } fn binding_sig( @@ -274,12 +277,12 @@ impl HsmTxProver for LocalTxProver { ctx: &mut Self::SaplingProvingContext, value_balance: Amount, sighash: &[u8; 32], - ) -> Result { + ) -> Result { ctx.binding_sig(value_balance, sighash) } } -impl crate::zcash::primitives::sapling::prover::TxProver for LocalTxProver { +impl zcash_primitives::sapling::prover::TxProver for LocalTxProver { type SaplingProvingContext = ::SaplingProvingContext; fn new_sapling_proving_context(&self) -> Self::SaplingProvingContext { @@ -297,7 +300,7 @@ impl crate::zcash::primitives::sapling::prover::TxProver for LocalTxProver { anchor: bls12_381::Scalar, merkle_path: MerklePath, ) -> Result<([u8; GROTH_PROOF_SIZE], jubjub::ExtendedPoint, PublicKey), ()> { - //default, same as zcash's prover + // default, same as zcash's prover let mut rng = OsRng; let rcv = jubjub::Fr::random(&mut rng); @@ -313,6 +316,7 @@ impl crate::zcash::primitives::sapling::prover::TxProver for LocalTxProver { merkle_path, rcv, ) + .map_err(|_| ()) } fn output_proof( @@ -323,11 +327,11 @@ impl crate::zcash::primitives::sapling::prover::TxProver for LocalTxProver { rcm: jubjub::Fr, value: u64, ) -> ([u8; GROTH_PROOF_SIZE], jubjub::ExtendedPoint) { - //default, same as zcash's prover + // default, same as zcash's prover let mut rng = OsRng; let rcv = jubjub::Fr::random(&mut rng); - HsmTxProver::output_proof(self, ctx, esk, payment_address, rcm, value, rcv) + HsmTxProver::output_proof(self, ctx, esk, payment_address, rcm, value, rcv).expect("output proof") } fn binding_sig( @@ -336,6 +340,6 @@ impl crate::zcash::primitives::sapling::prover::TxProver for LocalTxProver { value_balance: Amount, sighash: &[u8; 32], ) -> Result { - HsmTxProver::binding_sig(self, ctx, value_balance, sighash) + HsmTxProver::binding_sig(self, ctx, value_balance, sighash).map_err(|_| ()) } } diff --git a/ledger-zcash/Cargo.toml b/ledger-zcash/Cargo.toml index 34041a3..d7b3271 100644 --- a/ledger-zcash/Cargo.toml +++ b/ledger-zcash/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "ledger-zcash" description = "Library to integrate with the Ledger Zcash app" -version = "0.6.0" +version = "0.11.2" license = "Apache-2.0" -authors = ["Zondax GmbH "] +authors = ["Zondax AG "] homepage = "https://github.com/Zondax/ledger-zcash-rs" repository = "https://github.com/Zondax/ledger-zcash-rs" readme = "README.md" @@ -16,45 +16,42 @@ autobenches = false name = "ledger_zcash" [features] -default = ["normal-zcash"] -normal-zcash = ["zcash_primitives", "zcash-hsmbuilder/normal-zcash"] -zecwallet-compat = ["zecw_primitives", "zcash-hsmbuilder/zecwallet-compat"] +default = ["zcash_primitives"] [dependencies] -arrayvec = "0.7.2" -byteorder = "1.4.3" -cfg-if = "1.0.0" +arrayvec = "0.7" +byteorder = "1.5" +cfg-if = "1" +lazy_static = "1" +hex = "0.4" +serde = { version = "1.0", features = ["derive"] } +tokio = { version = "1.38", features = ["sync"] } +educe = "0.5" +log = "0.4" +sha2 = "0.10.8" +thiserror = "1.0" + ff = "0.12" group = "0.12" -lazy_static = "1" -hex = "0.4.3" jubjub = { version = "0.9", default-features = false } -log = "0.4.17" rand_core = "0.6" ripemd = "0.1" -secp256k1 = { version = "0.21", default-features = false } -sha2 = "0.9" -thiserror = "1.0.31" -zx-bip44 = "0.1.0" +secp256k1 = { version = "0.29", default-features = false } -serde = { version = "1.0", features = ["derive"] } - -ledger-transport = "0.9.0" -ledger-zondax-generic = "0.9.1" - -#zcash -zcash-hsmbuilder = { path = "../zcash-hsmbuilder", default-features = false } -zcash_primitives = { version = "0.6", features = ["transparent-inputs"], optional = true } +zx-bip44 = "0.1.0" +ledger-transport = "0.11" +ledger-zondax-generic = "0.11" +ledger-zcash-builder = { version = "0.11.1" } -#zecwallet-compat -zecw_primitives = { git = "https://github.com/adityapk00/librustzcash", rev = "7183acd2fe12ebf201cae5b871166e356273c481", features = ["transparent-inputs"], optional = true, package = "zcash_primitives" } -tokio = { version = "1.6", features = ["sync"] } -educe = "0.4.19" +zcash_primitives = { version = "0.7", features = [ + "transparent-inputs", +], optional = true } [dev-dependencies] -futures = "0.3.21" -matches = "0.1.9" -serial_test = "0.7.0" -env_logger = { version = "0.9.0", default-features = false } +futures = "0.3" +matches = "0.1" +serial_test = "3.1" +env_logger = { version = "0.11", default-features = false } tokio = { version = "1", features = ["full"] } -ledger-transport-hid = "0.9.0" + +ledger-transport-hid = "0.11" diff --git a/ledger-zcash/src/apdu_extra.rs b/ledger-zcash/src/apdu_extra.rs index 9717ffb..85fbb52 100644 --- a/ledger-zcash/src/apdu_extra.rs +++ b/ledger-zcash/src/apdu_extra.rs @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2023 Zondax AG +* (c) 2022-2024 Zondax AG * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,31 @@ where E: Exchange, E::Error: std::error::Error, { - /// Variant of [`ledger_zondax_generic::AppExt::send_chunks`] which sends P2 on all messages + /// Handles the error response from APDU exchange. + /// + /// # Arguments + /// * `response` - The response from the APDU exchange. + /// + /// # Returns + /// A result indicating success or containing a specific ledger application error. + fn handle_response_error(response: &APDUAnswer) -> Result<(), LedgerAppError> { + match response.error_code() { + Ok(APDUErrorCode::NoError) => Ok(()), + Ok(err) => Err(LedgerAppError::AppSpecific(err as _, err.description())), + Err(err) => Err(LedgerAppError::Unknown(err)), + } + } + + /// Sends chunks of a message with P2 set on all messages. + /// This is a variant of the `send_chunks` method which specifically sets P2 for all chunks. + /// + /// # Arguments + /// * `transport` - The transport layer used for communication. + /// * `command` - The initial APDU command. + /// * `message` - The message to be sent in chunks. + /// + /// # Returns + /// A result containing the final APDU answer or a ledger application error. pub(crate) async fn send_chunks_p2_all + Send + Sync>( transport: &E, command: APDUCommand, @@ -45,12 +69,9 @@ where let p2 = command.p2; + // Initial exchange with the transport layer let mut response = transport.exchange(&command).await?; - match response.error_code() { - Ok(APDUErrorCode::NoError) => {} - Ok(err) => return Err(LedgerAppError::AppSpecific(err as _, err.description())), - Err(err) => return Err(LedgerAppError::Unknown(err)), - } + Self::handle_response_error(&response)?; // Send message chunks let last_chunk_index = chunks.len() - 1; @@ -60,20 +81,10 @@ where p1 = ChunkPayloadType::Last as u8 } - let command = APDUCommand { - cla: command.cla, - ins: command.ins, - p1, - p2, - data: chunk.to_vec(), - }; + let command = APDUCommand { cla: command.cla, ins: command.ins, p1, p2, data: chunk.to_vec() }; response = transport.exchange(&command).await?; - match response.error_code() { - Ok(APDUErrorCode::NoError) => {} - Ok(err) => return Err(LedgerAppError::AppSpecific(err as _, err.description())), - Err(err) => return Err(LedgerAppError::Unknown(err)), - } + Self::handle_response_error(&response)?; } Ok(response) diff --git a/ledger-zcash/src/app.rs b/ledger-zcash/src/app.rs index 2154a7a..c582917 100644 --- a/ledger-zcash/src/app.rs +++ b/ledger-zcash/src/app.rs @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2022 Zondax GmbH +* (c) 2022-2024 Zondax AG * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,128 +21,34 @@ use std::{convert::TryFrom, path::Path, str}; +use byteorder::{LittleEndian, WriteBytesExt}; +use group::GroupEncoding; use ledger_transport::{APDUCommand, APDUErrorCode, Exchange}; -use ledger_zondax_generic::{ - App, AppExt, AppInfo, ChunkPayloadType, DeviceInfo, LedgerAppError, Version, +use ledger_zcash_builder::{ + data::{ + HashSeed, HsmTxData, InitData, OutputBuilderInfo, SaplingInData, SaplingOutData, SpendBuilderInfo, TinData, + ToutData, TransparentInputBuilderInfo, TransparentOutputBuilderInfo, + }, + txbuilder::SaplingMetadata, }; - -use crate::zcash::primitives::{ +use ledger_zondax_generic::{App, AppExt, AppInfo, ChunkPayloadType, DeviceInfo, LedgerAppError, Version}; +use sha2::{Digest, Sha256}; +use zcash_primitives::{ consensus::{self, Parameters}, keys::OutgoingViewingKey, legacy::Script, memo::MemoBytes as Memo, merkle_tree::MerklePath, - sapling::{ - redjubjub::Signature, Diversifier, Node, Note, Nullifier, PaymentAddress, - ProofGenerationKey, Rseed, - }, + sapling::{redjubjub::Signature, Diversifier, Node, Note, Nullifier, PaymentAddress, ProofGenerationKey, Rseed}, transaction::{ components::{Amount, OutPoint}, Transaction, TxVersion, }, }; - -use zcash_hsmbuilder::{ - data::{ - HashSeed, HsmTxData, InitData, OutputBuilderInfo, ShieldedOutputData, ShieldedSpendData, - SpendBuilderInfo, TinData, ToutData, TransparentInputBuilderInfo, - TransparentOutputBuilderInfo, - }, - txbuilder::SaplingMetadata, -}; - -use byteorder::{LittleEndian, WriteBytesExt}; -use group::GroupEncoding; -use sha2::{Digest, Sha256}; use zx_bip44::BIP44Path; use crate::builder::{Builder, BuilderError}; - -const INS_GET_IVK: u8 = 0xf0; -const INS_GET_OVK: u8 = 0xf1; -const INS_GET_NF: u8 = 0xf2; -const INS_INIT_TX: u8 = 0xa0; -const INS_EXTRACT_SPEND: u8 = 0xa1; -const INS_EXTRACT_OUTPUT: u8 = 0xa2; -const INS_CHECKANDSIGN: u8 = 0xa3; -const INS_EXTRACT_SPENDSIG: u8 = 0xa4; -const INS_EXTRACT_TRANSSIG: u8 = 0xa5; -const INS_GET_DIV_LIST: u8 = 0x09; - -const CLA: u8 = 0x85; -const INS_GET_ADDR_SECP256K1: u8 = 0x01; -const INS_GET_ADDR_SAPLING: u8 = 0x11; -const INS_GET_ADDR_SAPLING_DIV: u8 = 0x10; - -///Lenght of diversifier index -const DIV_INDEX_SIZE: usize = 11; -///Diversifier length -const DIV_SIZE: usize = 11; -///get div list returns 20 diversifiers -const DIV_LIST_SIZE: usize = 220; - -///OVK size -const OVK_SIZE: usize = 32; - -///IVK size -const IVK_SIZE: usize = 32; - -///NF size -const NF_SIZE: usize = 32; - -///note commitment size -const NOTE_COMMITMENT_SIZE: usize = 32; - -///sha256 digest size -const SHA256_DIGEST_SIZE: usize = 32; - -///AK size -const AK_SIZE: usize = 32; - -///NSK size -const NSK_SIZE: usize = 32; - -///ALPHA size -const ALPHA_SIZE: usize = 32; - -///RCV size -const RCV_SIZE: usize = 32; - -///Spenddata length: AK (32) + NSK (32) + Alpha(32) + RCV (32) -const SPENDDATA_SIZE: usize = AK_SIZE + NSK_SIZE + ALPHA_SIZE + RCV_SIZE; - -///RCM size -const RSEED_SIZE: usize = 32; - -///hashseed size -const HASHSEED_SIZE: usize = 32; - -///outputdata length: RCV (32) + RCM (32) + -const OUTPUTDATA_SIZE: usize = RCV_SIZE + RSEED_SIZE; - -///outputdata length: RCV (32) + RCM (32) + Hashseed (32) -const OUTPUTDATA_HASHSEED_SIZE: usize = RCV_SIZE + RSEED_SIZE + HASHSEED_SIZE; - -/// Public Key Length (secp256k1) -pub const PK_LEN_SECP261K1: usize = 33; - -/// Public Key Length (sapling) -pub const PK_LEN_SAPLING: usize = 43; - -//T_IN input size: BIP44-path (20) + script (26) + value (8) -const T_IN_INPUT_SIZE: usize = 54; - -//T_OUT input size: script (26) + value (8) -const T_OUT_INPUT_SIZE: usize = 34; - -//S_SPEND input size: zip32-path (4) + address (43) + value (8) -const S_SPEND_INPUT_SIZE: usize = 55; - -//S_SPEND input size: address (43) + value (8) + memotype (1) + ovk(32) -const S_OUT_INPUT_SIZE: usize = 84; - -//Signature size for transparent and shielded signatures -const SIG_SIZE: usize = 64; +use crate::config::*; type PublicKeySecp256k1 = [u8; PK_LEN_SECP261K1]; @@ -166,35 +72,30 @@ impl ZcashApp { } } -///Data needed to handle transparent input for sapling transaction -///Contains information needed for both ledger and builder +/// Data needed to handle transparent input for sapling transaction +/// Contains information needed for both ledger and builder #[derive(educe::Educe)] #[educe(Debug)] pub struct DataTransparentInput { - ///BIP44 path for transparent input key derivation + /// BIP44 path for transparent input key derivation pub path: BIP44Path, - ///Public key belonging to the secret key (of the BIP44 path) - #[educe(Debug(trait = "std::fmt::Display"))] + /// Public key belonging to the secret key (of the BIP44 path) pub pk: secp256k1::PublicKey, - ///UTXO of transparent input + /// UTXO of transparent input pub prevout: OutPoint, - ///Script of transparent input + /// Script of transparent input pub script: Script, - ///Value of transparent input + /// Value of transparent input pub value: Amount, } impl DataTransparentInput { - ///Takes the fields needed to send to the ledger + /// Takes the fields needed to send to the ledger pub fn to_init_data(&self) -> TinData { - TinData { - path: self.path.0, - address: self.script.clone(), - value: self.value, - } + TinData { path: self.path.0, address: self.script.clone(), value: self.value } } - ///Takes the fields needed to send to the builder + /// Takes the fields needed to send to the builder pub fn to_builder_data(&self) -> TransparentInputBuilderInfo { TransparentInputBuilderInfo { outp: self.prevout.clone(), @@ -205,66 +106,60 @@ impl DataTransparentInput { } } -///Data needed to handle transparent output for sapling transaction +/// Data needed to handle transparent output for sapling transaction #[derive(Debug)] pub struct DataTransparentOutput { - ///The transparent output value + /// The transparent output value pub value: Amount, - ///The transparent output script + /// The transparent output script pub script_pubkey: Script, } impl DataTransparentOutput { - ///Decouples this struct to send to ledger + /// Decouples this struct to send to ledger pub fn to_init_data(&self) -> ToutData { - ToutData { - address: self.script_pubkey.clone(), - value: self.value, - } + ToutData { address: self.script_pubkey.clone(), value: self.value } } - ///Decouples this struct to send to builder + /// Decouples this struct to send to builder pub fn to_builder_data(&self) -> TransparentOutputBuilderInfo { - TransparentOutputBuilderInfo { - address: self.script_pubkey.clone(), - value: self.value, - } + TransparentOutputBuilderInfo { address: self.script_pubkey.clone(), value: self.value } } } -///Data needed to handle shielded spend for sapling transaction +/// Data needed to handle shielded spend for sapling transaction #[derive(Clone, Debug)] pub struct DataShieldedSpend { - ///ZIP32 path (last non-constant value) + /// ZIP32 path (last non-constant value) pub path: u32, /// Spend note /// Note: only Rseed::AfterZip202 supported pub note: Note, /// Diversifier of the address of the note pub diversifier: Diversifier, - ///Witness for the spend note + /// Witness for the spend note pub witness: MerklePath, } impl DataShieldedSpend { - /// Reetrieve the PaymentAddress that the note was paid to + /// Retrieve the PaymentAddress that the note was paid to pub fn address(&self) -> PaymentAddress { PaymentAddress::from_parts(self.diversifier, self.note.pk_d) - //if we have a note then pk_d is not the identity + // if we have a note then pk_d is not the identity .expect("pk_d not identity") } - ///Take the fields needed to send to ledger - pub fn to_init_data(&self) -> ShieldedSpendData { - ShieldedSpendData { + /// Take the fields needed to send to ledger + pub fn to_init_data(&self) -> SaplingInData { + SaplingInData { path: self.path, address: self.address(), - //if we have a note the amount is in range + // if we have a note the amount is in range value: Amount::from_u64(self.note.value).unwrap(), } } - ///Take the fields plus additional inputs to send to builder + /// Take the fields plus additional inputs to send to builder pub fn to_builder_data( &self, spendinfo: (ProofGenerationKey, jubjub::Fr, jubjub::Fr), @@ -283,34 +178,38 @@ impl DataShieldedSpend { } } -///Data needed to handle shielded output for sapling transaction +/// Data needed to handle shielded output for sapling transaction #[derive(educe::Educe)] #[educe(Debug)] pub struct DataShieldedOutput { - ///address of shielded output + /// address of shielded output #[educe(Debug(method = "crate::zcash::payment_address_bytes_fmt"))] pub address: PaymentAddress, - ///value send to that address + /// value send to that address pub value: Amount, - ///Optional outgoing viewing key + /// Optional outgoing viewing key pub ovk: Option, - ///Optional Memo + /// Optional Memo pub memo: Option, } impl DataShieldedOutput { - ///Constructs the fields needed to send to ledger - ///Ledger only checks memo-type, not the content - pub fn to_init_data(&self) -> ShieldedOutputData { - ShieldedOutputData { + /// Constructs the fields needed to send to ledger + /// Ledger only checks memo-type, not the content + pub fn to_init_data(&self) -> SaplingOutData { + SaplingOutData { address: self.address.clone(), value: self.value, - memo_type: self.memo.as_ref().map(|v| v.as_array()[0]).unwrap_or(0xf6), + memo_type: self + .memo + .as_ref() + .map(|v| v.as_array()[0]) + .unwrap_or(0xf6), ovk: self.ovk, } } - ///Take the fields plus additional inputs to send to builder + /// Take the fields plus additional inputs to send to builder pub fn to_builder_data( &self, outputinfo: (jubjub::Fr, Rseed, Option), @@ -327,25 +226,26 @@ impl DataShieldedOutput { } } -///Data needed for sapling transaction +/// Data needed for sapling transaction #[derive(Debug)] pub struct DataInput { - ///transaction fee. + /// transaction fee. /// Note: Ledger only supports fees of 10000 or 1000 - /// Note: Ledger only supports vectors up to length 5 at the moment for all below vectors + /// Note: Ledger only supports vectors up to length 5 at the moment for all + /// below vectors pub txfee: u64, - ///A vector of transparent inputs + /// A vector of transparent inputs pub vec_tin: Vec, - ///A vector of transparent outputs + /// A vector of transparent outputs pub vec_tout: Vec, - ///A vector of shielded spends + /// A vector of shielded spends pub vec_sspend: Vec, - ///A vector of shielded outputs + /// A vector of shielded outputs pub vec_soutput: Vec, } impl DataInput { - ///Prepares the data to send to the ledger + /// Prepares the data to send to the ledger pub fn to_inittx_data(&self) -> InitData { let mut t_in = Vec::with_capacity(self.vec_tin.len() * T_IN_INPUT_SIZE); for info in self.vec_tin.iter() { @@ -367,16 +267,11 @@ impl DataInput { s_output.push(info.to_init_data()); } - InitData { - t_in, - t_out, - s_spend, - s_output, - } + InitData { t_in, t_out, s_spend, s_output } } } -//type PublicKeySapling = [u8; PK_LEN_SAPLING]; +// type PublicKeySapling = [u8; PK_LEN_SAPLING]; /// Zcash unshielded address #[allow(dead_code)] @@ -400,7 +295,7 @@ impl ZcashApp where E: Exchange + Send + Sync, E::Error: std::error::Error, - //this bound is unnecessary but it's repeated here + // this bound is unnecessary but it's repeated here // for the sake of documentation Self: AppExt, { @@ -419,7 +314,7 @@ where >::get_device_info(&self.apdu_transport).await } - ///Initiates a transaction in the ledger + /// Initiates a transaction in the ledger pub async fn init_tx( &self, data: InitData, @@ -437,50 +332,39 @@ where data: Vec::::new(), }; - let response = - >::send_chunks(&self.apdu_transport, start_command, &data).await?; + let response = >::send_chunks(&self.apdu_transport, start_command, &data).await?; log::info!("init ok"); let response_data = response.data(); match response.error_code() { - Ok(APDUErrorCode::NoError) if response_data.is_empty() => { - return Err(LedgerAppError::NoSignature) - } - Ok(APDUErrorCode::NoError) => {} + Ok(APDUErrorCode::NoError) if response_data.is_empty() => return Err(LedgerAppError::NoSignature), + Ok(APDUErrorCode::NoError) => {}, Ok(err) => return Err(LedgerAppError::AppSpecific(err as _, err.description())), - Err(err) => { - return Err(LedgerAppError::AppSpecific( - err, - "[APDU_ERROR] Unknown".to_string(), - )) - } + Err(err) => return Err(LedgerAppError::AppSpecific(err, "[APDU_ERROR] Unknown".to_string())), } let mut hash = [0u8; SHA256_DIGEST_SIZE]; - hash.copy_from_slice(&response_data[..SHA256_DIGEST_SIZE]); + hash.copy_from_slice(&response_data[.. SHA256_DIGEST_SIZE]); let mut sha256 = Sha256::new(); sha256.update(data); let h = sha256.finalize(); if h[..] != hash[..] { - Err(LedgerAppError::AppSpecific( - 0, - String::from("Something went wrong in data transport"), - )) + Err(LedgerAppError::AppSpecific(0, String::from("Something went wrong in data transport"))) } else { Ok(hash) } } - ///Initiates a transaction in the ledger + /// Initiates a transaction in the ledger pub async fn checkandsign( &self, data: HsmTxData, tx_version: TxVersion, ) -> Result<[u8; 32], LedgerAppError> { - //this is actually infallible + // this is actually infallible let data = data.to_hsm_bytes().unwrap(); let hex_tx_version = match tx_version { TxVersion::Zip225 => 0x05, @@ -488,10 +372,7 @@ where _ => 0u8, }; if hex_tx_version == 0u8 { - return Err(LedgerAppError::AppSpecific( - 0, - String::from("Unsupported transaction version"), - )); + return Err(LedgerAppError::AppSpecific(0, String::from("Unsupported transaction version"))); } let start_command = APDUCommand { cla: Self::CLA, @@ -509,37 +390,27 @@ where let response_data = response.data(); match response.error_code() { - Ok(APDUErrorCode::NoError) if response_data.is_empty() => { - return Err(LedgerAppError::NoSignature) - } - Ok(APDUErrorCode::NoError) => {} + Ok(APDUErrorCode::NoError) if response_data.is_empty() => return Err(LedgerAppError::NoSignature), + Ok(APDUErrorCode::NoError) => {}, Ok(err) => return Err(LedgerAppError::AppSpecific(err as _, err.description())), - Err(err) => { - return Err(LedgerAppError::AppSpecific( - err, - "[APDU_ERROR] Unknown".to_string(), - )) - } + Err(err) => return Err(LedgerAppError::AppSpecific(err, "[APDU_ERROR] Unknown".to_string())), } let mut hash = [0u8; SHA256_DIGEST_SIZE]; - hash.copy_from_slice(&response_data[..SHA256_DIGEST_SIZE]); + hash.copy_from_slice(&response_data[.. SHA256_DIGEST_SIZE]); let mut sha256 = Sha256::new(); sha256.update(data); let h = sha256.finalize(); if h[..] != hash[..] { - Err(LedgerAppError::AppSpecific( - 0, - String::from("Something went wrong in data transport"), - )) + Err(LedgerAppError::AppSpecific(0, String::from("Something went wrong in data transport"))) } else { Ok(hash) } } - ///Does a complete transaction in the ledger + /// Does a complete transaction in the ledger pub async fn do_transaction( &self, input: DataInput, @@ -551,30 +422,21 @@ where log::info!("adding transaction data to builder"); let fee = input.txfee; - let builder: Builder = Builder::try_from(input) - .map_err(|e: BuilderError| LedgerAppError::AppSpecific(0, e.to_string()))?; + let builder: Builder = + Builder::try_from(input).map_err(|e: BuilderError| LedgerAppError::AppSpecific(0, e.to_string()))?; - let prover = zcash_hsmbuilder::txprover::LocalTxProver::new( + let prover = ledger_zcash_builder::txprover::LocalTxProver::new( Path::new("../params/sapling-spend.params"), Path::new("../params/sapling-output.params"), ); log::info!("building the transaction"); - // Set up a channel to recieve updates on the progress of building the transaction. + // Set up a channel to recieve updates on the progress of building the + // transaction. let (tx, _) = std::sync::mpsc::channel(); let txdata = builder - .build( - self, - parameters, - &prover, - fee, - &mut rand_core::OsRng, - target_height, - branch, - tx_version, - Some(tx), - ) + .build(self, parameters, &prover, fee, &mut rand_core::OsRng, target_height, branch, tx_version, Some(tx)) .await .map_err(|e| LedgerAppError::AppSpecific(0, e.to_string()))?; @@ -597,24 +459,16 @@ where let serialized_path = path.serialize(); let p1 = if require_confirmation { 1 } else { 0 }; - let command = APDUCommand { - cla: Self::CLA, - ins: INS_GET_ADDR_SECP256K1, - p1, - p2: 0x00, - data: serialized_path, - }; + let command = APDUCommand { cla: Self::CLA, ins: INS_GET_ADDR_SECP256K1, p1, p2: 0x00, data: serialized_path }; - let response = self.apdu_transport.exchange(&command).await?; + let response = self + .apdu_transport + .exchange(&command) + .await?; match response.error_code() { - Ok(APDUErrorCode::NoError) => {} + Ok(APDUErrorCode::NoError) => {}, Ok(err) => return Err(LedgerAppError::AppSpecific(err as _, err.description())), - Err(err) => { - return Err(LedgerAppError::AppSpecific( - err, - "[APDU_ERROR] Unknown".to_string(), - )) - } + Err(err) => return Err(LedgerAppError::AppSpecific(err, "[APDU_ERROR] Unknown".to_string())), } let response_data = response.data(); @@ -624,17 +478,14 @@ where log::info!("Received response {}", response_data.len()); - let mut address = AddressUnshielded { - public_key: [0; PK_LEN_SECP261K1], - address: "".to_string(), - }; + let mut address = AddressUnshielded { public_key: [0; PK_LEN_SECP261K1], address: "".to_string() }; address .public_key - .copy_from_slice(&response_data[..PK_LEN_SECP261K1]); - address.address = str::from_utf8(&response_data[PK_LEN_SECP261K1..]) + .copy_from_slice(&response_data[.. PK_LEN_SECP261K1]); + str::from_utf8(&response_data[PK_LEN_SECP261K1 ..]) .map_err(|_e| LedgerAppError::Utf8)? - .to_owned(); + .clone_into(&mut address.address); Ok(address) } @@ -651,24 +502,16 @@ where .write_u32::(path) .map_err(|_| LedgerAppError::AppSpecific(0, String::from("Invalid ZIP32-path")))?; - let command = APDUCommand { - cla: Self::CLA, - ins: INS_GET_ADDR_SAPLING, - p1, - p2: 0x00, - data: path_data, - }; + let command = APDUCommand { cla: Self::CLA, ins: INS_GET_ADDR_SAPLING, p1, p2: 0x00, data: path_data }; - let response = self.apdu_transport.exchange(&command).await?; + let response = self + .apdu_transport + .exchange(&command) + .await?; match response.error_code() { - Ok(APDUErrorCode::NoError) => {} + Ok(APDUErrorCode::NoError) => {}, Ok(err) => return Err(LedgerAppError::AppSpecific(err as _, err.description())), - Err(err) => { - return Err(LedgerAppError::AppSpecific( - err, - "[APDU_ERROR] Unknown".to_string(), - )) - } + Err(err) => return Err(LedgerAppError::AppSpecific(err, "[APDU_ERROR] Unknown".to_string())), } let response_data = response.data(); @@ -679,23 +522,20 @@ where log::info!("Received response {}", response_data.len()); let mut bytes = [0u8; PK_LEN_SAPLING]; - bytes.copy_from_slice(&response_data[..PK_LEN_SAPLING]); + bytes.copy_from_slice(&response_data[.. PK_LEN_SAPLING]); let addr = PaymentAddress::from_bytes(&bytes).ok_or(LedgerAppError::Crypto)?; - let mut address = AddressShielded { - public_key: addr, - address: "".to_string(), - }; + let mut address = AddressShielded { public_key: addr, address: "".to_string() }; - address.address = str::from_utf8(&response_data[PK_LEN_SAPLING..]) + str::from_utf8(&response_data[PK_LEN_SAPLING ..]) .map_err(|_e| LedgerAppError::Utf8)? - .to_owned(); + .clone_into(&mut address.address); Ok(address) } - ///Get list of diversifiers + /// Get list of diversifiers pub async fn get_div_list( &self, path: u32, @@ -707,24 +547,16 @@ where .map_err(|_| LedgerAppError::AppSpecific(0, String::from("Invalid ZIP32-path")))?; input_data.extend_from_slice(&index[..]); - let command = APDUCommand { - cla: Self::CLA, - ins: INS_GET_DIV_LIST, - p1: 0x00, - p2: 0x00, - data: input_data, - }; + let command = APDUCommand { cla: Self::CLA, ins: INS_GET_DIV_LIST, p1: 0x00, p2: 0x00, data: input_data }; - let response = self.apdu_transport.exchange(&command).await?; + let response = self + .apdu_transport + .exchange(&command) + .await?; match response.error_code() { - Ok(APDUErrorCode::NoError) => {} + Ok(APDUErrorCode::NoError) => {}, Ok(err) => return Err(LedgerAppError::AppSpecific(err as _, err.description())), - Err(err) => { - return Err(LedgerAppError::AppSpecific( - err, - "[APDU_ERROR] Unknown".to_string(), - )) - } + Err(err) => return Err(LedgerAppError::AppSpecific(err, "[APDU_ERROR] Unknown".to_string())), } let response_data = response.data(); @@ -737,7 +569,7 @@ where log::info!("{}", hex::encode(response_data)); let mut list = [0u8; DIV_LIST_SIZE]; - list.copy_from_slice(&response_data[..DIV_LIST_SIZE]); + list.copy_from_slice(&response_data[.. DIV_LIST_SIZE]); Ok(list) } @@ -757,24 +589,16 @@ where input_data.extend_from_slice(&div[..]); - let command = APDUCommand { - cla: Self::CLA, - ins: INS_GET_ADDR_SAPLING_DIV, - p1, - p2: 0x00, - data: input_data, - }; + let command = APDUCommand { cla: Self::CLA, ins: INS_GET_ADDR_SAPLING_DIV, p1, p2: 0x00, data: input_data }; - let response = self.apdu_transport.exchange(&command).await?; + let response = self + .apdu_transport + .exchange(&command) + .await?; match response.error_code() { - Ok(APDUErrorCode::NoError) => {} + Ok(APDUErrorCode::NoError) => {}, Ok(err) => return Err(LedgerAppError::AppSpecific(err as _, err.description())), - Err(err) => { - return Err(LedgerAppError::AppSpecific( - err, - "[APDU_ERROR] Unknown".to_string(), - )) - } + Err(err) => return Err(LedgerAppError::AppSpecific(err, "[APDU_ERROR] Unknown".to_string())), } let response_data = response.data(); @@ -787,46 +611,38 @@ where log::info!("{}", hex::encode(response_data)); let mut addrb = [0u8; PK_LEN_SAPLING]; - addrb.copy_from_slice(&response_data[..PK_LEN_SAPLING]); + addrb.copy_from_slice(&response_data[.. PK_LEN_SAPLING]); let addr = PaymentAddress::from_bytes(&addrb).ok_or(LedgerAppError::Crypto)?; - let mut address = AddressShielded { - public_key: addr, - address: "".to_string(), - }; + let mut address = AddressShielded { public_key: addr, address: "".to_string() }; - address.address = str::from_utf8(&response_data[PK_LEN_SAPLING..]) + str::from_utf8(&response_data[PK_LEN_SAPLING ..]) .map_err(|_e| LedgerAppError::Utf8)? - .to_owned(); + .clone_into(&mut address.address); Ok(address) } /// Retrieves a outgoing viewing key of a sapling key - pub async fn get_ovk(&self, path: u32) -> Result> { + pub async fn get_ovk( + &self, + path: u32, + ) -> Result> { let mut input_data = Vec::with_capacity(4); input_data .write_u32::(path) .map_err(|_| LedgerAppError::AppSpecific(0, String::from("Invalid ZIP32-path")))?; - let command = APDUCommand { - cla: Self::CLA, - ins: INS_GET_OVK, - p1: 0x01, - p2: 0x00, - data: input_data, - }; + let command = APDUCommand { cla: Self::CLA, ins: INS_GET_OVK, p1: 0x01, p2: 0x00, data: input_data }; - let response = self.apdu_transport.exchange(&command).await?; + let response = self + .apdu_transport + .exchange(&command) + .await?; match response.error_code() { - Ok(APDUErrorCode::NoError) => {} + Ok(APDUErrorCode::NoError) => {}, Ok(err) => return Err(LedgerAppError::AppSpecific(err as _, err.description())), - Err(err) => { - return Err(LedgerAppError::AppSpecific( - err, - "[APDU_ERROR] Unknown".to_string(), - )) - } + Err(err) => return Err(LedgerAppError::AppSpecific(err, "[APDU_ERROR] Unknown".to_string())), } let response_data = response.data(); @@ -838,7 +654,7 @@ where log::info!("Received response {}", response_data.len()); let mut bytes = [0u8; OVK_SIZE]; - bytes.copy_from_slice(&response_data[0..OVK_SIZE]); + bytes.copy_from_slice(&response_data[0 .. OVK_SIZE]); let ovk = OutgoingViewingKey(bytes); @@ -846,30 +662,25 @@ where } /// Retrieves a incoming viewing key of a sapling key - pub async fn get_ivk(&self, path: u32) -> Result> { + pub async fn get_ivk( + &self, + path: u32, + ) -> Result> { let mut input_data = Vec::with_capacity(4); input_data .write_u32::(path) .map_err(|_| LedgerAppError::AppSpecific(0, String::from("Invalid ZIP32-path")))?; - let command = APDUCommand { - cla: Self::CLA, - ins: INS_GET_IVK, - p1: 0x01, - p2: 0x00, - data: input_data, - }; + let command = APDUCommand { cla: Self::CLA, ins: INS_GET_IVK, p1: 0x01, p2: 0x00, data: input_data }; - let response = self.apdu_transport.exchange(&command).await?; + let response = self + .apdu_transport + .exchange(&command) + .await?; match response.error_code() { - Ok(APDUErrorCode::NoError) => {} + Ok(APDUErrorCode::NoError) => {}, Ok(err) => return Err(LedgerAppError::AppSpecific(err as _, err.description())), - Err(err) => { - return Err(LedgerAppError::AppSpecific( - err, - "[APDU_ERROR] Unknown".to_string(), - )) - } + Err(err) => return Err(LedgerAppError::AppSpecific(err, "[APDU_ERROR] Unknown".to_string())), } let response_data = response.data(); @@ -881,7 +692,7 @@ where log::info!("Received response {}", response_data.len()); let mut bytes = [0u8; IVK_SIZE]; - bytes.copy_from_slice(&response_data[0..IVK_SIZE]); + bytes.copy_from_slice(&response_data[0 .. IVK_SIZE]); let y = jubjub::Fr::from_bytes(&bytes); if y.is_some().into() { @@ -891,28 +702,20 @@ where } } - ///Get the information needed from ledger to make a shielded spend + /// Get the information needed from ledger to make a shielded spend pub async fn get_spendinfo( - &self, + &self ) -> Result<(ProofGenerationKey, jubjub::Fr, jubjub::Fr), LedgerAppError> { - let command = APDUCommand { - cla: Self::CLA, - ins: INS_EXTRACT_SPEND, - p1: 0x00, - p2: 0x00, - data: vec![], - }; + let command = APDUCommand { cla: Self::CLA, ins: INS_EXTRACT_SPEND, p1: 0x00, p2: 0x00, data: vec![] }; - let response = self.apdu_transport.exchange(&command).await?; + let response = self + .apdu_transport + .exchange(&command) + .await?; match response.error_code() { - Ok(APDUErrorCode::NoError) => {} + Ok(APDUErrorCode::NoError) => {}, Ok(err) => return Err(LedgerAppError::AppSpecific(err as _, err.description())), - Err(err) => { - return Err(LedgerAppError::AppSpecific( - err, - "[APDU_ERROR] Unknown".to_string(), - )) - } + Err(err) => return Err(LedgerAppError::AppSpecific(err, "[APDU_ERROR] Unknown".to_string())), } let response_data = response.data(); @@ -925,73 +728,51 @@ where let bytes = response_data; let mut akb = [0u8; AK_SIZE]; - akb.copy_from_slice(&bytes[0..AK_SIZE]); + akb.copy_from_slice(&bytes[0 .. AK_SIZE]); let mut nskb = [0u8; NSK_SIZE]; - nskb.copy_from_slice(&bytes[AK_SIZE..AK_SIZE + NSK_SIZE]); + nskb.copy_from_slice(&bytes[AK_SIZE .. AK_SIZE + NSK_SIZE]); let ak = jubjub::SubgroupPoint::from_bytes(&akb); let nsk = jubjub::Fr::from_bytes(&nskb); if ak.is_none().into() || nsk.is_none().into() { - return Err(LedgerAppError::AppSpecific( - 0, - String::from("Invalid proofgeneration bytes"), - )); + return Err(LedgerAppError::AppSpecific(0, String::from("Invalid proofgeneration bytes"))); } - let proofkey = ProofGenerationKey { - ak: ak.unwrap(), - nsk: nsk.unwrap(), - }; + let proofkey = ProofGenerationKey { ak: ak.unwrap(), nsk: nsk.unwrap() }; let mut rcvb = [0u8; RCV_SIZE]; - rcvb.copy_from_slice(&bytes[AK_SIZE + NSK_SIZE..AK_SIZE + NSK_SIZE + RCV_SIZE]); + rcvb.copy_from_slice(&bytes[AK_SIZE + NSK_SIZE .. AK_SIZE + NSK_SIZE + RCV_SIZE]); let f = jubjub::Fr::from_bytes(&rcvb); if f.is_none().into() { - return Err(LedgerAppError::AppSpecific( - 0, - String::from("Invalid rcv bytes"), - )); + return Err(LedgerAppError::AppSpecific(0, String::from("Invalid rcv bytes"))); } let rcv = f.unwrap(); let mut alphab = [0u8; ALPHA_SIZE]; - alphab.copy_from_slice(&bytes[AK_SIZE + NSK_SIZE + RCV_SIZE..SPENDDATA_SIZE]); + alphab.copy_from_slice(&bytes[AK_SIZE + NSK_SIZE + RCV_SIZE .. SPENDDATA_SIZE]); let f = jubjub::Fr::from_bytes(&alphab); if f.is_none().into() { - return Err(LedgerAppError::AppSpecific( - 0, - String::from("Invalid rcv bytes"), - )); + return Err(LedgerAppError::AppSpecific(0, String::from("Invalid rcv bytes"))); } let alpha = f.unwrap(); Ok((proofkey, rcv, alpha)) } - ///Get the information needed from ledger to make a shielded output - pub async fn get_outputinfo( - &self, - ) -> Result<(jubjub::Fr, Rseed, Option), LedgerAppError> { - let command = APDUCommand { - cla: Self::CLA, - ins: INS_EXTRACT_OUTPUT, - p1: 0x00, - p2: 0x00, - data: vec![], - }; + /// Get the information needed from ledger to make a shielded output + pub async fn get_outputinfo(&self) -> Result<(jubjub::Fr, Rseed, Option), LedgerAppError> { + let command = APDUCommand { cla: Self::CLA, ins: INS_EXTRACT_OUTPUT, p1: 0x00, p2: 0x00, data: vec![] }; - let response = self.apdu_transport.exchange(&command).await?; + let response = self + .apdu_transport + .exchange(&command) + .await?; match response.error_code() { - Ok(APDUErrorCode::NoError) => {} + Ok(APDUErrorCode::NoError) => {}, Ok(err) => return Err(LedgerAppError::AppSpecific(err as _, err.description())), - Err(err) => { - return Err(LedgerAppError::AppSpecific( - err, - "[APDU_ERROR] Unknown".to_string(), - )) - } + Err(err) => return Err(LedgerAppError::AppSpecific(err, "[APDU_ERROR] Unknown".to_string())), } let response_data = response.data(); @@ -1005,29 +786,29 @@ where let bytes = response_data; let mut rcvb = [0u8; RCV_SIZE]; - rcvb.copy_from_slice(&bytes[0..RCV_SIZE]); + rcvb.copy_from_slice(&bytes[0 .. RCV_SIZE]); let f = jubjub::Fr::from_bytes(&rcvb); if f.is_none().into() { - return Err(LedgerAppError::AppSpecific( - 0, - String::from("Invalid rcv bytes"), - )); + return Err(LedgerAppError::AppSpecific(0, String::from("Invalid rcv bytes"))); } let rcv = f.unwrap(); let mut rseedb = [0u8; RSEED_SIZE]; - rseedb.copy_from_slice(&bytes[RCV_SIZE..RCV_SIZE + RSEED_SIZE]); + rseedb.copy_from_slice(&bytes[RCV_SIZE .. RCV_SIZE + RSEED_SIZE]); let rseed = Rseed::AfterZip212(rseedb); - let hashseed = match bytes.len() { - OUTPUTDATA_HASHSEED_SIZE => { - let mut seed = [0u8; HASHSEED_SIZE]; - seed.copy_from_slice(&bytes[RCV_SIZE + RSEED_SIZE..OUTPUTDATA_HASHSEED_SIZE]); - Some(HashSeed(seed)) - } - _ => None, + + let outputdata_hashseed_size = RCV_SIZE + RSEED_SIZE + HASHSEED_SIZE; + + let hashseed = if bytes.len() == outputdata_hashseed_size { + let mut seed = [0u8; HASHSEED_SIZE]; + seed.copy_from_slice(&bytes[RCV_SIZE + RSEED_SIZE .. outputdata_hashseed_size]); + Some(HashSeed(seed)) + } else { + None }; + Ok((rcv, rseed, hashseed)) } @@ -1043,24 +824,16 @@ where input_data.extend_from_slice(&position.to_le_bytes()); input_data.extend_from_slice(¬e_commitment[..]); - let command = APDUCommand { - cla: Self::CLA, - ins: INS_GET_NF, - p1: 0x01, - p2: 0x00, - data: input_data, - }; + let command = APDUCommand { cla: Self::CLA, ins: INS_GET_NF, p1: 0x01, p2: 0x00, data: input_data }; - let response = self.apdu_transport.exchange(&command).await?; + let response = self + .apdu_transport + .exchange(&command) + .await?; match response.error_code() { - Ok(APDUErrorCode::NoError) => {} + Ok(APDUErrorCode::NoError) => {}, Ok(err) => return Err(LedgerAppError::AppSpecific(err as _, err.description())), - Err(err) => { - return Err(LedgerAppError::AppSpecific( - err, - "[APDU_ERROR] Unknown".to_string(), - )) - } + Err(err) => return Err(LedgerAppError::AppSpecific(err, "[APDU_ERROR] Unknown".to_string())), } let response_data = response.data(); @@ -1072,35 +845,25 @@ where log::info!("Received response {}", response_data.len()); let mut nf_bytes = [0u8; NF_SIZE]; - nf_bytes.copy_from_slice(&response_data[0..NF_SIZE]); + nf_bytes.copy_from_slice(&response_data[0 .. NF_SIZE]); let nf = Nullifier(nf_bytes); Ok(nf) } - ///Get a transparent signature from the ledger - pub async fn get_transparent_signature( - &self, - ) -> Result> { - let command = APDUCommand { - cla: Self::CLA, - ins: INS_EXTRACT_TRANSSIG, - p1: 0x00, - p2: 0x00, - data: vec![], - }; + /// Get a transparent signature from the ledger + pub async fn get_transparent_signature(&self) -> Result> { + let command = APDUCommand { cla: Self::CLA, ins: INS_EXTRACT_TRANSSIG, p1: 0x00, p2: 0x00, data: vec![] }; - let response = self.apdu_transport.exchange(&command).await?; + let response = self + .apdu_transport + .exchange(&command) + .await?; match response.error_code() { - Ok(APDUErrorCode::NoError) => {} + Ok(APDUErrorCode::NoError) => {}, Ok(err) => return Err(LedgerAppError::AppSpecific(err as _, err.description())), - Err(err) => { - return Err(LedgerAppError::AppSpecific( - err, - "[APDU_ERROR] Unknown".to_string(), - )) - } + Err(err) => return Err(LedgerAppError::AppSpecific(err, "[APDU_ERROR] Unknown".to_string())), } let response_data = response.data(); @@ -1111,30 +874,22 @@ where log::info!("Received response {}", response_data.len()); - secp256k1::ecdsa::Signature::from_compact(&response_data[0..SIG_SIZE]) + secp256k1::ecdsa::Signature::from_compact(&response_data[0 .. SIG_SIZE]) .map_err(|_| LedgerAppError::InvalidSignature) } - ///Get a shielded spend signature from the ledger + /// Get a shielded spend signature from the ledger pub async fn get_spend_signature(&self) -> Result> { - let command = APDUCommand { - cla: Self::CLA, - ins: INS_EXTRACT_SPENDSIG, - p1: 0x00, - p2: 0x00, - data: vec![], - }; + let command = APDUCommand { cla: Self::CLA, ins: INS_EXTRACT_SPENDSIG, p1: 0x00, p2: 0x00, data: vec![] }; - let response = self.apdu_transport.exchange(&command).await?; + let response = self + .apdu_transport + .exchange(&command) + .await?; match response.error_code() { - Ok(APDUErrorCode::NoError) => {} + Ok(APDUErrorCode::NoError) => {}, Ok(err) => return Err(LedgerAppError::AppSpecific(err as _, err.description())), - Err(err) => { - return Err(LedgerAppError::AppSpecific( - err, - "[APDU_ERROR] Unknown".to_string(), - )) - } + Err(err) => return Err(LedgerAppError::AppSpecific(err, "[APDU_ERROR] Unknown".to_string())), } let response_data = response.data(); @@ -1145,6 +900,6 @@ where log::info!("Received response {}", response_data.len()); - Signature::read(&response_data[..SIG_SIZE]).map_err(|_| LedgerAppError::InvalidSignature) + Signature::read(&response_data[.. SIG_SIZE]).map_err(|_| LedgerAppError::InvalidSignature) } } diff --git a/ledger-zcash/src/config.rs b/ledger-zcash/src/config.rs new file mode 100644 index 0000000..3c373b6 --- /dev/null +++ b/ledger-zcash/src/config.rs @@ -0,0 +1,101 @@ +/// Application Identifier for Zcash commands +pub const CLA: u8 = 0x85; + +/// Instruction to get Incoming Viewing Key +pub const INS_GET_IVK: u8 = 0xf0; +/// Instruction to get Outgoing Viewing Key +pub const INS_GET_OVK: u8 = 0xf1; +/// Instruction to get Nullifier +pub const INS_GET_NF: u8 = 0xf2; +/// Instruction to initialize a transaction +pub const INS_INIT_TX: u8 = 0xa0; +/// Instruction to extract spend data +pub const INS_EXTRACT_SPEND: u8 = 0xa1; +/// Instruction to extract output data +pub const INS_EXTRACT_OUTPUT: u8 = 0xa2; +/// Instruction to check and sign a transaction +pub const INS_CHECKANDSIGN: u8 = 0xa3; +/// Instruction to extract a spend signature +pub const INS_EXTRACT_SPENDSIG: u8 = 0xa4; +/// Instruction to extract a transaction signature +pub const INS_EXTRACT_TRANSSIG: u8 = 0xa5; +/// Instruction to get a list of diversifiers +pub const INS_GET_DIV_LIST: u8 = 0x09; + +/// Instruction to get a secp256k1 address +pub const INS_GET_ADDR_SECP256K1: u8 = 0x01; +/// Instruction to get a Sapling address +pub const INS_GET_ADDR_SAPLING: u8 = 0x11; +/// Instruction to get a Sapling address with diversifier +pub const INS_GET_ADDR_SAPLING_DIV: u8 = 0x10; + +//////////////////// +//////////////////// +//////////////////// + +/// Length of diversifier index +pub const DIV_INDEX_SIZE: usize = 11; +/// Diversifier length +pub const DIV_SIZE: usize = 11; +/// Number of diversifiers returned by get div list +pub const DIV_LIST_SIZE: usize = 220; + +/// Outgoing Viewing Key size +pub const OVK_SIZE: usize = 32; + +/// Incoming Viewing Key size +pub const IVK_SIZE: usize = 32; + +/// Nullifier size +pub const NF_SIZE: usize = 32; + +/// Note commitment size +pub const NOTE_COMMITMENT_SIZE: usize = 32; + +/// SHA-256 digest size +pub const SHA256_DIGEST_SIZE: usize = 32; + +/// Authorizing Key size +pub const AK_SIZE: usize = 32; + +/// Nullifier Key size +pub const NSK_SIZE: usize = 32; + +/// Alpha size (random scalar for Jubjub) +pub const ALPHA_SIZE: usize = 32; + +/// RCV size (random scalar for value commitment) +pub const RCV_SIZE: usize = 32; + +/// Spend data length: AK (32) + NSK (32) + Alpha(32) + RCV (32) +pub const SPENDDATA_SIZE: usize = AK_SIZE + NSK_SIZE + ALPHA_SIZE + RCV_SIZE; + +/// Rseed size (random seed for note commitment) +pub const RSEED_SIZE: usize = 32; + +/// Hash seed size +pub const HASHSEED_SIZE: usize = 32; + +/// Output data length: RCV (32) + Rseed (32) +pub const OUTPUTDATA_SIZE: usize = RCV_SIZE + RSEED_SIZE; + +/// Public Key Length for secp256k1 +pub const PK_LEN_SECP261K1: usize = 33; + +/// Public Key Length for Sapling +pub const PK_LEN_SAPLING: usize = 43; + +/// Transparent input size: BIP44-path (20) + script (26) + value (8) +pub const T_IN_INPUT_SIZE: usize = 54; + +/// Transparent output size: script (26) + value (8) +pub const T_OUT_INPUT_SIZE: usize = 34; + +/// Shielded spend input size: zip32-path (4) + address (43) + value (8) +pub const S_SPEND_INPUT_SIZE: usize = 55; + +/// Shielded output input size: address (43) + value (8) + memotype (1) + ovk(32) +pub const S_OUT_INPUT_SIZE: usize = 84; + +/// Signature size for transparent and shielded signatures +pub const SIG_SIZE: usize = 64; diff --git a/ledger-zcash/src/lib.rs b/ledger-zcash/src/lib.rs index d44878a..6d87335 100644 --- a/ledger-zcash/src/lib.rs +++ b/ledger-zcash/src/lib.rs @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2022 Zondax GmbH +* (c) 2022-2024 Zondax AG * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,23 +13,32 @@ * See the License for the specific language governing permissions and * limitations under the License. ********************************************************************************/ -//! Support library for Zcash Ledger Nano S/X apps +//! This module provides a support library for Zcash applications on Ledger Nano S and X devices. +//! It includes functionality to handle APDU commands and errors, and to interact with the Zcash blockchain. #![deny(warnings, trivial_casts, trivial_numeric_casts)] #![deny(unused_import_braces, unused_qualifications)] #![deny(missing_docs)] +/// Re-export APDU-related types from the `ledger_transport` crate. pub use ledger_transport::{APDUAnswer, APDUCommand, APDUErrorCode}; +/// Re-export error handling utilities from the `ledger_zondax_generic` crate. pub use ledger_zondax_generic::LedgerAppError; -/// Ledger app +/// Module containing the main functionality of the Ledger app. mod app; +/// Re-export everything from the `app` module. pub use app::*; +/// Module for Zcash-specific functionality. pub mod zcash; -/// Ergonomic transaction builder +/// Module providing an ergonomic interface for building transactions. #[path = "./txbuilder.rs"] pub mod builder; +/// Module containing additional APDU command handling. mod apdu_extra; + +/// Module containing configuration constants for the application. +pub mod config; diff --git a/ledger-zcash/src/txbuilder.rs b/ledger-zcash/src/txbuilder.rs index 1f33aee..7983037 100644 --- a/ledger-zcash/src/txbuilder.rs +++ b/ledger-zcash/src/txbuilder.rs @@ -1,6 +1,25 @@ +/******************************************************************************* +* (c) 2022-2024 Zondax AG +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ use std::convert::TryFrom; +use std::sync::mpsc; -use crate::zcash::primitives::{ +use arrayvec::ArrayVec; +use ledger_zcash_builder::{txbuilder::SaplingMetadata, txprover::HsmTxProver}; +use rand_core::{CryptoRng, RngCore}; +use zcash_primitives::{ consensus::{self, Parameters}, keys::OutgoingViewingKey, legacy::TransparentAddress, @@ -13,17 +32,9 @@ use crate::zcash::primitives::{ Transaction, TxVersion, }, }; -use zcash_hsmbuilder::{txbuilder::SaplingMetadata, txprover::HsmTxProver}; - -use arrayvec::ArrayVec; -use rand_core::{CryptoRng, RngCore}; -use std::sync::mpsc; use zx_bip44::BIP44Path; -use crate::{ - DataInput, DataShieldedOutput, DataShieldedSpend, DataTransparentInput, DataTransparentOutput, - ZcashApp, -}; +use crate::{DataInput, DataShieldedOutput, DataShieldedSpend, DataTransparentInput, DataTransparentOutput, ZcashApp}; /// Ergonomic ZCash transaction builder for HSM #[derive(Default)] @@ -42,15 +53,10 @@ impl TryFrom for Builder { let mut builder = Self::default(); for ti in input.vec_tin.into_iter() { - builder.add_transparent_input( - ti.path, - ti.pk, - ti.prevout, - TxOut { - value: ti.value, - script_pubkey: ti.script, - }, - )?; + builder.add_transparent_input(ti.path, ti.pk, ti.prevout, TxOut { + value: ti.value, + script_pubkey: ti.script, + })?; } for to in input.vec_tout.into_iter() { @@ -85,7 +91,8 @@ pub enum BuilderError { /// Attempted to add too many elements to the transaction /// - /// This is a limitation of the ledger, where currently maximum 5 elements are accepted per type + /// This is a limitation of the ledger, where currently maximum 5 elements + /// are accepted per type #[error("too many elements")] TooManyElements, @@ -163,16 +170,17 @@ impl Builder { utxo: OutPoint, coin: TxOut, ) -> Result<&mut Self, BuilderError> { + log::info!("add_transparent_input"); if coin.value.is_negative() { return Err(BuilderError::InvalidAmount); } let pkh = { use ripemd::{Digest as _, Ripemd160}; - use sha2::{Digest as _, Sha256}; + use sha2::Sha256; let serialized = key.serialize(); - let sha = Sha256::digest(&serialized); + let sha = Sha256::digest(serialized); let mut ripemd = Ripemd160::new(); ripemd.update(&sha[..]); @@ -180,7 +188,7 @@ impl Builder { }; match coin.script_pubkey.address() { - Some(TransparentAddress::PublicKey(hash)) if hash == pkh[..] => {} + Some(TransparentAddress::PublicKey(hash)) if hash == pkh[..] => {}, _ => return Err(BuilderError::InvalidUTXOAddress), } @@ -205,15 +213,13 @@ impl Builder { to: &TransparentAddress, value: Amount, ) -> Result<&mut Self, BuilderError> { + log::info!("add_transparent_output"); if value.is_negative() { return Err(BuilderError::InvalidAmount); } self.transaprent_outputs - .try_push(DataTransparentOutput { - value, - script_pubkey: to.script(), - }) + .try_push(DataTransparentOutput { value, script_pubkey: to.script() }) .map_err(|_| BuilderError::TooManyElements)?; Ok(self) @@ -221,7 +227,8 @@ impl Builder { /// Add a new sapling spend to the transaction /// - /// Performs some checks to ensure as much as possible that the spend is valid + /// Performs some checks to ensure as much as possible that the spend is + /// valid pub fn add_sapling_spend( &mut self, path: u32, @@ -229,7 +236,8 @@ impl Builder { note: Note, merkle_path: MerklePath, ) -> Result<&mut Self, BuilderError> { - //just need to check against the first one (if it exists) + log::info!("add_sapling_spend"); + // just need to check against the first one (if it exists) // as all will be checked against it to all are equal if let Some(spend) = self.sapling_spends.first() { let spend_cmu = Node::new(spend.note.cmu().into()); @@ -239,21 +247,18 @@ impl Builder { let this_root = merkle_path.root(cmu); if this_root != spend_root { + log::error!("Anchor mismatch"); return Err(BuilderError::AnchorMismatch); } } if Amount::from_u64(note.value).is_err() { + log::error!("Invalid Amount"); return Err(BuilderError::InvalidAmount); } self.sapling_spends - .try_push(DataShieldedSpend { - path, - note, - diversifier, - witness: merkle_path, - }) + .try_push(DataShieldedSpend { path, note, diversifier, witness: merkle_path }) .map_err(|_| BuilderError::TooManyElements)?; Ok(self) @@ -261,7 +266,8 @@ impl Builder { /// Add a new sapling output to the transaction /// - /// Not all checks are done here, only those that are possible with the current information + /// Not all checks are done here, only those that are possible with the + /// current information pub fn add_sapling_output( &mut self, ovk: Option, @@ -269,6 +275,7 @@ impl Builder { value: Amount, memo: Option, ) -> Result<&mut Self, BuilderError> { + log::info!("add_sapling_output"); if to.g_d().is_none() { return Err(BuilderError::InvalidAddress); } @@ -278,19 +285,14 @@ impl Builder { } self.sapling_outputs - .try_push(DataShieldedOutput { - address: to, - value, - ovk, - memo, - }) + .try_push(DataShieldedOutput { address: to, value, ovk, memo }) .map_err(|_| BuilderError::TooManyElements)?; Ok(self) } - /// If there are any shielded inputs, always have at least two shielded outputs, - /// padding with dummy outputs if necessary. + /// If there are any shielded inputs, always have at least two shielded + /// outputs, padding with dummy outputs if necessary. /// See /// /// Same applies when we only have 1 output @@ -303,7 +305,7 @@ impl Builder { if !self.sapling_spends.is_empty() || self.sapling_outputs.len() == 1 { let dummies = 2usize.saturating_sub(self.sapling_outputs.len()); - for _ in 0..dummies { + for _ in 0 .. dummies { rv.push(DataShieldedOutput { address: random_payment_address(rng), value: Amount::from_u64(0).unwrap(), @@ -318,15 +320,21 @@ impl Builder { /// Sets the Sapling address to which any change will be sent. /// - /// By default, change is sent to the Sapling address corresponding to the first note - /// being spent (i.e. the first call to [`Builder::add_sapling_spend`]). - pub fn send_change_to(&mut self, ovk: OutgoingViewingKey, to: PaymentAddress) { + /// By default, change is sent to the Sapling address corresponding to the + /// first note being spent (i.e. the first call to + /// [`Builder::add_sapling_spend`]). + pub fn send_change_to( + &mut self, + ovk: OutgoingViewingKey, + to: PaymentAddress, + ) { self.change_address = Some((ovk, to)) } } impl Builder { - /// Calculate the fee according to ZIP-0317 for the given transaction elements + /// Calculate the fee according to ZIP-0317 for the given transaction + /// elements /// /// Note: non-p2pk, joinsplits and orchard are not supported pub fn calculate_zip0317_fee( @@ -348,37 +356,51 @@ impl Builder { Amount::from_u64(rv).unwrap() } - fn calculate_change(&self, fee: Amount) -> Amount { - let tin_values = self.transparent_inputs.iter().map(|tin| tin.value); + fn calculate_change( + &self, + fee: Amount, + ) -> Amount { + let tin_values = self + .transparent_inputs + .iter() + .map(|tin| tin.value); let spends_values = self .sapling_spends .iter() .flat_map(|spend| Amount::from_u64(spend.note.value).ok()); - let tout_amounts = self.transaprent_outputs.iter().map(|tout| tout.value); + let tout_amounts = self + .transaprent_outputs + .iter() + .map(|tout| tout.value); - let soutputs_values = self.sapling_outputs.iter().map(|out| out.value); + let soutputs_values = self + .sapling_outputs + .iter() + .map(|out| out.value); let inputs = tin_values .chain(spends_values) - .try_fold(Amount::zero(), |acc, x| (acc + x)); + .try_fold(Amount::zero(), |acc, x| acc + x); let outputs = tout_amounts .chain(soutputs_values) - .try_fold(Amount::zero(), |acc, x| (acc + x)); + .try_fold(Amount::zero(), |acc, x| acc + x); if let (Some(balance), Some(expense)) = (inputs, outputs) { + log::trace!("balance: {:?}, expense: {:?}, fee: {:?}", balance, expense, fee); (balance - expense - fee).unwrap_or_else(Amount::zero) } else { Amount::zero() } } - /// This takes care of setting up an output for a change address if necessary (and pad sapling outputs) + /// This takes care of setting up an output for a change address if + /// necessary (and pad sapling outputs) /// - /// When a change output is necessary: if there's any leftover currency from summing all - /// the inputs and removing all the outputs and the fee. + /// When a change output is necessary: if there's any leftover currency from + /// summing all the inputs and removing all the outputs and the fee. /// /// Rules for determining change address, in order of preference: /// @@ -399,17 +421,20 @@ impl Builder { if value != Amount::zero() { log::debug!("adding output with change"); - //avoid having a single output (from the change address) when there are no spends - let change_to_sapling = - if !self.sapling_spends.is_empty() && !self.sapling_outputs.is_empty() { - self.change_address - .clone() - .map(|(ovk, addr)| (Some(ovk), addr)) - .or_else(|| self.sapling_spends.get(0).map(|s| (None, s.address()))) - } else { - None - }; - + // avoid having a single output (from the change address) when there are no + // spends + let change_to_sapling = if !self.sapling_spends.is_empty() && !self.sapling_outputs.is_empty() { + self.change_address + .clone() + .map(|(ovk, addr)| (Some(ovk), addr)) + .or_else(|| { + self.sapling_spends + .first() + .map(|s| (None, s.address())) + }) + } else { + None + }; if let Some((ovk, addr)) = change_to_sapling { fee = Self::calculate_zip0317_fee( self.transparent_inputs.len(), @@ -419,12 +444,7 @@ impl Builder { ); let value = self.calculate_change(fee); - let output = DataShieldedOutput { - address: addr, - value, - ovk, - memo: None, - }; + let output = DataShieldedOutput { address: addr, value, ovk, memo: None }; pads.pop(); pads.push(output); } else if let &[tin, ..] = &self.transparent_inputs.as_slice() { @@ -435,14 +455,11 @@ impl Builder { self.sapling_outputs.len() + pads.len(), ); let value = self.calculate_change(fee); - let output = DataTransparentOutput { - value, - script_pubkey: tin.script.clone(), - }; + let output = DataTransparentOutput { value, script_pubkey: tin.script.clone() }; self.transaprent_outputs.push(output); } else { - //if change is <0 we could end up here + // if change is <0 we could end up here return Err(BuilderError::InvalidAmount); } } @@ -454,13 +471,28 @@ impl Builder { Ok(fee) } - fn into_data_input(self, fee: Amount) -> DataInput { + fn into_data_input( + self, + fee: Amount, + ) -> DataInput { DataInput { txfee: fee.into(), - vec_tin: self.transparent_inputs.into_iter().collect(), - vec_tout: self.transaprent_outputs.into_iter().collect(), - vec_sspend: self.sapling_spends.into_iter().collect(), - vec_soutput: self.sapling_outputs.into_iter().collect(), + vec_tin: self + .transparent_inputs + .into_iter() + .collect(), + vec_tout: self + .transaprent_outputs + .into_iter() + .collect(), + vec_sspend: self + .sapling_spends + .into_iter() + .collect(), + vec_soutput: self + .sapling_outputs + .into_iter() + .collect(), } } @@ -469,9 +501,9 @@ impl Builder { /// `height` is the target block height for inclusion on chain /// /// `branch` must be valid for the block height that this transaction is - /// targeting. An invalid `consensus_branch_id` will *not* result in an error from - /// this function, and instead will generate a transaction that will be rejected by - /// the network. + /// targeting. An invalid `consensus_branch_id` will *not* result in an + /// error from this function, and instead will generate a transaction + /// that will be rejected by the network. #[allow(clippy::too_many_arguments)] pub async fn build( mut self, @@ -501,45 +533,32 @@ impl Builder { let fee = self.setup_change_and_pad_outputs(rng, fee)?; let mut hsmbuilder = - zcash_hsmbuilder::txbuilder::Builder::new_with_fee_rng(params, height, rng, fee.into()); + ledger_zcash_builder::txbuilder::Builder::new_with_fee_rng(params, height, rng, fee.into()); let input = self.into_data_input(fee); - log::info!("Bulding TX with: {:?}", &input); + log::trace!("Building TX with: {:?}", &input); app.init_tx(input.to_inittx_data()) .await .map_err(|_| BuilderError::UnableToInitializeTx)?; - let DataInput { - txfee: _, - vec_tin, - vec_tout, - vec_sspend, - vec_soutput, - } = input; + let DataInput { txfee: _, vec_tin, vec_tout, vec_sspend, vec_soutput } = input; let num_transparent_inputs = vec_tin.len(); let num_sapling_spends = vec_sspend.len(); - /* Feed the builder with the various parts, - * retrieving data from the ledger device */ + // Feed the builder with the various parts, + // retrieving data from the ledger device for info in vec_tin.into_iter() { hsmbuilder - .add_transparent_input( - info.pk, - info.prevout, - TxOut { - value: info.value, - script_pubkey: info.script, - }, - ) - //verified the inputs when we added it + .add_transparent_input(info.pk, info.prevout, TxOut { value: info.value, script_pubkey: info.script }) + // verified the inputs when we added it .unwrap(); } for info in vec_tout.into_iter() { hsmbuilder .add_transparent_output(info.script_pubkey, info.value) - //checked when we added these to the builder + // checked when we added these to the builder .unwrap(); } @@ -550,15 +569,8 @@ impl Builder { .map_err(|_| BuilderError::UnableToRetrieveSpendInfo(i))?; hsmbuilder - .add_sapling_spend( - info.diversifier, - info.note, - info.witness, - alpha, - proofkey, - rcv, - ) - //parameters checked before + .add_sapling_spend(info.diversifier, info.note, info.witness, alpha, proofkey, rcv) + // parameters checked before .unwrap(); } @@ -573,16 +585,8 @@ impl Builder { } hsmbuilder - .add_sapling_output( - info.ovk, - info.address, - info.value, - info.memo, - rcv, - rseed, - hash_seed, - ) - //parameters checked before + .add_sapling_output(info.ovk, info.address, info.value, info.memo, rcv, rseed, hash_seed) + // parameters checked before .unwrap(); } @@ -599,8 +603,8 @@ impl Builder { let mut tsigs = Vec::with_capacity(num_transparent_inputs); let mut zsigs = Vec::with_capacity(num_sapling_spends); - //retrieve signatures - for i in 0..num_transparent_inputs { + // retrieve signatures + for i in 0 .. num_transparent_inputs { let sig = app .get_transparent_signature() .await @@ -608,7 +612,7 @@ impl Builder { tsigs.push(sig); } - for i in 0..num_sapling_spends { + for i in 0 .. num_sapling_spends { let sig = app .get_spend_signature() .await @@ -616,7 +620,7 @@ impl Builder { zsigs.push(sig); } - //apply them in the builder + // apply them in the builder let hsmbuilder = hsmbuilder .add_signatures_spend(zsigs) .map_err(|_| BuilderError::UnableToApplySaplingSigs)?; diff --git a/ledger-zcash/src/zcash.rs b/ledger-zcash/src/zcash.rs index 3eeae0f..ffefa0e 100644 --- a/ledger-zcash/src/zcash.rs +++ b/ledger-zcash/src/zcash.rs @@ -1,27 +1,33 @@ +/******************************************************************************* +* (c) 2022-2024 Zondax AG +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ //! Wrapper over zcash crates and zecwallet fork of the crates //! //! Use this instead of importing things from zcash crates directly!! -cfg_if::cfg_if! { - if #[cfg(all(feature = "zecwallet-compat", feature = "normal-zcash"))] { - compile_error!("Only one feature should be enabled between 'zecwallet-compat' and 'normal-zcash'!"); - } else if #[cfg(feature = "zecwallet-compat")] { - pub use zecw_primitives as primitives; - } else if #[cfg(feature = "normal-zcash")] { - pub use zcash_primitives as primitives; - } else { - compile_error!("One feature should be enabled between 'zecwallet-compat' and 'normal-zcash'!"); - } -} - #[allow(dead_code)] -//actually used in `DataShieldedOutput` debug implementation -/// The payment_address_bytes_fmt function formats the bytes of the payment address for debug information +// actually used in `DataShieldedOutput` debug implementation +/// The payment_address_bytes_fmt function formats the bytes of the payment +/// address for debug information pub fn payment_address_bytes_fmt( - this: &primitives::sapling::PaymentAddress, + this: &zcash_primitives::sapling::PaymentAddress, f: &mut std::fmt::Formatter, ) -> std::fmt::Result { let bytes = this.to_bytes(); - f.debug_tuple("PaymentAddress").field(&bytes).finish() + f.debug_tuple("PaymentAddress") + .field(&bytes) + .finish() } diff --git a/ledger-zcash/tests/integration_test.rs b/ledger-zcash/tests/integration_test.rs index 89f2126..2f94ea9 100644 --- a/ledger-zcash/tests/integration_test.rs +++ b/ledger-zcash/tests/integration_test.rs @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2018-2022 Zondax GmbH +* (c) 2022-2024 Zondax AG * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,15 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. ********************************************************************************/ -use serial_test::serial; - use env_logger::Env; +use serial_test::serial; use zx_bip44::BIP44Path; #[path = "../src/zcash.rs"] mod zcash; -use zcash::primitives::{ +use ledger_transport_hid::{hidapi::HidApi, TransportNativeHID}; +use ledger_zcash::config::{PK_LEN_SAPLING, PK_LEN_SECP261K1}; +use ledger_zcash::*; +use zcash_primitives::{ consensus::{self, TestNetwork}, keys::OutgoingViewingKey, legacy::Script, @@ -30,9 +32,6 @@ use zcash::primitives::{ transaction::components::{Amount, OutPoint}, }; -use ledger_transport_hid::{hidapi::HidApi, TransportNativeHID}; -use ledger_zcash::*; - lazy_static::lazy_static! { static ref HIDAPI: HidApi = HidApi::new().expect("Failed to create Hidapi"); } @@ -76,10 +75,7 @@ async fn get_key_ivk() { let ivk = hex::encode(resp.to_bytes()); - assert_eq!( - ivk, - "6dfadf175921e6fbfa093c8f7c704a0bdb07328474f56c833dfcfa5301082d03" - ); + assert_eq!(ivk, "6dfadf175921e6fbfa093c8f7c704a0bdb07328474f56c833dfcfa5301082d03"); } #[tokio::test] @@ -95,10 +91,7 @@ async fn get_key_ovk() { let ovk = hex::encode(resp.0); - assert_eq!( - ovk, - "6fc01eaa665e03a53c1e033ed0d77b670cf075ede4ada769997a2ed2ec225fca" - ); + assert_eq!(ovk, "6fc01eaa665e03a53c1e033ed0d77b670cf075ede4ada769997a2ed2ec225fca"); } #[tokio::test] @@ -113,16 +106,19 @@ async fn get_nf() { let pos: u64 = 2578461368; let cm: [u8; 32] = [ - 33, 201, 70, 152, 202, 50, 75, 76, 186, 206, 41, 29, 39, 171, 182, 138, 10, 175, 39, 55, - 220, 69, 86, 84, 28, 127, 205, 232, 206, 17, 221, 232, + 33, 201, 70, 152, 202, 50, 75, 76, 186, 206, 41, 29, 39, 171, 182, 138, 10, 175, 39, 55, 220, 69, 86, 84, 28, + 127, 205, 232, 206, 17, 221, 232, ]; - let resp = app.get_nullifier(path, pos, &cm).await.unwrap(); + let resp = app + .get_nullifier(path, pos, &cm) + .await + .unwrap(); let vec_nf = resp.to_vec(); - let expected_nf: Vec = ([ - 37, 241, 242, 207, 94, 44, 43, 195, 29, 7, 182, 111, 77, 84, 240, 144, 173, 137, 177, 152, - 137, 63, 18, 173, 174, 68, 125, 223, 132, 226, 20, 90, - ]) + let expected_nf: Vec = [ + 37, 241, 242, 207, 94, 44, 43, 195, 29, 7, 182, 111, 77, 84, 240, 144, 173, 137, 177, 152, 137, 63, 18, 173, + 174, 68, 125, 223, 132, 226, 20, 90, + ] .to_vec(); assert_eq!(vec_nf, expected_nf); } @@ -135,18 +131,18 @@ async fn address_unshielded() { let app = ZcashApp::new(TransportNativeHID::new(&HIDAPI).expect("Unable to create transport")); let path = BIP44Path::from_string("m/44'/133'/0'/0/0").unwrap(); - let resp = app.get_address_unshielded(&path, false).await.unwrap(); + let resp = app + .get_address_unshielded(&path, false) + .await + .unwrap(); assert_eq!(resp.public_key.len(), PK_LEN_SECP261K1); - let pkhex = hex::encode(&resp.public_key[..]); - println!("Public Key {:?}", pkhex); + let pk_hex = hex::encode(&resp.public_key[..]); + println!("Public Key {:?}", pk_hex); println!("Address address {:?}", resp.address); - assert_eq!( - pkhex, - "0239511e41d70cf95ead40aae910cb3b6790f561f9077aaa1a8e091eeb78a8f26a" - ); + assert_eq!(pk_hex, "0239511e41d70cf95ead40aae910cb3b6790f561f9077aaa1a8e091eeb78a8f26a"); assert_eq!(resp.address, "t1csLd5XeD1MyNM8gW8JfCzy8BYyidj6aCV"); } @@ -158,22 +154,19 @@ async fn address_shielded() { let app = ZcashApp::new(TransportNativeHID::new(&HIDAPI).expect("Unable to create transport")); let path = 1000; - let resp = app.get_address_shielded(path, false).await.unwrap(); + let resp = app + .get_address_shielded(path, false) + .await + .unwrap(); assert_eq!(resp.public_key.to_bytes().len(), PK_LEN_SAPLING); - let pkhex = hex::encode(resp.public_key.to_bytes()); - println!("Public Key {:?}", pkhex); + let pk_hex = hex::encode(resp.public_key.to_bytes()); + println!("Public Key {:?}", pk_hex); println!("Address address {:?}", resp.address); - assert_eq!( - pkhex, - "c69e979c6763c1b09238dc6bd5dcbf35360df95dcadf8c0fa25dcbedaaf6057538b812d06656726ea27667" - ); - assert_eq!( - resp.address, - "zs1c60f08r8v0qmpy3cm34ath9lx5mqm72aet0ccrazth97m2hkq46n3wqj6pn9vunw5fmxwclltd3" - ); + assert_eq!(pk_hex, "c69e979c6763c1b09238dc6bd5dcbf35360df95dcadf8c0fa25dcbedaaf6057538b812d06656726ea27667"); + assert_eq!(resp.address, "zs1c60f08r8v0qmpy3cm34ath9lx5mqm72aet0ccrazth97m2hkq46n3wqj6pn9vunw5fmxwclltd3"); } #[tokio::test] @@ -184,18 +177,18 @@ async fn show_address_unshielded() { let app = ZcashApp::new(TransportNativeHID::new(&HIDAPI).expect("Unable to create transport")); let path = BIP44Path::from_string("m/44'/133'/0'/0/0").unwrap(); - let resp = app.get_address_unshielded(&path, true).await.unwrap(); + let resp = app + .get_address_unshielded(&path, true) + .await + .unwrap(); assert_eq!(resp.public_key.len(), PK_LEN_SECP261K1); - let pkhex = hex::encode(&resp.public_key[..]); - println!("Public Key {:?}", pkhex); + let pk_hex = hex::encode(&resp.public_key[..]); + println!("Public Key {:?}", pk_hex); println!("Address address {:?}", resp.address); - assert_eq!( - pkhex, - "0239511e41d70cf95ead40aae910cb3b6790f561f9077aaa1a8e091eeb78a8f26a" - ); + assert_eq!(pk_hex, "0239511e41d70cf95ead40aae910cb3b6790f561f9077aaa1a8e091eeb78a8f26a"); assert_eq!(resp.address, "t1csLd5XeD1MyNM8gW8JfCzy8BYyidj6aCV"); } @@ -207,22 +200,19 @@ async fn show_address_shielded() { let app = ZcashApp::new(TransportNativeHID::new(&HIDAPI).expect("Unable to create transport")); let path = 1000; - let resp = app.get_address_shielded(path, true).await.unwrap(); + let resp = app + .get_address_shielded(path, true) + .await + .unwrap(); assert_eq!(resp.public_key.to_bytes().len(), PK_LEN_SAPLING); - let pkhex = hex::encode(resp.public_key.to_bytes()); - println!("Public Key {:?}", pkhex); + let pk_hex = hex::encode(resp.public_key.to_bytes()); + println!("Public Key {:?}", pk_hex); println!("Address address {:?}", resp.address); - assert_eq!( - pkhex, - "c69e979c6763c1b09238dc6bd5dcbf35360df95dcadf8c0fa25dcbedaaf6057538b812d06656726ea27667" - ); - assert_eq!( - resp.address, - "zs1c60f08r8v0qmpy3cm34ath9lx5mqm72aet0ccrazth97m2hkq46n3wqj6pn9vunw5fmxwclltd3" - ); + assert_eq!(pk_hex, "c69e979c6763c1b09238dc6bd5dcbf35360df95dcadf8c0fa25dcbedaaf6057538b812d06656726ea27667"); + assert_eq!(resp.address, "zs1c60f08r8v0qmpy3cm34ath9lx5mqm72aet0ccrazth97m2hkq46n3wqj6pn9vunw5fmxwclltd3"); } #[tokio::test] @@ -234,13 +224,12 @@ async fn get_div_list() { let startindex: [u8; 11] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; let path = 1000; - let r = app.get_div_list(path, &startindex).await; + let r = app + .get_div_list(path, &startindex) + .await; assert!(r.is_ok()); let bytes = r.unwrap(); - assert_eq!( - bytes[22..33], - [198, 158, 151, 156, 103, 99, 193, 176, 146, 56, 220] - ); + assert_eq!(bytes[22 .. 33], [198, 158, 151, 156, 103, 99, 193, 176, 146, 56, 220]); } #[tokio::test] @@ -253,21 +242,17 @@ async fn get_addr_with_div() { let div: [u8; 11] = [198, 158, 151, 156, 103, 99, 193, 176, 146, 56, 220]; let path = 1000; - let resp = app.get_address_shielded_with_div(path, &div, true).await; + let resp = app + .get_address_shielded_with_div(path, &div, true) + .await; assert!(resp.is_ok()); let resp = resp.unwrap(); - let pkhex = hex::encode(resp.public_key.to_bytes()); - println!("Public Key {:?}", pkhex); + let pk_hex = hex::encode(resp.public_key.to_bytes()); + println!("Public Key {:?}", pk_hex); println!("Address address {:?}", resp.address); - assert_eq!( - pkhex, - "c69e979c6763c1b09238dc6bd5dcbf35360df95dcadf8c0fa25dcbedaaf6057538b812d06656726ea27667" - ); - assert_eq!( - resp.address, - "zs1c60f08r8v0qmpy3cm34ath9lx5mqm72aet0ccrazth97m2hkq46n3wqj6pn9vunw5fmxwclltd3" - ); + assert_eq!(pk_hex, "c69e979c6763c1b09238dc6bd5dcbf35360df95dcadf8c0fa25dcbedaaf6057538b812d06656726ea27667"); + assert_eq!(resp.address, "zs1c60f08r8v0qmpy3cm34ath9lx5mqm72aet0ccrazth97m2hkq46n3wqj6pn9vunw5fmxwclltd3"); } #[tokio::test] @@ -278,9 +263,8 @@ async fn do_full_transaction_shieldedonly() { let app = ZcashApp::new(TransportNativeHID::new(&HIDAPI).expect("Unable to create transport")); let addr = PaymentAddress::from_bytes(&[ - 198, 158, 151, 156, 103, 99, 193, 176, 146, 56, 220, 107, 213, 220, 191, 53, 54, 13, 249, - 93, 202, 223, 140, 15, 162, 93, 203, 237, 170, 246, 5, 117, 56, 184, 18, 208, 102, 86, 114, - 110, 162, 118, 103, + 198, 158, 151, 156, 103, 99, 193, 176, 146, 56, 220, 107, 213, 220, 191, 53, 54, 13, 249, 93, 202, 223, 140, + 15, 162, 93, 203, 237, 170, 246, 5, 117, 56, 184, 18, 208, 102, 86, 114, 110, 162, 118, 103, ]) .unwrap(); @@ -323,9 +307,8 @@ async fn do_full_transaction_shieldedonly() { let output1 = DataShieldedOutput { value: Amount::from_u64(60000).unwrap(), address: PaymentAddress::from_bytes(&[ - 21, 234, 231, 0, 224, 30, 36, 226, 19, 125, 85, 77, 103, 187, 13, 166, 78, 238, 11, - 241, 194, 195, 146, 197, 241, 23, 58, 151, 155, 174, 184, 153, 102, 56, 8, 205, 34, - 237, 141, 242, 117, 102, 204, + 21, 234, 231, 0, 224, 30, 36, 226, 19, 125, 85, 77, 103, 187, 13, 166, 78, 238, 11, 241, 194, 195, 146, + 197, 241, 23, 58, 151, 155, 174, 184, 153, 102, 56, 8, 205, 34, 237, 141, 242, 117, 102, 204, ]) .unwrap(), ovk: None, @@ -339,14 +322,13 @@ async fn do_full_transaction_shieldedonly() { let output2 = DataShieldedOutput { value: Amount::from_u64(change_amount).unwrap(), address: PaymentAddress::from_bytes(&[ - 198, 158, 151, 156, 103, 99, 193, 176, 146, 56, 220, 107, 213, 220, 191, 53, 54, 13, - 249, 93, 202, 223, 140, 15, 162, 93, 203, 237, 170, 246, 5, 117, 56, 184, 18, 208, 102, - 86, 114, 110, 162, 118, 103, + 198, 158, 151, 156, 103, 99, 193, 176, 146, 56, 220, 107, 213, 220, 191, 53, 54, 13, 249, 93, 202, 223, + 140, 15, 162, 93, 203, 237, 170, 246, 5, 117, 56, 184, 18, 208, 102, 86, 114, 110, 162, 118, 103, ]) .unwrap(), ovk: Some(OutgoingViewingKey([ - 111, 192, 30, 170, 102, 94, 3, 165, 60, 30, 3, 62, 208, 215, 123, 103, 12, 240, 117, - 237, 228, 173, 167, 105, 153, 122, 46, 210, 236, 34, 95, 202, + 111, 192, 30, 170, 102, 94, 3, 165, 60, 30, 3, 62, 208, 215, 123, 103, 12, 240, 117, 237, 228, 173, 167, + 105, 153, 122, 46, 210, 236, 34, 95, 202, ])), memo: None, }; @@ -387,15 +369,12 @@ async fn do_full_transaction_combinedshieldtransparent() { let tout1 = DataTransparentOutput { value: Amount::from_u64(10000).unwrap(), - script_pubkey: Script( - hex::decode("76a914000000000000000000000000000000000000000088ac").unwrap(), - ), + script_pubkey: Script(hex::decode("76a914000000000000000000000000000000000000000088ac").unwrap()), }; let address = PaymentAddress::from_bytes(&[ - 198, 158, 151, 156, 103, 99, 193, 176, 146, 56, 220, 107, 213, 220, 191, 53, 54, 13, 249, - 93, 202, 223, 140, 15, 162, 93, 203, 237, 170, 246, 5, 117, 56, 184, 18, 208, 102, 86, 114, - 110, 162, 118, 103, + 198, 158, 151, 156, 103, 99, 193, 176, 146, 56, 220, 107, 213, 220, 191, 53, 54, 13, 249, 93, 202, 223, 140, + 15, 162, 93, 203, 237, 170, 246, 5, 117, 56, 184, 18, 208, 102, 86, 114, 110, 162, 118, 103, ]) .unwrap(); let d = *address.diversifier(); @@ -423,9 +402,8 @@ async fn do_full_transaction_combinedshieldtransparent() { let output1 = DataShieldedOutput { value: Amount::from_u64(60000).unwrap(), address: PaymentAddress::from_bytes(&[ - 21, 234, 231, 0, 224, 30, 36, 226, 19, 125, 85, 77, 103, 187, 13, 166, 78, 238, 11, - 241, 194, 195, 146, 197, 241, 23, 58, 151, 155, 174, 184, 153, 102, 56, 8, 205, 34, - 237, 141, 242, 117, 102, 204, + 21, 234, 231, 0, 224, 30, 36, 226, 19, 125, 85, 77, 103, 187, 13, 166, 78, 238, 11, 241, 194, 195, 146, + 197, 241, 23, 58, 151, 155, 174, 184, 153, 102, 56, 8, 205, 34, 237, 141, 242, 117, 102, 204, ]) .unwrap(), ovk: None, @@ -435,23 +413,19 @@ async fn do_full_transaction_combinedshieldtransparent() { let fee = 1000; let txfee = Amount::from_u64(fee).unwrap(); - let change_amount = Amount::from_u64(spend1.note.value).unwrap() + tin1.value - - tout1.value - - output1.value - - txfee; + let change_amount = Amount::from_u64(spend1.note.value).unwrap() + tin1.value - tout1.value - output1.value - txfee; let change_amount = change_amount.unwrap(); let output2 = DataShieldedOutput { value: change_amount, address: PaymentAddress::from_bytes(&[ - 198, 158, 151, 156, 103, 99, 193, 176, 146, 56, 220, 107, 213, 220, 191, 53, 54, 13, - 249, 93, 202, 223, 140, 15, 162, 93, 203, 237, 170, 246, 5, 117, 56, 184, 18, 208, 102, - 86, 114, 110, 162, 118, 103, + 198, 158, 151, 156, 103, 99, 193, 176, 146, 56, 220, 107, 213, 220, 191, 53, 54, 13, 249, 93, 202, 223, + 140, 15, 162, 93, 203, 237, 170, 246, 5, 117, 56, 184, 18, 208, 102, 86, 114, 110, 162, 118, 103, ]) .unwrap(), ovk: Some(OutgoingViewingKey([ - 111, 192, 30, 170, 102, 94, 3, 165, 60, 30, 3, 62, 208, 215, 123, 103, 12, 240, 117, - 237, 228, 173, 167, 105, 153, 122, 46, 210, 236, 34, 95, 202, + 111, 192, 30, 170, 102, 94, 3, 165, 60, 30, 3, 62, 208, 215, 123, 103, 12, 240, 117, 237, 228, 173, 167, + 105, 153, 122, 46, 210, 236, 34, 95, 202, ])), memo: None, }; @@ -505,9 +479,7 @@ async fn do_full_transaction_transparentonly() { let tout1 = DataTransparentOutput { value: Amount::from_u64(70000).unwrap(), - script_pubkey: Script( - hex::decode("76a914000000000000000000000000000000000000000088ac").unwrap(), - ), + script_pubkey: Script(hex::decode("76a914000000000000000000000000000000000000000088ac").unwrap()), }; let fee = 1000; @@ -518,9 +490,7 @@ async fn do_full_transaction_transparentonly() { let tout2 = DataTransparentOutput { value: change_amount, - script_pubkey: Script( - hex::decode("76a9140f71709c4b828df00f93d20aa2c34ae987195b3388ac").unwrap(), - ), + script_pubkey: Script(hex::decode("76a9140f71709c4b828df00f93d20aa2c34ae987195b3388ac").unwrap()), }; let input = DataInput { diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..4e425a3 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,38 @@ +# rustfmt.toml example for strict formatting + +# General +edition = "2021" # Target edition of Rust +max_width = 120 # Max width of each line +hard_tabs = false # Use spaces instead of tabs +tab_spaces = 4 # Number of spaces per tab +newline_style = "Unix" # Line ending style +use_small_heuristics = "Max" # Be aggressive with formatting small items + +# Imports +reorder_imports = true # Reorder import statements +reorder_modules = true # Reorder module declarations +group_imports = "StdExternalCrate" # Group imports by type + +# Spaces around punctuation +spaces_around_ranges = true # Spaces around range operators for readability +space_before_colon = false # No space before colons +space_after_colon = true # Space after colons + +# Blank lines +blank_lines_upper_bound = 1 # Max blank lines in a row +blank_lines_lower_bound = 0 # Min blank lines in a row + +# Control flow +brace_style = "SameLineWhere" # Opening brace on the same line +control_brace_style = "AlwaysSameLine" # Control flow braces on the same line + +# Function calls and definitions +fn_params_layout = "Vertical" # Function arguments layout to favor visual alignment + +# Structs, enums, and unions +struct_variant_width = 18 # Max width of struct variants before falling back to vertical formatting, corrected to integer + +# Misc +match_block_trailing_comma = true # Trailing comma in match blocks for easier editing +chain_width = 40 # Max width for a chain of method calls before breaking to enhance readability +overflow_delimited_expr = true # Allow certain expressions to overflow delimiters diff --git a/zcash-hsmbuilder/Cargo.toml b/zcash-hsmbuilder/Cargo.toml deleted file mode 100644 index f5ef2ac..0000000 --- a/zcash-hsmbuilder/Cargo.toml +++ /dev/null @@ -1,59 +0,0 @@ -[package] -name = "zcash-hsmbuilder" -description = "Library to build transactions for HSM apps" -version = "0.4.0" -license = "Apache-2.0" -authors = ["Zondax GmbH "] -homepage = "https://github.com/Zondax/ledger-zcash-rs" -repository = "https://github.com/Zondax/ledger-zcash-rs" -readme = "README.md" -categories = ["authentication", "cryptography"] -keywords = ["ledger", "nano", "apdu", "zcash"] -edition = "2018" -autobenches = false - -[lib] -name = "zcash_hsmbuilder" - -[features] -default = ["normal-zcash"] -normal-zcash = [ "zcash_primitives", "zcash_proofs" ] -zecwallet-compat = [ "zecw_primitives", "zecw_proofs", "zecw_note_encryption" ] -#local tx prover features -bundled-prover = ["wagyu-zcash-parameters"] -local-prover = [] - -[dependencies] -bellman = { version = "0.13", default-features = false, features = ["groth16"] } -blake2b_simd = "1" -bls12_381 = { version = "0.7" } -byteorder = "1.4.3" -cfg-if = "1.0.0" -chacha20poly1305 = "0.9" -ff = "0.12" -group = "0.12" -hex = { version = "0.4", default-features = false } -jubjub = { version = "0.9", default-features = false } -lazy_static = "1" -pairing = { version = "0.22" } -rand = { version = "0.8", default-features = false } -rand_core = "0.6" -ripemd = "0.1" -secp256k1 = { version = "0.21" } -sha2 = "0.9" -serde_derive = "1" -serde = { version = "1", features = ["derive"] } - -#zcash -wagyu-zcash-parameters = { version = "0.2", optional = true } -zcash_primitives = { version = "0.6", features = ["transparent-inputs"], optional = true } -zcash_proofs = { version = "0.6", features = ["multicore"], optional = true } -zcash_note_encryption = { version = "0.1", features = ["pre-zip-212"] } - -#zecwallet-compat -zecw_primitives = { git = "https://github.com/adityapk00/librustzcash", rev = "7183acd2fe12ebf201cae5b871166e356273c481", features = ["transparent-inputs"], optional = true, package = "zcash_primitives" } -zecw_proofs = { git = "https://github.com/adityapk00/librustzcash", rev = "7183acd2fe12ebf201cae5b871166e356273c481", features = ["multicore"], optional = true, package = "zcash_proofs" } -zecw_note_encryption = { git = "https://github.com/adityapk00/librustzcash", rev = "7183acd2fe12ebf201cae5b871166e356273c481", features = ["pre-zip-212"], optional = true, package = "zcash_note_encryption" } -tokio = { version = "1.6", features = ["sync"] } -educe = "0.4.19" -log = "0.4.17" diff --git a/zcash-hsmbuilder/src/errors.rs b/zcash-hsmbuilder/src/errors.rs deleted file mode 100644 index e9cebee..0000000 --- a/zcash-hsmbuilder/src/errors.rs +++ /dev/null @@ -1,99 +0,0 @@ -use std::error; -use std::fmt; - -#[derive(Debug, PartialEq)] -pub enum Error { - AnchorMismatch, - BindingSig, - ChangeIsNegative, - InvalidAddress, - InvalidAddressFormat, - InvalidAddressHash, - InvalidAmount, - NoChangeAddress, - SpendProof, - MissingSpendSig, - SpendSig, - InvalidSpendSig, - NoSpendSig, - TranspararentSig, - Finalization, - MinShieldedOuputs, - BuilderNoKeys, - ReadWriteError, - InvalidOVKHashSeed, - AlreadyAuthorized, - Unauthorized, - UnknownAuthorization, -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Error::AnchorMismatch => { - write!(f, "Anchor mismatch (anchors for all spends must be equal)") - } - Error::BindingSig => write!(f, "Failed to create bindingSig"), - Error::ChangeIsNegative => write!(f, "Change is negative"), - Error::InvalidAddress => write!(f, "Invalid address"), - Error::InvalidAmount => write!(f, "Invalid amount"), - Error::NoChangeAddress => { - write!(f, "No change address specified or discoverable") - } - Error::SpendProof => { - write!(f, "Failed to create Sapling spend proof") - } - Error::MissingSpendSig => { - write!(f, "Missing Sapling spend signature(s)") - } - Error::SpendSig => { - write!(f, "Failed to get Sapling spend signature") - } - Error::InvalidSpendSig => { - write!(f, "Sapling spend signature failed to verify") - } - Error::NoSpendSig => { - write!(f, "No Sapling spend signatures") - } - Error::InvalidAddressFormat => { - write!(f, "Incorrect format of address") - } - Error::InvalidAddressHash => write!(f, "Incorrect hash of address"), - Error::TranspararentSig => { - write!(f, "Failed to sign transparent inputs") - } - Error::Finalization => { - write!(f, "Failed to build complete transaction") - } - Error::MinShieldedOuputs => { - write!(f, "Not enough shielded outputs for transaction") - } - Error::BuilderNoKeys => { - write!(f, "Builder does not have any keys set") - } - Error::ReadWriteError => { - write!(f, "Error writing/reading bytes to/from vector") - } - Error::InvalidOVKHashSeed => { - write!(f, "Error: either OVK or hashseed should be some") - } - Error::AlreadyAuthorized => { - write!(f, "Error: operation not available after authorization") - } - Error::Unauthorized => { - write!(f, "Error: operation not available without authorization") - } - Error::UnknownAuthorization => { - write!(f, "Error: authorization status unknown") - } - } - } -} - -impl error::Error for Error {} - -impl From for Error { - fn from(e: std::io::Error) -> Error { - Error::ReadWriteError - } -} diff --git a/zcash-hsmbuilder/src/lib.rs b/zcash-hsmbuilder/src/lib.rs deleted file mode 100644 index 0eb321a..0000000 --- a/zcash-hsmbuilder/src/lib.rs +++ /dev/null @@ -1,45 +0,0 @@ -#![allow( - dead_code, - unused_imports, - unused_mut, - unused_variables, - clippy::too_many_arguments, - clippy::result_unit_err, - deprecated -)] - -use blake2b_simd::Params as Blake2bParams; -use group::{cofactor::CofactorCurveAffine, GroupEncoding}; -use jubjub::AffinePoint; -use rand::RngCore; -use rand_core::OsRng; -use zcash::primitives::{ - consensus::{self, Parameters, TestNetwork}, - keys::OutgoingViewingKey, - legacy::Script, - memo::MemoBytes as Memo, - merkle_tree::{IncrementalWitness, MerklePath}, - sapling::{redjubjub::Signature, Node, PaymentAddress, ProofGenerationKey, Rseed}, - transaction::{ - components::{Amount, OutPoint, TxOut}, - Transaction, - }, -}; - -pub(crate) mod zcash; - -use data::*; -use errors::Error; -use txbuilder::SaplingMetadata; - -mod prover; - -pub mod errors; - -pub mod data; -pub mod txbuilder; -pub mod txprover; - -// Re exports -pub use crate::txbuilder::{hsmauth, Builder}; -pub use crate::txprover::LocalTxProver; diff --git a/zcash-hsmbuilder/src/zcash.rs b/zcash-hsmbuilder/src/zcash.rs deleted file mode 100644 index 2199975..0000000 --- a/zcash-hsmbuilder/src/zcash.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! Wrapper over zcash crates and zecwallet fork of the crates -//! -//! Use this instead of importing things from zcash crates directly!! - -cfg_if::cfg_if! { - if #[cfg(all(feature = "zecwallet-compat", feature = "normal-zcash"))] { - compile_error!("Only one feature should be enabled between 'zecwallet-compat' and 'normal-zcash'!"); - } else if #[cfg(feature = "zecwallet-compat")] { - pub use zecw_primitives as primitives; - pub use zecw_proofs as proofs; - pub use zecw_note_encryption as note_encryption; - } else if #[cfg(feature = "normal-zcash")] { - pub use zcash_primitives as primitives; - pub use zcash_proofs as proofs; - pub use zcash_note_encryption as note_encryption; - } else { - compile_error!("One feature should be enabled between 'zecwallet-compat' and 'normal-zcash'!"); - } -}