From e4915f99c44e5b1651535441deba58d4df4e70c7 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sat, 14 Dec 2024 13:11:32 +0000 Subject: [PATCH] zcash_transparent: Refactor code so it compiles in its new crate --- Cargo.lock | 3 + components/zcash_protocol/CHANGELOG.md | 3 + components/zcash_protocol/Cargo.toml | 4 ++ components/zcash_protocol/src/lib.rs | 3 + components/zcash_protocol/src/txid.rs | 65 ++++++++++++++++++ devtools/Cargo.toml | 1 + devtools/src/bin/inspect/transaction.rs | 28 +++++--- pczt/src/roles/signer/mod.rs | 16 ++++- zcash_primitives/CHANGELOG.md | 7 +- zcash_primitives/src/transaction/builder.rs | 12 ++-- zcash_primitives/src/transaction/mod.rs | 66 ++----------------- zcash_primitives/src/transaction/sighash.rs | 19 ++---- .../src/transaction/sighash_v4.rs | 19 ++---- .../src/transaction/sighash_v5.rs | 20 ++---- zcash_primitives/src/transaction/tests.rs | 65 +++++++++--------- zcash_primitives/src/transaction/txid.rs | 2 +- zcash_transparent/src/address.rs | 3 - zcash_transparent/src/builder.rs | 66 ++++++++----------- zcash_transparent/src/bundle.rs | 30 ++++----- zcash_transparent/src/keys.rs | 2 +- zcash_transparent/src/pczt.rs | 13 ++-- zcash_transparent/src/pczt/parse.rs | 7 +- zcash_transparent/src/pczt/signer.rs | 46 +++++-------- zcash_transparent/src/pczt/spend_finalizer.rs | 2 +- zcash_transparent/src/pczt/tx_extractor.rs | 38 +++++------ zcash_transparent/src/pczt/updater.rs | 2 +- zcash_transparent/src/pczt/verify.rs | 2 +- zcash_transparent/src/sighash.rs | 32 +++++++++ 28 files changed, 303 insertions(+), 273 deletions(-) create mode 100644 components/zcash_protocol/src/txid.rs diff --git a/Cargo.lock b/Cargo.lock index e9149dae79..c6cbb1959c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1249,6 +1249,7 @@ dependencies = [ "zcash_primitives", "zcash_proofs", "zcash_protocol", + "zcash_transparent", ] [[package]] @@ -6424,7 +6425,9 @@ dependencies = [ name = "zcash_protocol" version = "0.4.2" dependencies = [ + "core2", "document-features", + "hex", "incrementalmerkletree", "incrementalmerkletree-testing", "memuse", diff --git a/components/zcash_protocol/CHANGELOG.md b/components/zcash_protocol/CHANGELOG.md index a917ee3a40..d617cea980 100644 --- a/components/zcash_protocol/CHANGELOG.md +++ b/components/zcash_protocol/CHANGELOG.md @@ -6,6 +6,9 @@ and this library adheres to Rust's notion of [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- `zcash_protocol::TxId` (moved from `zcash_primitives::transaction`). + ## [0.4.2] - 2024-12-13 ### Added - `no-std` compatibility (`alloc` is required). A default-enabled `std` feature diff --git a/components/zcash_protocol/Cargo.toml b/components/zcash_protocol/Cargo.toml index 8a59bba43d..ac79e5df1f 100644 --- a/components/zcash_protocol/Cargo.toml +++ b/components/zcash_protocol/Cargo.toml @@ -28,6 +28,10 @@ memuse = { workspace = true, optional = true } # - Documentation document-features = { workspace = true, optional = true } +# - Encodings +core2.workspace = true +hex.workspace = true + # - Test dependencies proptest = { workspace = true, optional = true } incrementalmerkletree = { workspace = true, optional = true } diff --git a/components/zcash_protocol/src/lib.rs b/components/zcash_protocol/src/lib.rs index bc42977713..ae25f66d52 100644 --- a/components/zcash_protocol/src/lib.rs +++ b/components/zcash_protocol/src/lib.rs @@ -31,6 +31,9 @@ pub mod local_consensus; pub mod memo; pub mod value; +mod txid; +pub use txid::TxId; + /// A Zcash shielded transfer protocol. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum ShieldedProtocol { diff --git a/components/zcash_protocol/src/txid.rs b/components/zcash_protocol/src/txid.rs new file mode 100644 index 0000000000..245d06fb86 --- /dev/null +++ b/components/zcash_protocol/src/txid.rs @@ -0,0 +1,65 @@ +use alloc::string::ToString; +use core::fmt; +use core2::io::{self, Read, Write}; + +#[cfg(feature = "std")] +use memuse::DynamicUsage; + +/// The identifier for a Zcash transaction. +/// +/// - For v1-4 transactions, this is a double-SHA-256 hash of the encoded transaction. +/// This means that it is malleable, and only a reliable identifier for transactions +/// that have been mined. +/// - For v5 transactions onwards, this identifier is derived only from "effecting" data, +/// and is non-malleable in all contexts. +#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)] +pub struct TxId([u8; 32]); + +#[cfg(feature = "std")] +memuse::impl_no_dynamic_usage!(TxId); + +impl fmt::Debug for TxId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // The (byte-flipped) hex string is more useful than the raw bytes, because we can + // look that up in RPC methods and block explorers. + let txid_str = self.to_string(); + f.debug_tuple("TxId").field(&txid_str).finish() + } +} + +impl fmt::Display for TxId { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut data = self.0; + data.reverse(); + formatter.write_str(&hex::encode(data)) + } +} + +impl AsRef<[u8; 32]> for TxId { + fn as_ref(&self) -> &[u8; 32] { + &self.0 + } +} + +impl From for [u8; 32] { + fn from(value: TxId) -> Self { + value.0 + } +} + +impl TxId { + pub const fn from_bytes(bytes: [u8; 32]) -> Self { + TxId(bytes) + } + + pub fn read(mut reader: R) -> io::Result { + let mut hash = [0u8; 32]; + reader.read_exact(&mut hash)?; + Ok(TxId::from_bytes(hash)) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + writer.write_all(&self.0)?; + Ok(()) + } +} diff --git a/devtools/Cargo.toml b/devtools/Cargo.toml index 417404ed67..56917a21ad 100644 --- a/devtools/Cargo.toml +++ b/devtools/Cargo.toml @@ -27,6 +27,7 @@ zcash_protocol = {workspace = true, features = ["local-consensus"] } # Transparent secp256k1.workspace = true +transparent.workspace = true # Sprout ed25519-zebra = "4" diff --git a/devtools/src/bin/inspect/transaction.rs b/devtools/src/bin/inspect/transaction.rs index 3521430765..bfb8bbc3aa 100644 --- a/devtools/src/bin/inspect/transaction.rs +++ b/devtools/src/bin/inspect/transaction.rs @@ -3,6 +3,7 @@ use std::{ convert::{TryFrom, TryInto}, }; +use ::transparent::sighash::SighashType; use bellman::groth16; use group::GroupEncoding; use orchard::note_encryption::OrchardDomain; @@ -283,7 +284,7 @@ pub(crate) fn inspect( let sig = secp256k1::ecdsa::Signature::from_der( &txin.script_sig.0[1..1 + sig_len], ); - let hash_type = txin.script_sig.0[1 + sig_len]; + let hash_type = SighashType::parse(txin.script_sig.0[1 + sig_len]); let pubkey_bytes = &txin.script_sig.0[1 + sig_len + 2..]; let pubkey = secp256k1::PublicKey::from_slice(pubkey_bytes); @@ -293,13 +294,18 @@ pub(crate) fn inspect( i, e ); } + if hash_type.is_none() { + eprintln!(" ⚠️ Txin {} has invalid sighash type", i); + } if let Err(e) = pubkey { eprintln!( " ⚠️ Txin {} has invalid pubkey encoding: {}", i, e ); } - if let (Ok(sig), Ok(pubkey)) = (sig, pubkey) { + if let (Ok(sig), Some(hash_type), Ok(pubkey)) = + (sig, hash_type, pubkey) + { #[allow(deprecated)] if pubkey_to_address(&pubkey) != addr { eprintln!(" ⚠️ Txin {} pubkey does not match coin's script_pubkey", i); @@ -307,14 +313,16 @@ pub(crate) fn inspect( let sighash = signature_hash( tx, - &SignableInput::Transparent { - hash_type, - index: i, - // For P2PKH these are the same. - script_code: &coin.script_pubkey, - script_pubkey: &coin.script_pubkey, - value: coin.value, - }, + &SignableInput::Transparent( + ::transparent::sighash::SignableInput::from_parts( + hash_type, + i, + // For P2PKH these are the same. + &coin.script_pubkey, + &coin.script_pubkey, + coin.value, + ), + ), txid_parts, ); let msg = secp256k1::Message::from_slice(sighash.as_ref()) diff --git a/pczt/src/roles/signer/mod.rs b/pczt/src/roles/signer/mod.rs index a3c6effdb4..c464cd7406 100644 --- a/pczt/src/roles/signer/mod.rs +++ b/pczt/src/roles/signer/mod.rs @@ -95,7 +95,21 @@ impl Signer { // TODO input - .sign(index, &self.tx_data, &self.txid_parts, sk, &self.secp) + .sign( + index, + |input| { + v5_signature_hash( + &self.tx_data, + &SignableInput::Transparent(input), + &self.txid_parts, + ) + .as_ref() + .try_into() + .unwrap() + }, + sk, + &self.secp, + ) .map_err(Error::TransparentSign)?; // Update transaction modifiability: diff --git a/zcash_primitives/CHANGELOG.md b/zcash_primitives/CHANGELOG.md index af6e72fdc1..4c2c8c0675 100644 --- a/zcash_primitives/CHANGELOG.md +++ b/zcash_primitives/CHANGELOG.md @@ -24,8 +24,8 @@ and this library adheres to Rust's notion of - `zcash_primitives::transaction::components::transparent`: - `builder::TransparentBuilder::add_input` now takes `secp256k1::PublicKey` instead of `secp256k1::SecretKey`. - - `Bundle::apply_signatures` now takes an additional argument - `&TransparentSigningSet`. + - `Bundle::apply_signatures` has had its arguments replaced with + a function providing the sighash calculation, and `&TransparentSigningSet`. - `builder::Error` has a new variant `MissingSigningKey`. - `zcash_primitives::transaction::builder`: - `Builder::add_orchard_spend` now takes `orchard::keys::FullViewingKey` @@ -38,6 +38,9 @@ and this library adheres to Rust's notion of - `&TransparentSigningSet` - `&[sapling::zip32::ExtendedSpendingKey]` - `&[orchard::keys::SpendAuthorizingKey]` +- `zcash_primitives::transaction::sighash`: + - `SignableInput::Transparent` is now a wrapper around + `zcash_transparent::sighash::SignableInput`. ## [0.20.0] - 2024-11-14 diff --git a/zcash_primitives/src/transaction/builder.rs b/zcash_primitives/src/transaction/builder.rs index 398d0caf5a..61b323bb41 100644 --- a/zcash_primitives/src/transaction/builder.rs +++ b/zcash_primitives/src/transaction/builder.rs @@ -806,10 +806,14 @@ impl<'a, P: consensus::Parameters, U: sapling::builder::ProverProgress> Builder< .clone() .map(|b| { b.apply_signatures( - #[cfg(feature = "transparent-inputs")] - &unauthed_tx, - #[cfg(feature = "transparent-inputs")] - &txid_parts, + |input| { + *signature_hash( + &unauthed_tx, + &SignableInput::Transparent(input), + &txid_parts, + ) + .as_ref() + }, transparent_signing_set, ) }) diff --git a/zcash_primitives/src/transaction/mod.rs b/zcash_primitives/src/transaction/mod.rs index c9c1d77e6d..14f4d654a9 100644 --- a/zcash_primitives/src/transaction/mod.rs +++ b/zcash_primitives/src/transaction/mod.rs @@ -13,9 +13,7 @@ mod tests; use blake2b_simd::Hash as Blake2bHash; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; -use memuse::DynamicUsage; use std::convert::TryFrom; -use std::fmt; use std::fmt::Debug; use std::io::{self, Read, Write}; use std::ops::Deref; @@ -60,63 +58,7 @@ const ZFUTURE_VERSION_GROUP_ID: u32 = 0xFFFFFFFF; #[cfg(zcash_unstable = "zfuture")] const ZFUTURE_TX_VERSION: u32 = 0x0000FFFF; -/// The identifier for a Zcash transaction. -/// -/// - For v1-4 transactions, this is a double-SHA-256 hash of the encoded transaction. -/// This means that it is malleable, and only a reliable identifier for transactions -/// that have been mined. -/// - For v5 transactions onwards, this identifier is derived only from "effecting" data, -/// and is non-malleable in all contexts. -#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)] -pub struct TxId([u8; 32]); - -memuse::impl_no_dynamic_usage!(TxId); - -impl fmt::Debug for TxId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // The (byte-flipped) hex string is more useful than the raw bytes, because we can - // look that up in RPC methods and block explorers. - let txid_str = self.to_string(); - f.debug_tuple("TxId").field(&txid_str).finish() - } -} - -impl fmt::Display for TxId { - fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut data = self.0; - data.reverse(); - formatter.write_str(&hex::encode(data)) - } -} - -impl AsRef<[u8; 32]> for TxId { - fn as_ref(&self) -> &[u8; 32] { - &self.0 - } -} - -impl From for [u8; 32] { - fn from(value: TxId) -> Self { - value.0 - } -} - -impl TxId { - pub fn from_bytes(bytes: [u8; 32]) -> Self { - TxId(bytes) - } - - pub fn read(mut reader: R) -> io::Result { - let mut hash = [0u8; 32]; - reader.read_exact(&mut hash)?; - Ok(TxId::from_bytes(hash)) - } - - pub fn write(&self, mut writer: W) -> io::Result<()> { - writer.write_all(&self.0)?; - Ok(()) - } -} +pub use zcash_protocol::TxId; /// The set of defined transaction format versions. /// @@ -611,12 +553,12 @@ impl Transaction { fn from_data_v4(data: TransactionData) -> io::Result { let mut tx = Transaction { - txid: TxId([0; 32]), + txid: TxId::from_bytes([0; 32]), data, }; let mut writer = HashWriter::default(); tx.write(&mut writer)?; - tx.txid.0.copy_from_slice(&writer.into_hash()); + tx.txid = TxId::from_bytes(writer.into_hash().into()); Ok(tx) } @@ -706,7 +648,7 @@ impl Transaction { txid.copy_from_slice(&hash_bytes); Ok(Transaction { - txid: TxId(txid), + txid: TxId::from_bytes(txid), data: TransactionData { version, consensus_branch_id, diff --git a/zcash_primitives/src/transaction/sighash.rs b/zcash_primitives/src/transaction/sighash.rs index e0999afe41..ff379eda48 100644 --- a/zcash_primitives/src/transaction/sighash.rs +++ b/zcash_primitives/src/transaction/sighash.rs @@ -1,13 +1,10 @@ use blake2b_simd::Hash as Blake2bHash; use super::{ - components::amount::NonNegativeAmount, sighash_v4::v4_signature_hash, - sighash_v5::v5_signature_hash, Authorization, TransactionData, TxDigests, TxVersion, -}; -use crate::{ - legacy::Script, - sapling::{self, bundle::GrothProofBytes}, + sighash_v4::v4_signature_hash, sighash_v5::v5_signature_hash, Authorization, TransactionData, + TxDigests, TxVersion, }; +use crate::sapling::{self, bundle::GrothProofBytes}; #[cfg(zcash_unstable = "zfuture")] use {super::components::Amount, crate::extensions::transparent::Precondition}; @@ -16,13 +13,7 @@ pub use transparent::sighash::*; pub enum SignableInput<'a> { Shielded, - Transparent { - hash_type: u8, - index: usize, - script_code: &'a Script, - script_pubkey: &'a Script, - value: NonNegativeAmount, - }, + Transparent(transparent::sighash::SignableInput<'a>), #[cfg(zcash_unstable = "zfuture")] Tze { index: usize, @@ -35,7 +26,7 @@ impl<'a> SignableInput<'a> { pub fn hash_type(&self) -> u8 { match self { SignableInput::Shielded => SIGHASH_ALL, - SignableInput::Transparent { hash_type, .. } => *hash_type, + SignableInput::Transparent(input) => input.hash_type().encode(), #[cfg(zcash_unstable = "zfuture")] SignableInput::Tze { .. } => SIGHASH_ALL, } diff --git a/zcash_primitives/src/transaction/sighash_v4.rs b/zcash_primitives/src/transaction/sighash_v4.rs index ad136e5f8d..3d9e47a5ce 100644 --- a/zcash_primitives/src/transaction/sighash_v4.rs +++ b/zcash_primitives/src/transaction/sighash_v4.rs @@ -186,8 +186,8 @@ pub fn v4_signature_hash< ); } else if (hash_type & SIGHASH_MASK) == SIGHASH_SINGLE { match (tx.transparent_bundle.as_ref(), signable_input) { - (Some(b), SignableInput::Transparent { index, .. }) if index < &b.vout.len() => { - h.update(single_output_hash(&b.vout[*index]).as_bytes()) + (Some(b), SignableInput::Transparent(input)) if input.index() < &b.vout.len() => { + h.update(single_output_hash(&b.vout[*input.index()]).as_bytes()) } _ => h.update(&[0; 32]), }; @@ -235,18 +235,13 @@ pub fn v4_signature_hash< match signable_input { SignableInput::Shielded => (), - SignableInput::Transparent { - index, - script_code, - value, - .. - } => { + SignableInput::Transparent(input) => { if let Some(bundle) = tx.transparent_bundle.as_ref() { let mut data = vec![]; - bundle.vin[*index].prevout.write(&mut data).unwrap(); - script_code.write(&mut data).unwrap(); - data.extend_from_slice(&value.to_i64_le_bytes()); - data.extend_from_slice(&bundle.vin[*index].sequence.to_le_bytes()); + bundle.vin[*input.index()].prevout.write(&mut data).unwrap(); + input.script_code().write(&mut data).unwrap(); + data.extend_from_slice(&input.value().to_i64_le_bytes()); + data.extend_from_slice(&bundle.vin[*input.index()].sequence.to_le_bytes()); h.update(&data); } else { panic!( diff --git a/zcash_primitives/src/transaction/sighash_v5.rs b/zcash_primitives/src/transaction/sighash_v5.rs index 60b02b8b75..410feb638d 100644 --- a/zcash_primitives/src/transaction/sighash_v5.rs +++ b/zcash_primitives/src/transaction/sighash_v5.rs @@ -89,10 +89,10 @@ fn transparent_sig_digest( txid_digests.sequence_digest }; - let outputs_digest = if let SignableInput::Transparent { index, .. } = input { + let outputs_digest = if let SignableInput::Transparent(input) = input { if flag_single { - if *index < bundle.vout.len() { - transparent_outputs_hash(&[&bundle.vout[*index]]) + if *input.index() < bundle.vout.len() { + transparent_outputs_hash(&[&bundle.vout[*input.index()]]) } else { transparent_outputs_hash::(&[]) } @@ -110,17 +110,11 @@ fn transparent_sig_digest( //S.2g.iii: scriptPubKey (field encoding) //S.2g.iv: nSequence (4-byte unsigned little-endian) let mut ch = hasher(ZCASH_TRANSPARENT_INPUT_HASH_PERSONALIZATION); - if let SignableInput::Transparent { - index, - script_pubkey, - value, - .. - } = input - { - let txin = &bundle.vin[*index]; + if let SignableInput::Transparent(input) = input { + let txin = &bundle.vin[*input.index()]; txin.prevout.write(&mut ch).unwrap(); - ch.write_all(&value.to_i64_le_bytes()).unwrap(); - script_pubkey.write(&mut ch).unwrap(); + ch.write_all(&input.value().to_i64_le_bytes()).unwrap(); + input.script_pubkey().write(&mut ch).unwrap(); ch.write_all(&txin.sequence.to_le_bytes()).unwrap(); } let txin_sig_digest = ch.finalize(); diff --git a/zcash_primitives/src/transaction/tests.rs b/zcash_primitives/src/transaction/tests.rs index 6aa63b29c8..fed4a1e799 100644 --- a/zcash_primitives/src/transaction/tests.rs +++ b/zcash_primitives/src/transaction/tests.rs @@ -1,3 +1,4 @@ +use ::transparent::sighash::SighashType; use blake2b_simd::Hash as Blake2bHash; use std::ops::Deref; @@ -9,10 +10,7 @@ use crate::{ use super::{ sapling, - sighash::{ - SignableInput, TransparentAuthorizingContext, SIGHASH_ALL, SIGHASH_ANYONECANPAY, - SIGHASH_NONE, SIGHASH_SINGLE, - }, + sighash::{SignableInput, TransparentAuthorizingContext}, sighash_v4::v4_signature_hash, sighash_v5::v5_signature_hash, testing::arb_tx, @@ -136,13 +134,15 @@ fn zip_0143() { for tv in self::data::zip_0143::make_test_vectors() { let tx = Transaction::read(&tv.tx[..], tv.consensus_branch_id).unwrap(); let signable_input = match tv.transparent_input { - Some(n) => SignableInput::Transparent { - hash_type: tv.hash_type as u8, - index: n as usize, - script_code: &tv.script_code, - script_pubkey: &tv.script_code, - value: NonNegativeAmount::from_nonnegative_i64(tv.amount).unwrap(), - }, + Some(n) => { + SignableInput::Transparent(::transparent::sighash::SignableInput::from_parts( + SighashType::parse(tv.hash_type as u8).unwrap(), + n as usize, + &tv.script_code, + &tv.script_code, + NonNegativeAmount::from_nonnegative_i64(tv.amount).unwrap(), + )) + } _ => SignableInput::Shielded, }; @@ -158,13 +158,15 @@ fn zip_0243() { for tv in self::data::zip_0243::make_test_vectors() { let tx = Transaction::read(&tv.tx[..], tv.consensus_branch_id).unwrap(); let signable_input = match tv.transparent_input { - Some(n) => SignableInput::Transparent { - hash_type: tv.hash_type as u8, - index: n as usize, - script_code: &tv.script_code, - script_pubkey: &tv.script_code, - value: NonNegativeAmount::from_nonnegative_i64(tv.amount).unwrap(), - }, + Some(n) => { + SignableInput::Transparent(::transparent::sighash::SignableInput::from_parts( + SighashType::parse(tv.hash_type as u8).unwrap(), + n as usize, + &tv.script_code, + &tv.script_code, + NonNegativeAmount::from_nonnegative_i64(tv.amount).unwrap(), + )) + } _ => SignableInput::Shielded, }; @@ -287,27 +289,30 @@ fn zip_0244() { let bundle = txdata.transparent_bundle().unwrap(); let value = bundle.authorization.input_amounts[index]; let script_pubkey = &bundle.authorization.input_scriptpubkeys[index]; - let signable_input = |hash_type| SignableInput::Transparent { - hash_type, - index, - script_code: script_pubkey, - script_pubkey, - value, + let signable_input = |hash_type| { + SignableInput::Transparent(::transparent::sighash::SignableInput::from_parts( + hash_type, + index, + script_pubkey, + script_pubkey, + value, + )) }; assert_eq!( - v5_signature_hash(&txdata, &signable_input(SIGHASH_ALL), &txid_parts).as_ref(), + v5_signature_hash(&txdata, &signable_input(SighashType::ALL), &txid_parts).as_ref(), &tv.sighash_all.unwrap() ); assert_eq!( - v5_signature_hash(&txdata, &signable_input(SIGHASH_NONE), &txid_parts).as_ref(), + v5_signature_hash(&txdata, &signable_input(SighashType::NONE), &txid_parts) + .as_ref(), &tv.sighash_none.unwrap() ); if index < bundle.vout.len() { assert_eq!( - v5_signature_hash(&txdata, &signable_input(SIGHASH_SINGLE), &txid_parts) + v5_signature_hash(&txdata, &signable_input(SighashType::SINGLE), &txid_parts) .as_ref(), &tv.sighash_single.unwrap() ); @@ -318,7 +323,7 @@ fn zip_0244() { assert_eq!( v5_signature_hash( &txdata, - &signable_input(SIGHASH_ALL | SIGHASH_ANYONECANPAY), + &signable_input(SighashType::ALL_ANYONECANPAY), &txid_parts, ) .as_ref(), @@ -328,7 +333,7 @@ fn zip_0244() { assert_eq!( v5_signature_hash( &txdata, - &signable_input(SIGHASH_NONE | SIGHASH_ANYONECANPAY), + &signable_input(SighashType::NONE_ANYONECANPAY), &txid_parts, ) .as_ref(), @@ -339,7 +344,7 @@ fn zip_0244() { assert_eq!( v5_signature_hash( &txdata, - &signable_input(SIGHASH_SINGLE | SIGHASH_ANYONECANPAY), + &signable_input(SighashType::SINGLE_ANYONECANPAY), &txid_parts, ) .as_ref(), diff --git a/zcash_primitives/src/transaction/txid.rs b/zcash_primitives/src/transaction/txid.rs index 84941271c9..de5671c633 100644 --- a/zcash_primitives/src/transaction/txid.rs +++ b/zcash_primitives/src/transaction/txid.rs @@ -420,7 +420,7 @@ pub fn to_txid( digests.tze_digests.as_ref(), ); - TxId(<[u8; 32]>::try_from(txid_digest.as_bytes()).unwrap()) + TxId::from_bytes(<[u8; 32]>::try_from(txid_digest.as_bytes()).unwrap()) } /// Digester which constructs a digest of only the witness data. diff --git a/zcash_transparent/src/address.rs b/zcash_transparent/src/address.rs index efc766fcc5..2dfde0e0ab 100644 --- a/zcash_transparent/src/address.rs +++ b/zcash_transparent/src/address.rs @@ -9,9 +9,6 @@ use std::ops::Shl; use zcash_encoding::Vector; -#[cfg(feature = "transparent-inputs")] -pub mod keys; - /// Defined script opcodes. /// /// Most of the opcodes are unused by this crate, but we define them so that the alternate diff --git a/zcash_transparent/src/builder.rs b/zcash_transparent/src/builder.rs index 97ef23188b..78d04aad9c 100644 --- a/zcash_transparent/src/builder.rs +++ b/zcash_transparent/src/builder.rs @@ -3,26 +3,18 @@ use std::collections::BTreeMap; use std::fmt; +use zcash_protocol::value::{BalanceError, ZatBalance as Amount, Zatoshis as NonNegativeAmount}; + use crate::{ - legacy::{Script, TransparentAddress}, - transaction::{ - components::{ - amount::{Amount, BalanceError, NonNegativeAmount}, - transparent::{self, Authorization, Authorized, Bundle, TxIn, TxOut}, - }, - sighash::TransparentAuthorizingContext, - }, + address::{Script, TransparentAddress}, + bundle::{Authorization, Authorized, Bundle, TxIn, TxOut}, + pczt, + sighash::{SighashType, SignableInput, TransparentAuthorizingContext}, }; #[cfg(feature = "transparent-inputs")] use { - crate::transaction::{ - self as tx, - components::transparent::OutPoint, - sighash::{signature_hash, SighashType, SignableInput, SIGHASH_ALL}, - TransactionData, TxDigests, - }, - blake2b_simd::Hash as Blake2bHash, + crate::{bundle::OutPoint, sighash::SIGHASH_ALL}, sha2::Digest, }; @@ -206,7 +198,7 @@ impl TransparentBuilder { (Amount::from(input_sum) - Amount::from(output_sum)).ok_or(BalanceError::Underflow) } - pub fn build(self) -> Option> { + pub fn build(self) -> Option> { #[cfg(feature = "transparent-inputs")] let vin: Vec> = self .inputs @@ -220,7 +212,7 @@ impl TransparentBuilder { if vin.is_empty() && self.vout.is_empty() { None } else { - Some(transparent::Bundle { + Some(Bundle { vin, vout: self.vout, authorization: Unauthorized { @@ -231,12 +223,13 @@ impl TransparentBuilder { } } - pub(crate) fn build_for_pczt(self) -> Option { + /// Builds a bundle containing the given inputs and outputs, for inclusion in a PCZT. + pub fn build_for_pczt(self) -> Option { #[cfg(feature = "transparent-inputs")] let inputs = self .inputs .iter() - .map(|i| transparent::pczt::Input { + .map(|i| pczt::Input { prevout_txid: i.utxo.hash, prevout_index: i.utxo.n, sequence: None, @@ -267,7 +260,7 @@ impl TransparentBuilder { let outputs = self .vout .into_iter() - .map(|o| transparent::pczt::Output { + .map(|o| pczt::Output { value: o.value, script_pubkey: o.script_pubkey, // We don't currently support spending P2SH coins, so we only ever see @@ -280,7 +273,7 @@ impl TransparentBuilder { }) .collect(); - Some(transparent::pczt::Bundle { inputs, outputs }) + Some(pczt::Bundle { inputs, outputs }) } } } @@ -323,12 +316,15 @@ impl TransparentAuthorizingContext for Unauthorized { } impl Bundle { - pub fn apply_signatures( + #[cfg_attr(not(feature = "transparent-inputs"), allow(unused_variables))] + pub fn apply_signatures( self, - #[cfg(feature = "transparent-inputs")] mtx: &TransactionData, - #[cfg(feature = "transparent-inputs")] txid_parts_cache: &TxDigests, + calculate_sighash: F, signing_set: &TransparentSigningSet, - ) -> Result, Error> { + ) -> Result, Error> + where + F: Fn(SignableInput) -> [u8; 32], + { #[cfg(feature = "transparent-inputs")] let script_sigs = self .authorization @@ -343,17 +339,13 @@ impl Bundle { .find(|(_, pubkey)| pubkey == &info.pubkey) .ok_or(Error::MissingSigningKey)?; - let sighash = signature_hash( - mtx, - &SignableInput::Transparent { - hash_type: SIGHASH_ALL, - index, - script_code: &info.coin.script_pubkey, // for p2pkh, always the same as script_pubkey - script_pubkey: &info.coin.script_pubkey, - value: info.coin.value, - }, - txid_parts_cache, - ); + let sighash = calculate_sighash(SignableInput { + hash_type: SighashType::ALL, + index, + script_code: &info.coin.script_pubkey, // for p2pkh, always the same as script_pubkey + script_pubkey: &info.coin.script_pubkey, + value: info.coin.value, + }); let msg = secp256k1::Message::from_slice(sighash.as_ref()).expect("32 bytes"); let sig = signing_set.secp.sign_ecdsa(&msg, sk); @@ -369,7 +361,7 @@ impl Bundle { #[cfg(not(feature = "transparent-inputs"))] let script_sigs = std::iter::empty::>(); - Ok(transparent::Bundle { + Ok(Bundle { vin: self .vin .iter() diff --git a/zcash_transparent/src/bundle.rs b/zcash_transparent/src/bundle.rs index ce441f9780..3125894de9 100644 --- a/zcash_transparent/src/bundle.rs +++ b/zcash_transparent/src/bundle.rs @@ -1,20 +1,18 @@ //! Structs representing the components within Zcash transactions. use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use zcash_protocol::TxId; use std::fmt::Debug; use std::io::{self, Read, Write}; +use zcash_protocol::value::{BalanceError, ZatBalance as Amount, Zatoshis as NonNegativeAmount}; + use crate::{ - legacy::{Script, TransparentAddress}, - transaction::{sighash::TransparentAuthorizingContext, TxId}, + address::{Script, TransparentAddress}, + sighash::TransparentAuthorizingContext, }; -use super::amount::{Amount, BalanceError, NonNegativeAmount}; - -pub mod builder; -pub mod pczt; - pub trait Authorization: Debug { type ScriptSig: Debug + Clone + PartialEq; } @@ -23,7 +21,7 @@ pub trait Authorization: Debug { /// information for creating sighashes. #[derive(Debug)] pub struct EffectsOnly { - inputs: Vec, + pub(crate) inputs: Vec, } impl Authorization for EffectsOnly { @@ -56,11 +54,6 @@ pub trait MapAuth { } /// The identity map. -/// -/// This can be used with [`TransactionData::map_authorization`] when you want to map the -/// authorization of a subset of a transaction's bundles. -/// -/// [`TransactionData::map_authorization`]: crate::transaction::TransactionData::map_authorization impl MapAuth for () { fn map_script_sig( &self, @@ -138,8 +131,8 @@ impl Bundle { #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct OutPoint { - hash: TxId, - n: u32, + pub(crate) hash: TxId, + pub(crate) n: u32, } impl OutPoint { @@ -156,7 +149,7 @@ impl OutPoint { #[cfg(any(test, feature = "test-dependencies"))] pub const fn fake() -> Self { OutPoint { - hash: TxId([1u8; 32]), + hash: TxId::from_bytes([1u8; 32]), n: 1, } } @@ -262,8 +255,9 @@ pub mod testing { use proptest::collection::vec; use proptest::prelude::*; use proptest::sample::select; + use zcash_protocol::value::testing::arb_zatoshis; - use crate::{legacy::Script, transaction::components::amount::testing::arb_nonnegative_amount}; + use crate::address::Script; use super::{Authorized, Bundle, OutPoint, TxIn, TxOut}; @@ -301,7 +295,7 @@ pub mod testing { } prop_compose! { - pub fn arb_txout()(value in arb_nonnegative_amount(), script_pubkey in arb_script()) -> TxOut { + pub fn arb_txout()(value in arb_zatoshis(), script_pubkey in arb_script()) -> TxOut { TxOut { value, script_pubkey } } } diff --git a/zcash_transparent/src/keys.rs b/zcash_transparent/src/keys.rs index 1e3336a237..39a0908c6a 100644 --- a/zcash_transparent/src/keys.rs +++ b/zcash_transparent/src/keys.rs @@ -11,7 +11,7 @@ use zcash_protocol::consensus::{self, NetworkConstants}; use zcash_spec::PrfExpand; use zip32::AccountId; -use super::TransparentAddress; +use crate::address::TransparentAddress; /// The scope of a transparent key. /// diff --git a/zcash_transparent/src/pczt.rs b/zcash_transparent/src/pczt.rs index ba8d7cfadf..4a3cd5106d 100644 --- a/zcash_transparent/src/pczt.rs +++ b/zcash_transparent/src/pczt.rs @@ -4,12 +4,9 @@ use std::collections::BTreeMap; use bip32::ChildNumber; use getset::Getters; -use zcash_protocol::value::Zatoshis; +use zcash_protocol::{value::Zatoshis, TxId}; -use crate::{ - legacy::Script, - transaction::{sighash::SighashType, TxId}, -}; +use crate::{address::Script, sighash::SighashType}; mod parse; pub use parse::ParseError; @@ -37,7 +34,7 @@ pub use tx_extractor::{TxExtractorError, Unbound}; /// This struct is for representing Sapling in a partially-created transaction. If you /// have a fully-created transaction, use [the regular `Bundle` struct]. /// -/// [the regular `Bundle` struct]: super::Bundle +/// [the regular `Bundle` struct]: crate::bundle::Bundle #[derive(Debug, Getters)] #[getset(get = "pub")] pub struct Bundle { @@ -68,7 +65,7 @@ impl Bundle { /// This struct is for representing transparent spends in a partially-created transaction. /// If you have a fully-created transaction, use [the regular `TxIn` struct]. /// -/// [the regular `TxIn` struct]: super::TxIn +/// [the regular `TxIn` struct]: crate::bundle::TxIn #[derive(Debug, Getters)] #[getset(get = "pub")] pub struct Input { @@ -185,7 +182,7 @@ pub struct Input { /// transaction. If you have a fully-created transaction, use /// [the regular `TxOut` struct]. /// -/// [the regular `TxOut` struct]: super::TxOut +/// [the regular `TxOut` struct]: crate::bundle::TxOut #[derive(Debug, Getters)] #[getset(get = "pub")] pub struct Output { diff --git a/zcash_transparent/src/pczt/parse.rs b/zcash_transparent/src/pczt/parse.rs index fe42101aeb..40acbe8f5b 100644 --- a/zcash_transparent/src/pczt/parse.rs +++ b/zcash_transparent/src/pczt/parse.rs @@ -1,12 +1,9 @@ use std::collections::BTreeMap; use bip32::ChildNumber; -use zcash_protocol::value::Zatoshis; +use zcash_protocol::{value::Zatoshis, TxId}; -use crate::{ - legacy::Script, - transaction::{sighash::SighashType, TxId}, -}; +use crate::{address::Script, sighash::SighashType}; use super::{Bip32Derivation, Bundle, Input, Output}; diff --git a/zcash_transparent/src/pczt/signer.rs b/zcash_transparent/src/pczt/signer.rs index fbe80c639a..471f559af7 100644 --- a/zcash_transparent/src/pczt/signer.rs +++ b/zcash_transparent/src/pczt/signer.rs @@ -1,10 +1,4 @@ -use blake2b_simd::Hash as Blake2bHash; - -use crate::transaction::{ - sighash::{SignableInput, TransparentAuthorizingContext}, - sighash_v5::v5_signature_hash, - Authorization, TransactionData, TxDigests, -}; +use crate::sighash::SignableInput; impl super::Input { /// Signs the transparent spend with the given spend authorizing key. @@ -12,43 +6,37 @@ impl super::Input { /// It is the caller's responsibility to perform any semantic validity checks on the /// PCZT (for example, comfirming that the change amounts are correct) before calling /// this method. - pub fn sign< - TA: TransparentAuthorizingContext, - A: Authorization, - C: secp256k1::Signing, - >( + pub fn sign( &mut self, index: usize, - mtx: &TransactionData, - txid_parts: &TxDigests, + calculate_sighash: F, sk: &secp256k1::SecretKey, secp: &secp256k1::Secp256k1, - ) -> Result<(), SignerError> { - let hash_type = self.sighash_type.encode(); + ) -> Result<(), SignerError> + where + F: FnOnce(SignableInput) -> [u8; 32], + { let pubkey = sk.public_key(secp).serialize(); // Check that the corresponding pubkey appears in either `script_pubkey` or // `redeem_script`. // TODO - let sighash = v5_signature_hash( - mtx, - &SignableInput::Transparent { - hash_type, - index, - script_code: self.redeem_script.as_ref().unwrap_or(&self.script_pubkey), // for p2pkh, always the same as script_pubkey - script_pubkey: &self.script_pubkey, - value: self.value, - }, - txid_parts, - ); + let sighash = calculate_sighash(SignableInput { + hash_type: self.sighash_type, + index, + // for p2pkh, always the same as script_pubkey + script_code: self.redeem_script.as_ref().unwrap_or(&self.script_pubkey), + script_pubkey: &self.script_pubkey, + value: self.value, + }); - let msg = secp256k1::Message::from_slice(sighash.as_ref()).expect("32 bytes"); + let msg = secp256k1::Message::from_slice(&sighash).expect("32 bytes"); let sig = secp.sign_ecdsa(&msg, sk); // Signature has to have the SighashType appended to it. let mut sig_bytes: Vec = sig.serialize_der()[..].to_vec(); - sig_bytes.extend([hash_type]); + sig_bytes.extend([self.sighash_type.encode()]); self.partial_signatures.insert(pubkey, sig_bytes); diff --git a/zcash_transparent/src/pczt/spend_finalizer.rs b/zcash_transparent/src/pczt/spend_finalizer.rs index 5c4711bc34..dc2ec4be55 100644 --- a/zcash_transparent/src/pczt/spend_finalizer.rs +++ b/zcash_transparent/src/pczt/spend_finalizer.rs @@ -1,7 +1,7 @@ use ripemd::Ripemd160; use sha2::{Digest, Sha256}; -use crate::legacy::{Script, TransparentAddress}; +use crate::address::{Script, TransparentAddress}; impl super::Bundle { /// Finalizes the spends for this bundle. diff --git a/zcash_transparent/src/pczt/tx_extractor.rs b/zcash_transparent/src/pczt/tx_extractor.rs index 6251d7ab72..8805bb61ca 100644 --- a/zcash_transparent/src/pczt/tx_extractor.rs +++ b/zcash_transparent/src/pczt/tx_extractor.rs @@ -1,11 +1,9 @@ use zcash_protocol::value::Zatoshis; use crate::{ - legacy::Script, - transaction::{ - components::{transparent, OutPoint}, - sighash::TransparentAuthorizingContext, - }, + address::Script, + bundle::{Authorization, EffectsOnly, OutPoint, TxIn, TxOut}, + sighash::TransparentAuthorizingContext, }; use super::Input; @@ -15,10 +13,10 @@ impl super::Bundle { /// /// This is used by the Signer role to produce the transaction sighash. /// - /// [regular `Bundle`]: transparent::Bundle + /// [regular `Bundle`]: super::Bundle pub fn extract_effects( &self, - ) -> Result>, TxExtractorError> { + ) -> Result>, TxExtractorError> { self.to_tx_data(|_| Ok(()), |bundle| Ok(effects_only(bundle))) } @@ -26,8 +24,8 @@ impl super::Bundle { /// /// This is used by the Transaction Extractor role to produce the final transaction. /// - /// [regular `Bundle`]: transparent::Bundle - pub fn extract(self) -> Result>, TxExtractorError> { + /// [regular `Bundle`]: super::Bundle + pub fn extract(self) -> Result>, TxExtractorError> { self.to_tx_data( |input| { input @@ -43,11 +41,11 @@ impl super::Bundle { &self, script_sig: F, bundle_auth: G, - ) -> Result>, E> + ) -> Result>, E> where - A: transparent::Authorization, + A: Authorization, E: From, - F: Fn(&Input) -> Result<::ScriptSig, E>, + F: Fn(&Input) -> Result<::ScriptSig, E>, G: FnOnce(&Self) -> Result, { let vin = self @@ -56,7 +54,7 @@ impl super::Bundle { .map(|input| { let prevout = OutPoint::new(input.prevout_txid.into(), input.prevout_index); - Ok(transparent::TxIn { + Ok(TxIn { prevout, script_sig: script_sig(input)?, sequence: input.sequence.unwrap_or(std::u32::MAX), @@ -67,7 +65,7 @@ impl super::Bundle { let vout = self .outputs .iter() - .map(|output| transparent::TxOut { + .map(|output| TxOut { value: output.value, script_pubkey: output.script_pubkey.clone(), }) @@ -76,7 +74,7 @@ impl super::Bundle { Ok(if vin.is_empty() && vout.is_empty() { None } else { - Some(transparent::Bundle { + Some(crate::bundle::Bundle { vin, vout, authorization: bundle_auth(self)?, @@ -93,25 +91,25 @@ pub enum TxExtractorError { MissingScriptSig, } -fn effects_only(bundle: &super::Bundle) -> transparent::EffectsOnly { +fn effects_only(bundle: &super::Bundle) -> EffectsOnly { let inputs = bundle .inputs .iter() - .map(|input| transparent::TxOut { + .map(|input| TxOut { value: input.value, script_pubkey: input.script_pubkey.clone(), }) .collect(); - transparent::EffectsOnly { inputs } + EffectsOnly { inputs } } /// Authorizing data for a transparent bundle in a transaction that is just missing /// binding signatures. #[derive(Debug)] -pub struct Unbound(transparent::EffectsOnly); +pub struct Unbound(EffectsOnly); -impl transparent::Authorization for Unbound { +impl Authorization for Unbound { type ScriptSig = Script; } diff --git a/zcash_transparent/src/pczt/updater.rs b/zcash_transparent/src/pczt/updater.rs index d2bfea116f..9dcb902489 100644 --- a/zcash_transparent/src/pczt/updater.rs +++ b/zcash_transparent/src/pczt/updater.rs @@ -1,7 +1,7 @@ use ripemd::Ripemd160; use sha2::{Digest, Sha256}; -use crate::legacy::{Script, TransparentAddress}; +use crate::address::{Script, TransparentAddress}; use super::{Bip32Derivation, Bundle, Input, Output}; diff --git a/zcash_transparent/src/pczt/verify.rs b/zcash_transparent/src/pczt/verify.rs index 74d7c2a893..6ba662f24f 100644 --- a/zcash_transparent/src/pczt/verify.rs +++ b/zcash_transparent/src/pczt/verify.rs @@ -1,7 +1,7 @@ use ripemd::Ripemd160; use sha2::{Digest, Sha256}; -use crate::legacy::TransparentAddress; +use crate::address::TransparentAddress; impl super::Input { /// Verifies the consistency of this transparent input. diff --git a/zcash_transparent/src/sighash.rs b/zcash_transparent/src/sighash.rs index 3679de9fc4..f53ea19d5a 100644 --- a/zcash_transparent/src/sighash.rs +++ b/zcash_transparent/src/sighash.rs @@ -1,3 +1,4 @@ +use getset::Getters; use zcash_protocol::value::Zatoshis; use crate::{address::Script, bundle::Authorization}; @@ -55,3 +56,34 @@ pub trait TransparentAuthorizingContext: Authorization { /// providing these inputs. fn input_scriptpubkeys(&self) -> Vec