diff --git a/src/bundle.rs b/src/bundle.rs index 550b42ca..2df9d239 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -2,6 +2,7 @@ pub mod commitments; +use std::convert::TryInto; use std::io; use blake2b_simd::Hash as Blake2bHash; @@ -298,15 +299,6 @@ impl Bundle { &self.authorization } - /// Computes a commitment to the effects of this bundle, suitable for inclusion within - /// a transaction ID. - pub fn commitment<'a>(&'a self) -> BundleCommitment - where - i64: From<&'a V>, - { - BundleCommitment(hash_bundle_txid_data(self)) - } - /// Construct a new bundle by applying a transformation that might fail /// to the value balance. pub fn try_map_value_balance Result>( @@ -405,7 +397,13 @@ impl Bundle { } } -impl> Bundle { +impl> Bundle { + /// Computes a commitment to the effects of this bundle, suitable for inclusion within + /// a transaction ID. + pub fn commitment(&self) -> BundleCommitment { + BundleCommitment(hash_bundle_txid_data(self)) + } + /// Returns the transaction binding validating key for this bundle. /// /// This can be used to validate the [`Authorized::binding_signature`] returned from @@ -416,7 +414,10 @@ impl> Bundle { .iter() .map(|a| a.cv_net()) .sum::() - - ValueCommitment::derive(self.value_balance.into(), ValueCommitTrapdoor::zero())) + - ValueCommitment::derive( + ValueSum::from_raw(self.value_balance.into()), + ValueCommitTrapdoor::zero(), + )) .into_bvk() } } @@ -500,6 +501,13 @@ impl DynamicUsage for Bundle { #[derive(Debug)] pub struct BundleCommitment(pub Blake2bHash); +impl From for [u8; 32] { + fn from(commitment: BundleCommitment) -> Self { + // The commitment uses BLAKE2b-256. + commitment.0.as_bytes().try_into().unwrap() + } +} + /// A commitment to the authorizing data within a bundle of actions. #[derive(Debug)] pub struct BundleAuthorizingCommitment(pub Blake2bHash); diff --git a/src/bundle/commitments.rs b/src/bundle/commitments.rs index 2b0334b0..548afda1 100644 --- a/src/bundle/commitments.rs +++ b/src/bundle/commitments.rs @@ -28,10 +28,9 @@ fn hasher(personal: &[u8; 16]) -> State { /// personalized with ZCASH_ORCHARD_ACTIONS_HASH_PERSONALIZATION /// /// [zip244]: https://zips.z.cash/zip-0244 -pub fn hash_bundle_txid_data<'a, A: Authorization, V>(bundle: &'a Bundle) -> Blake2bHash -where - i64: From<&'a V>, -{ +pub fn hash_bundle_txid_data>( + bundle: &Bundle, +) -> Blake2bHash { let mut h = hasher(ZCASH_ORCHARD_HASH_PERSONALIZATION); let mut ch = hasher(ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION); let mut mh = hasher(ZCASH_ORCHARD_ACTIONS_MEMOS_HASH_PERSONALIZATION); @@ -59,7 +58,7 @@ where h.write_all(&mh.finalize().as_bytes()).unwrap(); h.write_all(&nh.finalize().as_bytes()).unwrap(); h.write_all(&[bundle.flags().to_byte()]).unwrap(); - h.write_all(&::from(bundle.value_balance()).to_le_bytes()) + h.write_all(&(*bundle.value_balance()).into().to_le_bytes()) .unwrap(); h.write_all(&bundle.anchor().to_bytes()).unwrap(); h.finalize() diff --git a/src/primitives/redpallas.rs b/src/primitives/redpallas.rs index cbdae803..903de725 100644 --- a/src/primitives/redpallas.rs +++ b/src/primitives/redpallas.rs @@ -147,6 +147,13 @@ impl VerificationKey { } } +impl VerificationKey { + /// Verifies a purported `signature` over `msg` made by this verification key. + pub fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), reddsa::Error> { + self.0.verify(msg, &signature.0) + } +} + /// A RedPallas signature. #[derive(Debug, Clone)] pub struct Signature(reddsa::Signature); diff --git a/src/tree.rs b/src/tree.rs index c3b3f08b..754ccf27 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -107,11 +107,19 @@ impl MerklePath { /// Instantiates a new Merkle path given a leaf position and authentication path. pub(crate) fn new(position: u32, auth_path: [pallas::Base; MERKLE_DEPTH_ORCHARD]) -> Self { - Self { + Self::from_parts( position, - auth_path: gen_const_array_with_default(MerkleHashOrchard::empty_leaf(), |i| { + gen_const_array_with_default(MerkleHashOrchard::empty_leaf(), |i| { MerkleHashOrchard(auth_path[i]) }), + ) + } + + /// Instantiates a new Merkle path given a leaf position and authentication path. + pub fn from_parts(position: u32, auth_path: [MerkleHashOrchard; MERKLE_DEPTH_ORCHARD]) -> Self { + Self { + position, + auth_path, } } diff --git a/tests/builder.rs b/tests/builder.rs new file mode 100644 index 00000000..0c375da7 --- /dev/null +++ b/tests/builder.rs @@ -0,0 +1,102 @@ +use std::convert::TryInto; + +use incrementalmerkletree::{bridgetree::BridgeTree, Frontier, Hashable, Tree}; +use orchard::{ + builder::Builder, + bundle::{Authorized, Flags}, + circuit::{ProvingKey, VerifyingKey}, + keys::{FullViewingKey, IncomingViewingKey, SpendAuthorizingKey, SpendingKey}, + note::ExtractedNoteCommitment, + note_encryption::OrchardDomain, + tree::{MerkleHashOrchard, MerklePath}, + value::NoteValue, + Bundle, +}; +use rand::rngs::OsRng; +use zcash_note_encryption::try_note_decryption; + +fn verify_bundle(bundle: &Bundle, vk: &VerifyingKey) { + assert!(matches!(bundle.verify_proof(&vk), Ok(()))); + let sighash: [u8; 32] = bundle.commitment().into(); + let bvk = bundle.binding_validating_key(); + for action in bundle.actions() { + assert_eq!(action.rk().verify(&sighash, action.authorization()), Ok(())); + } + assert_eq!( + bvk.verify(&sighash, bundle.authorization().binding_signature()), + Ok(()) + ); +} + +#[test] +fn bundle_chain() { + let mut rng = OsRng; + let pk = ProvingKey::build(); + let vk = VerifyingKey::build(); + + let sk = SpendingKey::from_bytes([0; 32]).unwrap(); + let fvk = FullViewingKey::from(&sk); + let recipient = fvk.address_at(0u32); + + // Create a shielding bundle. + let shielding_bundle: Bundle<_, i64> = { + // Use the empty tree. + let anchor = MerkleHashOrchard::empty_root(32.into()).into(); + + let mut builder = Builder::new(Flags::from_parts(false, true), anchor); + assert_eq!( + builder.add_recipient(None, recipient, NoteValue::from_raw(5000), None), + Ok(()) + ); + let unauthorized = builder.build(&mut rng).unwrap(); + let sighash = unauthorized.commitment().into(); + let proven = unauthorized.create_proof(&pk).unwrap(); + proven.apply_signatures(&mut rng, sighash, &[]).unwrap() + }; + + // Verify the shielding bundle. + verify_bundle(&shielding_bundle, &vk); + + // Create a shielded bundle spending the previous output. + let shielded_bundle: Bundle<_, i64> = { + let ivk = IncomingViewingKey::from(&fvk); + let (note, _, _) = shielding_bundle + .actions() + .iter() + .find_map(|action| { + let domain = OrchardDomain::for_action(action); + try_note_decryption(&domain, &ivk, action) + }) + .unwrap(); + + // Use the tree with a single leaf. + let cmx: ExtractedNoteCommitment = note.commitment().into(); + let leaf = MerkleHashOrchard::from_cmx(&cmx); + let mut tree = BridgeTree::::new(0); + tree.append(&leaf); + tree.witness(); + let (position, auth_path) = tree.authentication_path(&leaf).unwrap(); + let merkle_path = MerklePath::from_parts( + u64::from(position).try_into().unwrap(), + auth_path[..].try_into().unwrap(), + ); + let anchor = tree.root().into(); + assert_eq!(anchor, merkle_path.root(cmx)); + + let mut builder = Builder::new(Flags::from_parts(true, true), anchor); + assert_eq!(builder.add_spend(fvk, note, merkle_path), Ok(())); + assert_eq!( + builder.add_recipient(None, recipient, NoteValue::from_raw(5000), None), + Ok(()) + ); + let unauthorized = builder.build(&mut rng).unwrap(); + let sighash = unauthorized.commitment().into(); + let proven = unauthorized.create_proof(&pk).unwrap(); + proven + .apply_signatures(&mut rng, sighash, &[SpendAuthorizingKey::from(&sk)]) + .unwrap() + }; + + // Verify the shielded bundle. + verify_bundle(&shielded_bundle, &vk); +}