From aa0c0ecbecbb61b708434e4158464f8516a9e4b8 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 1 Jul 2021 10:10:24 -0600 Subject: [PATCH 1/2] Implement ZIP-244 txid and authorizing commitments. --- src/bundle.rs | 55 ++++++++++++++++++-- src/bundle/commitments.rs | 102 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+), 5 deletions(-) create mode 100644 src/bundle/commitments.rs diff --git a/src/bundle.rs b/src/bundle.rs index 6c10fdf3..2296079f 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -1,10 +1,15 @@ //! Structs related to bundles of Orchard actions. +pub mod commitments; + +use std::io; use std::mem; +use blake2b_simd::Hash as Blake2bHash; use nonempty::NonEmpty; use crate::{ + bundle::commitments::{hash_bundle_auth_data, hash_bundle_txid_data}, circuit::{Instance, Proof, VerifyingKey}, note::{ExtractedNoteCommitment, Nullifier, TransmittedNoteCiphertext}, primitives::redpallas::{self, Binding, SpendAuth}, @@ -140,6 +145,10 @@ pub struct Flags { outputs_enabled: bool, } +const FLAG_SPENDS_ENABLED: u8 = 0b0000_0001; +const FLAG_OUTPUTS_ENABLED: u8 = 0b0000_0010; +const FLAGS_EXPECTED_UNSET: u8 = !(FLAG_SPENDS_ENABLED | FLAG_OUTPUTS_ENABLED); + impl Flags { /// Construct a set of flags from its constituent parts pub fn from_parts(spends_enabled: bool, outputs_enabled: bool) -> Self { @@ -166,6 +175,39 @@ impl Flags { pub fn outputs_enabled(&self) -> bool { self.outputs_enabled } + + /// Serialize flags to a byte as defined in [Zcash Protocol Spec § 7.1: Transaction + /// Encoding And Consensus][txencoding]. + /// + /// [txencoding]: https://zips.z.cash/protocol/nu5.pdf#txnencoding + pub fn to_byte(&self) -> u8 { + let mut value = 0u8; + if self.spends_enabled { + value |= FLAG_SPENDS_ENABLED; + } + if self.outputs_enabled { + value |= FLAG_OUTPUTS_ENABLED; + } + value + } + + /// Parse from a single byte as defined in [Zcash Protocol Spec § 7.1: Transaction + /// Encoding And Consensus][txencoding]. + /// + /// [txencoding]: https://zips.z.cash/protocol/nu5.pdf#txnencoding + pub fn from_byte(value: u8) -> io::Result { + if value & FLAGS_EXPECTED_UNSET == 0 { + Ok(Self::from_parts( + value & FLAG_SPENDS_ENABLED != 0, + value & FLAG_OUTPUTS_ENABLED != 0, + )) + } else { + Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Unexpected bits set in Orchard flags value.", + )) + } + } } /// Defines the authorization type of an Orchard bundle. @@ -240,8 +282,11 @@ impl Bundle { /// Computes a commitment to the effects of this bundle, suitable for inclusion within /// a transaction ID. - pub fn commitment(&self) -> BundleCommitment { - todo!() + 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 @@ -368,7 +413,7 @@ impl Bundle { /// /// This together with `Bundle::commitment` bind the entire bundle. pub fn authorizing_commitment(&self) -> BundleAuthorizingCommitment { - todo!() + BundleAuthorizingCommitment(hash_bundle_auth_data(self)) } /// Verifies the proof for this bundle. @@ -384,11 +429,11 @@ impl Bundle { /// This commitment is non-malleable, in the sense that a bundle's commitment will only /// change if the effects of the bundle are altered. #[derive(Debug)] -pub struct BundleCommitment; +pub struct BundleCommitment(pub Blake2bHash); /// A commitment to the authorizing data within a bundle of actions. #[derive(Debug)] -pub struct BundleAuthorizingCommitment; +pub struct BundleAuthorizingCommitment(pub Blake2bHash); /// Generators for property testing. #[cfg(any(test, feature = "test-dependencies"))] diff --git a/src/bundle/commitments.rs b/src/bundle/commitments.rs new file mode 100644 index 00000000..2b0334b0 --- /dev/null +++ b/src/bundle/commitments.rs @@ -0,0 +1,102 @@ +//! Utility functions for computing bundle commitments + +use blake2b_simd::{Hash as Blake2bHash, Params, State}; +use std::io::Write; + +use crate::bundle::{Authorization, Authorized, Bundle}; + +const ZCASH_ORCHARD_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdOrchardHash"; +const ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdOrcActCHash"; +const ZCASH_ORCHARD_ACTIONS_MEMOS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdOrcActMHash"; +const ZCASH_ORCHARD_ACTIONS_NONCOMPACT_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxIdOrcActNHash"; +const ZCASH_ORCHARD_SIGS_HASH_PERSONALIZATION: &[u8; 16] = b"ZTxAuthOrchaHash"; + +fn hasher(personal: &[u8; 16]) -> State { + Params::new().hash_length(32).personal(personal).to_state() +} + +/// Write disjoint parts of each Orchard shielded action as 3 separate hashes: +/// * \[(nullifier, cmx, ephemeral_key, enc_ciphertext\[..52\])*\] personalized +/// with ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION +/// * \[enc_ciphertext\[52..564\]*\] (memo ciphertexts) personalized +/// with ZCASH_ORCHARD_ACTIONS_MEMOS_HASH_PERSONALIZATION +/// * \[(cv, rk, enc_ciphertext\[564..\], out_ciphertext)*\] personalized +/// with ZCASH_ORCHARD_ACTIONS_NONCOMPACT_HASH_PERSONALIZATION +/// as defined in [ZIP-244: Transaction Identifier Non-Malleability][zip244] +/// +/// Then, hash these together along with (flags, value_balance_orchard, anchor_orchard), +/// 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>, +{ + 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); + let mut nh = hasher(ZCASH_ORCHARD_ACTIONS_NONCOMPACT_HASH_PERSONALIZATION); + + for action in bundle.actions().iter() { + ch.write_all(&action.nullifier().to_bytes()).unwrap(); + ch.write_all(&action.cmx().to_bytes()).unwrap(); + ch.write_all(&action.encrypted_note().epk_bytes).unwrap(); + ch.write_all(&action.encrypted_note().enc_ciphertext[..52]) + .unwrap(); + + mh.write_all(&action.encrypted_note().enc_ciphertext[52..564]) + .unwrap(); + + nh.write_all(&action.cv_net().to_bytes()).unwrap(); + nh.write_all(&<[u8; 32]>::from(action.rk())).unwrap(); + nh.write_all(&action.encrypted_note().enc_ciphertext[564..]) + .unwrap(); + nh.write_all(&action.encrypted_note().out_ciphertext) + .unwrap(); + } + + h.write_all(&ch.finalize().as_bytes()).unwrap(); + 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()) + .unwrap(); + h.write_all(&bundle.anchor().to_bytes()).unwrap(); + h.finalize() +} + +/// Construct the commitment for the absent bundle as defined in +/// [ZIP-244: Transaction Identifier Non-Malleability][zip244] +/// +/// [zip244]: https://zips.z.cash/zip-0244 +pub fn hash_bundle_txid_empty() -> Blake2bHash { + hasher(ZCASH_ORCHARD_HASH_PERSONALIZATION).finalize() +} + +/// Construct the commitment to the authorizing data of an +/// authorized bundle as defined in [ZIP-244: Transaction +/// Identifier Non-Malleability][zip244] +/// +/// [zip244]: https://zips.z.cash/zip-0244 +pub fn hash_bundle_auth_data(bundle: &Bundle) -> Blake2bHash { + let mut h = hasher(ZCASH_ORCHARD_SIGS_HASH_PERSONALIZATION); + h.write_all(bundle.authorization().proof().as_ref()) + .unwrap(); + for action in bundle.actions().iter() { + h.write_all(&<[u8; 64]>::from(action.authorization())) + .unwrap(); + } + h.write_all(&<[u8; 64]>::from( + bundle.authorization().binding_signature(), + )) + .unwrap(); + h.finalize() +} + +/// Construct the commitment for an absent bundle as defined in +/// [ZIP-244: Transaction Identifier Non-Malleability][zip244] +/// +/// [zip244]: https://zips.z.cash/zip-0244 +pub fn hash_bundle_auth_empty() -> Blake2bHash { + hasher(ZCASH_ORCHARD_SIGS_HASH_PERSONALIZATION).finalize() +} From 40d80c4d6f13d438f5cc8638709a7989d3972106 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 29 Jul 2021 07:16:14 -0600 Subject: [PATCH 2/2] Apply suggestions from code review Co-authored-by: Daira Hopwood --- src/bundle.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bundle.rs b/src/bundle.rs index 2296079f..5018c556 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -179,7 +179,7 @@ impl Flags { /// Serialize flags to a byte as defined in [Zcash Protocol Spec § 7.1: Transaction /// Encoding And Consensus][txencoding]. /// - /// [txencoding]: https://zips.z.cash/protocol/nu5.pdf#txnencoding + /// [txencoding]: https://zips.z.cash/protocol/protocol.pdf#txnencoding pub fn to_byte(&self) -> u8 { let mut value = 0u8; if self.spends_enabled { @@ -194,7 +194,7 @@ impl Flags { /// Parse from a single byte as defined in [Zcash Protocol Spec § 7.1: Transaction /// Encoding And Consensus][txencoding]. /// - /// [txencoding]: https://zips.z.cash/protocol/nu5.pdf#txnencoding + /// [txencoding]: https://zips.z.cash/protocol/protocol.pdf#txnencoding pub fn from_byte(value: u8) -> io::Result { if value & FLAGS_EXPECTED_UNSET == 0 { Ok(Self::from_parts(