From 1b8accab569401056707b760f887ecaad83dc3e8 Mon Sep 17 00:00:00 2001 From: soralit Date: Wed, 13 Nov 2024 15:08:55 +0800 Subject: [PATCH] feat: adopt pczt latest changes --- rust/apps/zcash/src/pczt.rs | 96 +++++++++--- rust/zcash_vendor/src/pczt/common.rs | 56 ++++++- rust/zcash_vendor/src/pczt/mod.rs | 25 ++++ rust/zcash_vendor/src/pczt/orchard.rs | 173 +++------------------- rust/zcash_vendor/src/pczt/pczt_ext.rs | 91 ++++++++---- rust/zcash_vendor/src/pczt/sapling.rs | 53 ++++--- rust/zcash_vendor/src/pczt/transparent.rs | 117 +++++++++++++-- 7 files changed, 369 insertions(+), 242 deletions(-) diff --git a/rust/apps/zcash/src/pczt.rs b/rust/apps/zcash/src/pczt.rs index e696b0aa0..8c2a4d540 100644 --- a/rust/apps/zcash/src/pczt.rs +++ b/rust/apps/zcash/src/pczt.rs @@ -1,8 +1,17 @@ -use alloc::string::{String, ToString}; +use alloc::{ + collections::btree_map::BTreeMap, + string::{String, ToString}, + vec::Vec, +}; use bitcoin::secp256k1::Message; -use keystore::algorithms::secp256k1::sign_message_by_seed; -use keystore::algorithms::zcash::sign_message_orchard; -use zcash_vendor::pczt::pczt_ext::{PcztSigner, ZcashSignature}; +use keystore::algorithms::secp256k1::{ + derive_public_key, get_public_key_by_seed, sign_message_by_seed, +}; +use keystore::algorithms::zcash::{calculate_seed_fingerprint, sign_message_orchard}; +use zcash_vendor::pczt::{ + common::Zip32Derivation, + pczt_ext::{PcztSigner, ZcashSignature}, +}; use crate::errors::ZcashError; @@ -12,19 +21,40 @@ struct SeedSigner { impl PcztSigner for SeedSigner { type Error = ZcashError; - fn sign_transparent(&self, hash: &[u8], path: String) -> Result { + fn sign_transparent( + &self, + hash: &[u8], + key_path: BTreeMap, Zip32Derivation>, + ) -> Result, ZcashSignature>, Self::Error> { let message = Message::from_digest_slice(hash).unwrap(); - sign_message_by_seed(&self.seed, &path, &message) - .map(|(_rec_id, signature)| ZcashSignature::from(signature)) - .map_err(|e| ZcashError::SigningError(e.to_string())) + let fingerprint = calculate_seed_fingerprint(&self.seed) + .map_err(|e| ZcashError::SigningError(e.to_string()))?; + + let mut result = BTreeMap::new(); + key_path.iter().try_for_each(|(pubkey, path)| { + let path_fingerprint = path.seed_fingerprint.clone(); + if fingerprint == path_fingerprint { + let my_pubkey = get_public_key_by_seed(&self.seed, &path.to_string()) + .map_err(|e| ZcashError::SigningError(e.to_string()))?; + if my_pubkey.serialize().to_vec().eq(pubkey) { + let signature = sign_message_by_seed(&self.seed, &path.to_string(), &message) + .map(|(rec_id, signature)| signature) + .map_err(|e| ZcashError::SigningError(e.to_string()))?; + result.insert(pubkey.clone(), signature); + } + } + Ok(()) + })?; + + Ok(result) } fn sign_sapling( &self, hash: &[u8], alpha: [u8; 32], - path: String, - ) -> Result { + path: Zip32Derivation, + ) -> Result, Self::Error> { // we don't support sapling yet Err(ZcashError::SigningError( "sapling not supported".to_string(), @@ -35,11 +65,19 @@ impl PcztSigner for SeedSigner { &self, hash: &[u8], alpha: [u8; 32], - path: String, - ) -> Result { - sign_message_orchard(&self.seed, alpha, hash, &path) - .map(|signature| ZcashSignature::from(signature)) - .map_err(|e| ZcashError::SigningError(e.to_string())) + path: Zip32Derivation, + ) -> Result, Self::Error> { + let fingerprint = calculate_seed_fingerprint(&self.seed) + .map_err(|e| ZcashError::SigningError(e.to_string()))?; + + let path_fingerprint = path.seed_fingerprint.clone(); + if fingerprint == path_fingerprint { + sign_message_orchard(&self.seed, alpha, hash, &path.to_string()) + .map(|signature| Some(signature)) + .map_err(|e| ZcashError::SigningError(e.to_string())) + } else { + Ok(None) + } } } @@ -47,7 +85,7 @@ impl PcztSigner for SeedSigner { mod tests { use alloc::{collections::btree_map::BTreeMap, vec}; use zcash_vendor::pczt::{ - common::Global, + common::{Global, Zip32Derivation}, orchard::{self, Action}, sapling, transparent, Pczt, Version, V5_TX_VERSION, V5_VERSION_GROUP_ID, }; @@ -55,11 +93,17 @@ mod tests { use super::*; extern crate std; + + const HARDENED_MASK: u32 = 0x8000_0000; + use std::println; #[test] fn test_pczt_sign() { let seed = hex::decode("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(); + + let fingerprint = hex::decode("a833c2361e2d72d8fef1ec19071a6433b5f3c0b8aafb82ce2930b2349ad985c5").unwrap(); + let signer = SeedSigner { seed: seed.try_into().unwrap(), }; @@ -71,14 +115,14 @@ mod tests { outputs: vec![], }, sapling: sapling::Bundle { - anchor: None, + anchor: [0; 32], spends: vec![], outputs: vec![], value_balance: 0, bsk: None, }, orchard: orchard::Bundle { - anchor: Some(hex::decode("a6c1ad5befd98da596ebe78491d76f76402f3400bf921f73a3b176bd70ab5000").unwrap().try_into().unwrap()), + anchor: hex::decode("a6c1ad5befd98da596ebe78491d76f76402f3400bf921f73a3b176bd70ab5000").unwrap().try_into().unwrap(), actions: vec![ Action { cv: hex::decode("4ac2480c13624d2b8aabf82ee808b4e4965d6c26efd9cfc9070f69e1a9a69609").unwrap().try_into().unwrap(), @@ -94,6 +138,10 @@ mod tests { nullifier: hex::decode("ef870733c09572b274782e32e28809c201a90c1e179ad78e88eb1477c7bd9631").unwrap().try_into().unwrap(), rk: hex::decode("7fe9364e043a92f893100dc09fc70f1a4faad022687767f8c3495a83a57e6726").unwrap().try_into().unwrap(), spend_auth_sig: None, + zip32_derivation: Some(Zip32Derivation { + seed_fingerprint: fingerprint.clone().try_into().unwrap(), + derivation_path: vec![HARDENED_MASK + 44, HARDENED_MASK + 133, HARDENED_MASK + 0], + }), }, output: orchard::Output { cmx: hex::decode("dbc7cc05319a4c70a6792ec195e99f1f3028194338953ee28f2f9426e06e1039").unwrap().try_into().unwrap(), @@ -106,6 +154,10 @@ mod tests { rseed: None, shared_secret: None, value: None, + zip32_derivation: Some(Zip32Derivation { + seed_fingerprint: fingerprint.clone().try_into().unwrap(), + derivation_path: vec![HARDENED_MASK + 44, HARDENED_MASK + 133, HARDENED_MASK + 0], + }), }, rcv: None, }, @@ -123,6 +175,10 @@ mod tests { nullifier: hex::decode("0e65a80237a3d3e1dcede4fe7632eec67254e0e1af721cd20fa8b9800263f508").unwrap().try_into().unwrap(), rk: hex::decode("afa2899a1fc1f5d16639e162979b29bedbf84aeb0987a2d8143d10134a47f722").unwrap().try_into().unwrap(), spend_auth_sig: None, + zip32_derivation: Some(Zip32Derivation { + seed_fingerprint: fingerprint.clone().try_into().unwrap(), + derivation_path: vec![HARDENED_MASK + 44, HARDENED_MASK + 133, HARDENED_MASK + 0], + }), }, output: orchard::Output { cmx: hex::decode("c7e391d7deb77891735e12be5f63e8821a79636a774578706bf495bef678072b").unwrap().try_into().unwrap(), @@ -135,6 +191,10 @@ mod tests { rseed: None, shared_secret: None, value: None, + zip32_derivation: Some(Zip32Derivation { + seed_fingerprint: fingerprint.clone().try_into().unwrap(), + derivation_path: vec![HARDENED_MASK + 44, HARDENED_MASK + 133, HARDENED_MASK + 0], + }), }, rcv: None, } diff --git a/rust/zcash_vendor/src/pczt/common.rs b/rust/zcash_vendor/src/pczt/common.rs index 96b53f1f0..2e9358b8c 100644 --- a/rust/zcash_vendor/src/pczt/common.rs +++ b/rust/zcash_vendor/src/pczt/common.rs @@ -1,4 +1,9 @@ -use alloc::{collections::BTreeMap, string::String, vec::Vec}; +use alloc::{ + collections::BTreeMap, + format, + string::{String, ToString}, + vec::Vec, +}; /// Global fields that are relevant to the transaction as a whole. #[derive(Clone, Debug)] @@ -49,3 +54,52 @@ impl Global { Some(self) } } + +pub const HARDENED_MASK: u32 = 0x8000_0000; + +#[derive(Clone, PartialEq, Debug)] +pub struct Zip32Derivation { + /// The [ZIP 32 seed fingerprint](https://zips.z.cash/zip-0032#seed-fingerprints). + pub seed_fingerprint: [u8; 32], + + /// The sequence of indices corresponding to the shielded HD path. + /// + /// Indices can be hardened or non-hardened (i.e. the hardened flag bit may be set). + pub derivation_path: Vec, +} + +impl ToString for Zip32Derivation { + fn to_string(&self) -> String { + let mut path = "m".to_string(); + for i in self.derivation_path.iter() { + if i & HARDENED_MASK != 0 { + path.push_str(&format!("/{}'", i - HARDENED_MASK)); + } else { + path.push_str(&format!("/{}", i)); + } + } + path + } +} + +#[cfg(test)] +mod tests { + use alloc::vec; + + use super::*; + + #[test] + fn test_zip32_derivation_to_string() { + let derivation = Zip32Derivation { + seed_fingerprint: [0; 32], + derivation_path: vec![ + HARDENED_MASK + 44, + HARDENED_MASK + 133, + HARDENED_MASK + 0, + 1, + 0, + ], + }; + assert_eq!(derivation.to_string(), "m/44'/133'/0'/1/0"); + } +} diff --git a/rust/zcash_vendor/src/pczt/mod.rs b/rust/zcash_vendor/src/pczt/mod.rs index 08a4d8fdf..dae8441b6 100644 --- a/rust/zcash_vendor/src/pczt/mod.rs +++ b/rust/zcash_vendor/src/pczt/mod.rs @@ -88,6 +88,8 @@ //! - Transaction Extractor //! - Creates bindingSig and extracts the final transaction. +use alloc::collections::btree_map::BTreeMap; + pub mod common; pub mod orchard; @@ -151,3 +153,26 @@ fn merge_optional(lhs: &mut Option, rhs: Option) -> bool { // Success! true } + +/// Merges two maps together. +/// +/// Returns `false` if the values cannot be merged. +pub(crate) fn merge_map( + lhs: &mut BTreeMap, + rhs: BTreeMap, +) -> bool { + for (key, rhs_value) in rhs.into_iter() { + if let Some(lhs_value) = lhs.get_mut(&key) { + // If the key is present in both maps, and their values are not equal, fail. + // Here we differ from BIP 174. + if lhs_value != &rhs_value { + return false; + } + } else { + lhs.insert(key, rhs_value); + } + } + + // Success! + true +} \ No newline at end of file diff --git a/rust/zcash_vendor/src/pczt/orchard.rs b/rust/zcash_vendor/src/pczt/orchard.rs index 51f83f9c1..3cd6957da 100644 --- a/rust/zcash_vendor/src/pczt/orchard.rs +++ b/rust/zcash_vendor/src/pczt/orchard.rs @@ -1,216 +1,72 @@ use alloc::{collections::BTreeMap, string::String, vec::Vec}; -use super::merge_optional; +use super::{common::Zip32Derivation, merge_map, merge_optional}; /// PCZT fields that are specific to producing the transaction's Orchard bundle (if any). #[derive(Clone, Debug)] pub struct Bundle { /// The Orchard actions in this bundle. - /// - /// Entries are added by the Constructor, and modified by an Updater, IO Finalizer, - /// Signer, Combiner, or Spend Finalizer. pub actions: Vec, /// The flags for the Orchard bundle. - /// - /// Contains: - /// - `enableSpendsOrchard` flag (bit 0) - /// - `enableOutputsOrchard` flag (bit 1) - /// - Reserved, zeros (bits 2..=7) - /// - /// This is set by the Creator. The Constructor MUST only add spends and outputs that - /// are consistent with these flags (i.e. are dummies as appropriate). pub flags: u8, /// The net value of Orchard spends minus outputs. - /// - /// This is initialized by the Creator, and updated by the Constructor as spends or - /// outputs are added to the PCZT. It enables per-spend and per-output values to be - /// redacted from the PCZT after they are no longer necessary. - pub value_balance: i64, + pub value_balance: u64, /// The Orchard anchor for this transaction. - /// - /// TODO: Should this be non-optional and set by the Creator (which would be simpler)? - /// Or do we need a separate role that picks the anchor, which runs before the - /// Constructor adds spends? - pub anchor: Option<[u8; 32]>, + pub anchor: [u8; 32], /// The Orchard bundle proof. - /// - /// This is `None` until it is set by the Prover. pub zkproof: Option>, /// The Orchard binding signature signing key. - /// - /// - This is `None` until it is set by the IO Finalizer. - /// - The Transaction Extractor uses this to produce the binding signature. pub bsk: Option<[u8; 32]>, } #[derive(Clone, Debug)] pub struct Action { - // - // Action effecting data. - // - // These are required fields that are part of the final transaction, and are filled in - // by the Constructor when adding an output. - // pub cv: [u8; 32], pub spend: Spend, pub output: Output, - - /// The value commitment randomness. - /// - /// - This is set by the Constructor. - /// - The IO Finalizer compresses it into the bsk. - /// - This is required by the Prover. - /// - This may be used by Signers to verify that the value correctly matches `cv`. - /// - /// This opens `cv` for all participants. For Signers who don't need this information, - /// or after proofs / signatures have been applied, this can be redacted. pub rcv: Option<[u8; 32]>, } /// Information about a Sapling spend within a transaction. #[derive(Clone, Debug)] pub struct Spend { - // - // Spend-specific Action effecting data. - // - // These are required fields that are part of the final transaction, and are filled in - // by the Constructor when adding an output. - // pub nullifier: [u8; 32], pub rk: [u8; 32], - - /// The spend authorization signature. - /// - /// This is set by the Signer. pub spend_auth_sig: Option<[u8; 64]>, - - /// The address that received the note being spent. - /// - /// - This is set by the Constructor (or Updater?). - /// - This is required by the Prover. pub recipient: Option<[u8; 43]>, - - /// The value of the input being spent. - /// - /// - This is required by the Prover. - /// - This may be used by Signers to verify that the value matches `cv`, and to - /// confirm the values and change involved in the transaction. - /// - /// This exposes the input value to all participants. For Signers who don't need this - /// information, or after signatures have been applied, this can be redacted. pub value: Option, - - /// The rho value for the note being spent. - /// - /// - This is set by the Constructor. - /// - This is required by the Prover. - /// - /// TODO: This could be merged with `rseed` into a tuple. `recipient` and `value` are - /// separate because they might need to be independently redacted. (For which role?) pub rho: Option<[u8; 32]>, - - /// The seed randomness for the note being spent. - /// - /// - This is set by the Constructor. - /// - This is required by the Prover. pub rseed: Option<[u8; 32]>, - - /// The full viewing key that received the note being spent. - /// - /// - This is set by the Updater. - /// - This is required by the Prover. pub fvk: Option<[u8; 96]>, - - /// A witness from the note to the bundle's anchor. - /// - /// - This is set by the Updater. - /// - This is required by the Prover. pub witness: Option<(u32, [[u8; 32]; 32])>, - - /// The spend authorization randomizer. - /// - /// - This is chosen by the Constructor. - /// - This is required by the Signer for creating `spend_auth_sig`, and may be used to - /// validate `rk`. - /// - After`zkproof` / `spend_auth_sig` has been set, this can be redacted. pub alpha: Option<[u8; 32]>, - - // TODO derivation path - - // TODO FROST - + pub zip32_derivation: Option, pub proprietary: BTreeMap>, } /// Information about an Orchard output within a transaction. #[derive(Clone, Debug)] pub struct Output { - // - // Output-specific Action effecting data. - // - // These are required fields that are part of the final transaction, and are filled in - // by the Constructor when adding an output. - // pub cmx: [u8; 32], pub ephemeral_key: [u8; 32], - /// TODO: Should it be possible to choose the memo _value_ after defining an Output? - pub enc_ciphertext: [u8; 580], - pub out_ciphertext: [u8; 80], - - /// The address that will receive the output. - /// - /// - This is set by the Constructor. - /// - This is required by the Prover. + pub enc_ciphertext: Vec, + pub out_ciphertext: Vec, pub recipient: Option<[u8; 43]>, - - /// The value of the output. - /// - /// This may be used by Signers to verify that the value matches `cv`, and to confirm - /// the values and change involved in the transaction. - /// - /// This exposes the value to all participants. For Signers who don't need this - /// information, we can drop the values and compress the rcvs into the bsk global. pub value: Option, - - /// The seed randomness for the output. - /// - /// - This is set by the Constructor. - /// - This is required by the Prover. - /// - /// TODO: This could instead be decrypted from `enc_ciphertext` if `shared_secret` - /// were required by the Prover. Likewise for `recipient` and `value`; is there ever a - /// need for these to be independently redacted though? pub rseed: Option<[u8; 32]>, - - /// The symmetric shared secret used to encrypt `enc_ciphertext`. - /// - /// This enables Signers to verify that `enc_ciphertext` is correctly encrypted (and - /// contains a note plaintext matching the public commitments), and to confirm the - /// value of the memo. pub shared_secret: Option<[u8; 32]>, - - /// The `ock` value used to encrypt `out_ciphertext`. - /// - /// This enables Signers to verify that `out_ciphertext` is correctly encrypted. - /// - /// This may be `None` if the Constructor added the output using an OVK policy of - /// "None", to make the output unrecoverable from the chain by the sender. pub ock: Option<[u8; 32]>, - - // TODO derivation path - + pub zip32_derivation: Option, pub proprietary: BTreeMap>, } impl Bundle { /// Merges this bundle with another. - /// - /// Returns `None` if the bundles have conflicting data. pub fn merge(mut self, other: Self) -> Option { // Destructure `other` to ensure we handle everything. let Self { @@ -251,8 +107,11 @@ impl Bundle { } } - if !(merge_optional(&mut self.anchor, anchor) && merge_optional(&mut self.zkproof, zkproof)) - { + if self.anchor != anchor { + return None; + } + + if !merge_optional(&mut self.zkproof, zkproof) { return None; } @@ -274,6 +133,7 @@ impl Bundle { fvk, witness, alpha, + zip32_derivation: spend_zip32_derivation, proprietary: spend_proprietary, }, output: @@ -287,6 +147,7 @@ impl Bundle { rseed: output_rseed, shared_secret, ock, + zip32_derivation: output_zip32_derivation, proprietary: output_proprietary, }, rcv, @@ -311,17 +172,19 @@ impl Bundle { && merge_optional(&mut lhs.spend.fvk, fvk) && merge_optional(&mut lhs.spend.witness, witness) && merge_optional(&mut lhs.spend.alpha, alpha) + && merge_optional(&mut lhs.spend.zip32_derivation, spend_zip32_derivation) + && merge_map(&mut lhs.spend.proprietary, spend_proprietary) && merge_optional(&mut lhs.output.recipient, output_recipient) && merge_optional(&mut lhs.output.value, output_value) && merge_optional(&mut lhs.output.rseed, output_rseed) && merge_optional(&mut lhs.output.shared_secret, shared_secret) && merge_optional(&mut lhs.output.ock, ock) + && merge_optional(&mut lhs.output.zip32_derivation, output_zip32_derivation) + && merge_map(&mut lhs.output.proprietary, output_proprietary) && merge_optional(&mut lhs.rcv, rcv)) { return None; } - - // TODO: Decide how to merge proprietary fields. } Some(self) diff --git a/rust/zcash_vendor/src/pczt/pczt_ext.rs b/rust/zcash_vendor/src/pczt/pczt_ext.rs index b5d88a485..6a8612ede 100644 --- a/rust/zcash_vendor/src/pczt/pczt_ext.rs +++ b/rust/zcash_vendor/src/pczt/pczt_ext.rs @@ -1,11 +1,15 @@ use crate::pczt::Pczt; use crate::zcash_encoding::WriteBytesExt; +use alloc::collections::btree_map::BTreeMap; use alloc::string::String; use alloc::string::ToString; +use alloc::vec::Vec; use blake2b_simd::{Hash, Params, State}; use byteorder::LittleEndian; use pasta_curves::Fq; +use super::common::Zip32Derivation; +use super::merge_map; use super::transparent::{Input, Output}; /// TxId tree root personalization @@ -75,19 +79,23 @@ pub type ZcashSignature = [u8; 64]; pub trait PcztSigner { type Error; - fn sign_transparent(&self, hash: &[u8], path: String) -> Result; + fn sign_transparent( + &self, + hash: &[u8], + key_path: BTreeMap, Zip32Derivation>, + ) -> Result, ZcashSignature>, Self::Error>; fn sign_sapling( &self, hash: &[u8], alpha: [u8; 32], - path: String, - ) -> Result; + path: Zip32Derivation, + ) -> Result, Self::Error>; fn sign_orchard( &self, hash: &[u8], alpha: [u8; 32], - path: String, - ) -> Result; + path: Zip32Derivation, + ) -> Result, Self::Error>; } impl Pczt { @@ -102,11 +110,11 @@ impl Pczt { } fn has_sapling(&self) -> bool { - self.sapling.anchor.is_some() + !self.sapling.spends.is_empty() && !self.sapling.outputs.is_empty() } fn has_orchard(&self) -> bool { - self.orchard.anchor.is_some() + !self.orchard.actions.is_empty() } fn digest_header(&self) -> Hash { @@ -192,7 +200,7 @@ impl Pczt { h.update(nh.finalize().as_bytes()); h.update(&[self.orchard.flags]); h.update(&self.orchard.value_balance.to_le_bytes()); - h.update(&self.orchard.anchor.unwrap()); + h.update(&self.orchard.anchor); h.finalize() } @@ -206,7 +214,7 @@ impl Pczt { ch.update(&s_spend.nullifier); nh.update(&s_spend.cv); - nh.update(&self.sapling.anchor.unwrap()); + nh.update(&self.sapling.anchor); nh.update(&s_spend.rk); } @@ -351,32 +359,40 @@ impl Pczt { .iter_mut() .enumerate() .try_for_each(|(i, input)| { - let signature = signer.sign_transparent( + let signatures = signer.sign_transparent( self.transparent_sig_digest(Some((input, i as u32))) .as_bytes(), - "".to_string(), + input.bip32_derivation.clone(), )?; - input - .signatures - .insert(input.script_pubkey.clone(), signature.to_vec()); + merge_map( + &mut input.partial_signatures, + signatures + .iter() + .map(|(pubkey, signature)| (pubkey.clone(), signature.to_vec())) + .collect(), + ); Ok(()) })?; pczt.sapling.spends.iter_mut().try_for_each(|spend| { - let signature = signer.sign_sapling( - self.sheilded_sig_commitment().as_bytes(), - pczt.sapling.anchor.unwrap(), - "".to_string(), - )?; - spend.spend_auth_sig = Some(signature); + if let Some(ref d) = spend.zip32_derivation { + let signature = signer.sign_sapling( + self.sheilded_sig_commitment().as_bytes(), + pczt.sapling.anchor, + d.clone(), + )?; + spend.spend_auth_sig = signature; + } Ok(()) })?; pczt.orchard.actions.iter_mut().try_for_each(|action| { - let signature = signer.sign_orchard( - self.sheilded_sig_commitment().as_bytes(), - action.spend.alpha.unwrap(), - "m/44'/133'/0'".to_string(), - )?; - action.spend.spend_auth_sig = Some(signature); + if let Some(ref d) = action.spend.zip32_derivation { + let signature = signer.sign_orchard( + self.sheilded_sig_commitment().as_bytes(), + action.spend.alpha.unwrap(), + d.clone(), + )?; + action.spend.spend_auth_sig = signature; + } Ok(()) })?; Ok(pczt) @@ -391,6 +407,7 @@ mod tests { use secp256k1::Message; use std::println; + use crate::pczt::common::Zip32Derivation; use crate::pczt::{ self, common::Global, @@ -398,6 +415,8 @@ mod tests { sapling, transparent, Version, V5_TX_VERSION, V5_VERSION_GROUP_ID, }; + const HARDENED_MASK: u32 = 0x8000_0000; + use super::*; #[test] @@ -417,14 +436,14 @@ mod tests { outputs: vec![], }, sapling: sapling::Bundle { - anchor: None, + anchor: [0; 32], spends: vec![], outputs: vec![], value_balance: 0, bsk: None, }, orchard: orchard::Bundle { - anchor: Some(hex::decode("ed3e3e7dd1c81ac9cc31cd69c213939b2e21067758d4bd7dc9c2fed1eaf95829").unwrap().try_into().unwrap()), + anchor: hex::decode("ed3e3e7dd1c81ac9cc31cd69c213939b2e21067758d4bd7dc9c2fed1eaf95829").unwrap().try_into().unwrap(), actions: vec![ Action { cv: hex::decode("2262e5f410e151d1f373224cfa45f6287ab7cad2fef81e2926c1c8e052388e07").unwrap().try_into().unwrap(), @@ -440,6 +459,10 @@ mod tests { nullifier: hex::decode("f35440b9ef04865f982a9e74a46a66864df9999070d1611a4fae263cb1cf5211").unwrap().try_into().unwrap(), rk: hex::decode("9e196d6d045d1d43a00100bca908a609e3411cdf5fef2fd89e23f2e60c43540a").unwrap().try_into().unwrap(), spend_auth_sig: None, + zip32_derivation: Some(Zip32Derivation { + seed_fingerprint: [0; 32], + derivation_path: vec![HARDENED_MASK + 44, HARDENED_MASK + 133, HARDENED_MASK + 0], + }), }, output: orchard::Output { cmx: hex::decode("0b4ca8a1c5c626285ef039069d7147370d512dd0ef94df8430b703701a978d06").unwrap().try_into().unwrap(), @@ -452,6 +475,10 @@ mod tests { rseed: None, shared_secret: None, value: None, + zip32_derivation: Some(Zip32Derivation { + seed_fingerprint: [0; 32], + derivation_path: vec![HARDENED_MASK + 44, HARDENED_MASK + 133, HARDENED_MASK + 0], + }), }, rcv: None, }, @@ -469,6 +496,10 @@ mod tests { nullifier: hex::decode("dbf349555524523f0edbc811adb445ed3e79d8d5a94fe29c3a682381c571c123").unwrap().try_into().unwrap(), rk: hex::decode("9d566b785aee161d20342e7b805facf2e9c103ab36ce3453ccf2161bc0da9d8c").unwrap().try_into().unwrap(), spend_auth_sig: None, + zip32_derivation: Some(Zip32Derivation { + seed_fingerprint: [0; 32], + derivation_path: vec![HARDENED_MASK + 44, HARDENED_MASK + 133, HARDENED_MASK + 0], + }) }, output: orchard::Output { cmx: hex::decode("40ce12b40aa59c0170f9440e36152509f9191a5b21c0378c6eb02e5ee530a935").unwrap().try_into().unwrap(), @@ -481,6 +512,10 @@ mod tests { rseed: None, shared_secret: None, value: None, + zip32_derivation: Some(Zip32Derivation { + seed_fingerprint: [0; 32], + derivation_path: vec![HARDENED_MASK + 44, HARDENED_MASK + 133, HARDENED_MASK + 0], + }), }, rcv: None, } diff --git a/rust/zcash_vendor/src/pczt/sapling.rs b/rust/zcash_vendor/src/pczt/sapling.rs index 28f27dd72..a6dc91cbe 100644 --- a/rust/zcash_vendor/src/pczt/sapling.rs +++ b/rust/zcash_vendor/src/pczt/sapling.rs @@ -1,6 +1,6 @@ use alloc::{collections::BTreeMap, string::String, vec::Vec}; -use super::merge_optional; +use super::{common::Zip32Derivation, merge_map, merge_optional}; const GROTH_PROOF_SIZE: usize = 48 + 96 + 48; @@ -19,10 +19,8 @@ pub struct Bundle { /// The Sapling anchor for this transaction. /// - /// TODO: Should this be non-optional and set by the Creator (which would be simpler)? - /// Or do we need a separate role that picks the anchor, which runs before the - /// Constructor adds spends? - pub anchor: Option<[u8; 32]>, + /// Set by the Creator. + pub anchor: [u8; 32], /// The Sapling binding signature signing key. /// @@ -107,9 +105,9 @@ pub struct Spend { /// - After`zkproof` / `spend_auth_sig` has been set, this can be redacted. pub alpha: Option<[u8; 32]>, - // TODO derivation path - - // TODO FROST + /// The ZIP 32 derivation path at which the spending key can be found for the note + /// being spent. + pub zip32_derivation: Option, pub proprietary: BTreeMap>, } @@ -126,9 +124,17 @@ pub struct Output { pub cv: [u8; 32], pub cmu: [u8; 32], pub ephemeral_key: [u8; 32], - /// TODO: Should it be possible to choose the memo _value_ after defining an Output? - pub enc_ciphertext: [u8; 580], - pub out_ciphertext: [u8; 80], + /// The encrypted note plaintext for the output. + /// + /// Encoded as a `Vec` because its length depends on the transaction version. + /// + /// Once we have memo bundles, we will be able to set memos independently of Outputs. + /// For now, the Constructor sets both at the same time. + pub enc_ciphertext: Vec, + /// The encrypted note plaintext for the output. + /// + /// Encoded as a `Vec` because its length depends on the transaction version. + pub out_ciphertext: Vec, /// The Output proof. /// @@ -153,11 +159,7 @@ pub struct Output { /// The seed randomness for the output. /// /// - This is set by the Constructor. - /// - This is required by the Prover. - /// - /// TODO: This could instead be decrypted from `enc_ciphertext` if `shared_secret` - /// were required by the Prover. Likewise for `recipient` and `value`; is there ever a - /// need for these to be independently redacted though? + /// - This is required by the Prover, instead of disclosing `shared_secret` to them. pub rseed: Option<[u8; 32]>, /// The value commitment randomness. @@ -186,7 +188,8 @@ pub struct Output { /// "None", to make the output unrecoverable from the chain by the sender. pub ock: Option<[u8; 32]>, - // TODO derivation path + /// The ZIP 32 derivation path at which the spending key can be found for the output. + pub zip32_derivation: Option, pub proprietary: BTreeMap>, } @@ -234,7 +237,7 @@ impl Bundle { } } - if !merge_optional(&mut self.anchor, anchor) { + if self.anchor != anchor { return None; } @@ -255,6 +258,7 @@ impl Bundle { proof_generation_key, witness, alpha, + zip32_derivation, proprietary, } = rhs; @@ -270,12 +274,12 @@ impl Bundle { && merge_optional(&mut lhs.rcv, rcv) && merge_optional(&mut lhs.proof_generation_key, proof_generation_key) && merge_optional(&mut lhs.witness, witness) - && merge_optional(&mut lhs.alpha, alpha)) + && merge_optional(&mut lhs.alpha, alpha) + && merge_optional(&mut lhs.zip32_derivation, zip32_derivation) + && merge_map(&mut lhs.proprietary, proprietary)) { return None; } - - // TODO: Decide how to merge proprietary fields. } for (lhs, rhs) in self.outputs.iter_mut().zip(outputs.into_iter()) { @@ -293,6 +297,7 @@ impl Bundle { rcv, shared_secret, ock, + zip32_derivation, proprietary, } = rhs; @@ -311,12 +316,12 @@ impl Bundle { && merge_optional(&mut lhs.rseed, rseed) && merge_optional(&mut lhs.rcv, rcv) && merge_optional(&mut lhs.shared_secret, shared_secret) - && merge_optional(&mut lhs.ock, ock)) + && merge_optional(&mut lhs.ock, ock) + && merge_optional(&mut lhs.zip32_derivation, zip32_derivation) + && merge_map(&mut lhs.proprietary, proprietary)) { return None; } - - // TODO: Decide how to merge proprietary fields. } Some(self) diff --git a/rust/zcash_vendor/src/pczt/transparent.rs b/rust/zcash_vendor/src/pczt/transparent.rs index 7a63011c7..a830aa202 100644 --- a/rust/zcash_vendor/src/pczt/transparent.rs +++ b/rust/zcash_vendor/src/pczt/transparent.rs @@ -1,6 +1,6 @@ use alloc::{collections::BTreeMap, string::String, vec::Vec}; -use super::merge_optional; +use super::{common::Zip32Derivation, merge_map, merge_optional}; /// PCZT fields that are specific to producing the transaction's transparent bundle (if /// any). @@ -23,6 +23,9 @@ pub struct Input { /// TODO: which role should set this? pub sequence: u32, + /// TODO: Both time-based and height-based? + pub required_locktime: u32, + /// A satisfying witness for the `script_pubkey` of the input being spent. /// /// This is set by the Spend Finalizer. @@ -33,15 +36,62 @@ pub struct Input { pub value: u64, pub script_pubkey: Vec, + /// The script required to spend this output, if it is P2SH. + /// + /// Set to `None` if this is a P2PKH output. + pub redeem_script: Option>, + /// A map from a pubkey to a signature created by it. /// - /// - Each entry is set by a Signer. + /// - Each pubkey should appear in `script_pubkey` or `redeem_script`. + /// - Each entry is set by a Signer, and should contain an ECDSA signature that is + /// valid under the corresponding pubkey. /// - These are required by the Spend Finalizer to assemble `script_sig`. /// /// TODO: Decide on map key type. - pub signatures: BTreeMap, Vec>, + pub partial_signatures: BTreeMap, Vec>, + + /// The sighash type to be used for this input. + /// + /// - Signers must use this sighash type to produce their signatures. Signers that + /// cannot produce signatures for this sighash type must not provide a signature. + /// - Spend Finalizers must fail to finalize inputs which have signatures that do not + /// match this sighash type. + pub sighash_type: u32, + + /// A map from a pubkey to the BIP 32 derivation path at which its corresponding + /// spending key can be found. + /// + /// - The pubkeys should appear in `script_pubkey` or `redeem_script`. + /// - Each entry is set by an Updater. + /// - Individual entries may be required by a Signer. + /// + /// TODO: Decide on map key type. + pub bip32_derivation: BTreeMap, Zip32Derivation>, - // TODO derivation path + /// Mappings of the form `key = RIPEMD160(value)`. + /// + /// - These may be used by the Signer to inspect parts of `script_pubkey` or + /// `redeem_script`. + pub ripemd160_preimages: BTreeMap<[u8; 20], Vec>, + + /// Mappings of the form `key = SHA256(value)`. + /// + /// - These may be used by the Signer to inspect parts of `script_pubkey` or + /// `redeem_script`. + pub sha256_preimages: BTreeMap<[u8; 32], Vec>, + + /// Mappings of the form `key = RIPEMD160(SHA256(value))`. + /// + /// - These may be used by the Signer to inspect parts of `script_pubkey` or + /// `redeem_script`. + pub hash160_preimages: BTreeMap<[u8; 20], Vec>, + + /// Mappings of the form `key = SHA256(SHA256(value))`. + /// + /// - These may be used by the Signer to inspect parts of `script_pubkey` or + /// `redeem_script`. + pub hash256_preimages: BTreeMap<[u8; 32], Vec>, pub proprietary: BTreeMap>, } @@ -54,19 +104,32 @@ pub struct Output { // These are required fields that are part of the final transaction, and are filled in // by the Constructor when adding an output. // - pub value: u64, - pub script_pubkey: Vec, + pub(crate) value: u64, + pub(crate) script_pubkey: Vec, - // TODO derivation path + /// The script required to spend this output, if it is P2SH. + /// + /// Set to `None` if this is a P2PKH output. + pub(crate) redeem_script: Option>, - pub proprietary: BTreeMap>, + /// A map from a pubkey to the BIP 32 derivation path at which its corresponding + /// spending key can be found. + /// + /// - The pubkeys should appear in `script_pubkey` or `redeem_script`. + /// - Each entry is set by an Updater. + /// - Individual entries may be required by a Signer. + /// + /// TODO: Decide on map key type. + pub(crate) bip32_derivation: BTreeMap, Zip32Derivation>, + + pub(crate) proprietary: BTreeMap>, } impl Bundle { /// Merges this bundle with another. /// /// Returns `None` if the bundles have conflicting data. - pub fn merge(mut self, other: Self) -> Option { + pub(crate) fn merge(mut self, other: Self) -> Option { // Destructure `other` to ensure we handle everything. let Self { mut inputs, @@ -86,29 +149,44 @@ impl Bundle { prevout_txid, prevout_index, sequence, + required_locktime, script_sig, value, script_pubkey, - signatures, + redeem_script, + partial_signatures, + sighash_type, + bip32_derivation, + ripemd160_preimages, + sha256_preimages, + hash160_preimages, + hash256_preimages, proprietary, } = rhs; if lhs.prevout_txid != prevout_txid || lhs.prevout_index != prevout_index || lhs.sequence != sequence + || lhs.required_locktime != required_locktime || lhs.value != value || lhs.script_pubkey != script_pubkey + || lhs.sighash_type != sighash_type { return None; } - if !merge_optional(&mut lhs.script_sig, script_sig) { + if !(merge_optional(&mut lhs.script_sig, script_sig) + && merge_optional(&mut lhs.redeem_script, redeem_script) + && merge_map(&mut lhs.partial_signatures, partial_signatures) + && merge_map(&mut lhs.bip32_derivation, bip32_derivation) + && merge_map(&mut lhs.ripemd160_preimages, ripemd160_preimages) + && merge_map(&mut lhs.sha256_preimages, sha256_preimages) + && merge_map(&mut lhs.hash160_preimages, hash160_preimages) + && merge_map(&mut lhs.hash256_preimages, hash256_preimages) + && merge_map(&mut lhs.proprietary, proprietary)) + { return None; } - - // TODO: Merge signature maps. - - // TODO: Decide how to merge proprietary fields. } for (lhs, rhs) in self.outputs.iter_mut().zip(outputs.into_iter()) { @@ -116,6 +194,8 @@ impl Bundle { let Output { value, script_pubkey, + redeem_script, + bip32_derivation, proprietary, } = rhs; @@ -123,7 +203,12 @@ impl Bundle { return None; } - // TODO: Decide how to merge proprietary fields. + if !(merge_optional(&mut lhs.redeem_script, redeem_script) + && merge_map(&mut lhs.bip32_derivation, bip32_derivation) + && merge_map(&mut lhs.proprietary, proprietary)) + { + return None; + } } Some(self)