From 6e371a8c13408308818299360c41a3f2234c71fe Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 4 Mar 2021 09:51:56 -0700 Subject: [PATCH 1/5] Make Sprout elements crate-public. --- .../src/transaction/components.rs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/zcash_primitives/src/transaction/components.rs b/zcash_primitives/src/transaction/components.rs index ea592c856..eec31b081 100644 --- a/zcash_primitives/src/transaction/components.rs +++ b/zcash_primitives/src/transaction/components.rs @@ -442,7 +442,7 @@ impl OutputDescription { } #[derive(Clone)] -enum SproutProof { +pub(crate) enum SproutProof { Groth([u8; GROTH_PROOF_SIZE]), PHGR([u8; PHGR_PROOF_SIZE]), } @@ -458,16 +458,16 @@ impl std::fmt::Debug for SproutProof { #[derive(Clone)] pub struct JSDescription { - vpub_old: Amount, - vpub_new: Amount, - anchor: [u8; 32], - nullifiers: [[u8; 32]; ZC_NUM_JS_INPUTS], - commitments: [[u8; 32]; ZC_NUM_JS_OUTPUTS], - ephemeral_key: [u8; 32], - random_seed: [u8; 32], - macs: [[u8; 32]; ZC_NUM_JS_INPUTS], - proof: SproutProof, - ciphertexts: [[u8; 601]; ZC_NUM_JS_OUTPUTS], + pub(crate) vpub_old: Amount, + pub(crate) vpub_new: Amount, + pub(crate) anchor: [u8; 32], + pub(crate) nullifiers: [[u8; 32]; ZC_NUM_JS_INPUTS], + pub(crate) commitments: [[u8; 32]; ZC_NUM_JS_OUTPUTS], + pub(crate) ephemeral_key: [u8; 32], + pub(crate) random_seed: [u8; 32], + pub(crate) macs: [[u8; 32]; ZC_NUM_JS_INPUTS], + pub(crate) proof: SproutProof, + pub(crate) ciphertexts: [[u8; 601]; ZC_NUM_JS_OUTPUTS], } impl std::fmt::Debug for JSDescription { From 7fcb7bbe991461c716180500ca25cec1bce44330 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 4 Mar 2021 08:27:39 -0700 Subject: [PATCH 2/5] Factor Sprout transaction elements into a separate module. --- .../src/transaction/components.rs | 169 +---------------- .../src/transaction/components/sprout.rs | 172 ++++++++++++++++++ 2 files changed, 174 insertions(+), 167 deletions(-) create mode 100644 zcash_primitives/src/transaction/components/sprout.rs diff --git a/zcash_primitives/src/transaction/components.rs b/zcash_primitives/src/transaction/components.rs index eec31b081..986b98d59 100644 --- a/zcash_primitives/src/transaction/components.rs +++ b/zcash_primitives/src/transaction/components.rs @@ -23,15 +23,11 @@ use crate::{ }; pub mod amount; -pub use self::amount::Amount; +pub mod sprout; +pub use self::{amount::Amount, sprout::JSDescription}; // π_A + π_B + π_C pub const GROTH_PROOF_SIZE: usize = 48 + 96 + 48; -// π_A + π_A' + π_B + π_B' + π_C + π_C' + π_K + π_H -const PHGR_PROOF_SIZE: usize = 33 + 33 + 65 + 33 + 33 + 33 + 33 + 33; - -const ZC_NUM_JS_INPUTS: usize = 2; -const ZC_NUM_JS_OUTPUTS: usize = 2; #[derive(Clone, Debug, PartialEq)] pub struct OutPoint { @@ -440,164 +436,3 @@ impl OutputDescription { writer.write_all(&self.zkproof) } } - -#[derive(Clone)] -pub(crate) enum SproutProof { - Groth([u8; GROTH_PROOF_SIZE]), - PHGR([u8; PHGR_PROOF_SIZE]), -} - -impl std::fmt::Debug for SproutProof { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - match self { - SproutProof::Groth(_) => write!(f, "SproutProof::Groth"), - SproutProof::PHGR(_) => write!(f, "SproutProof::PHGR"), - } - } -} - -#[derive(Clone)] -pub struct JSDescription { - pub(crate) vpub_old: Amount, - pub(crate) vpub_new: Amount, - pub(crate) anchor: [u8; 32], - pub(crate) nullifiers: [[u8; 32]; ZC_NUM_JS_INPUTS], - pub(crate) commitments: [[u8; 32]; ZC_NUM_JS_OUTPUTS], - pub(crate) ephemeral_key: [u8; 32], - pub(crate) random_seed: [u8; 32], - pub(crate) macs: [[u8; 32]; ZC_NUM_JS_INPUTS], - pub(crate) proof: SproutProof, - pub(crate) ciphertexts: [[u8; 601]; ZC_NUM_JS_OUTPUTS], -} - -impl std::fmt::Debug for JSDescription { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - write!( - f, - "JSDescription( - vpub_old = {:?}, vpub_new = {:?}, - anchor = {:?}, - nullifiers = {:?}, - commitments = {:?}, - ephemeral_key = {:?}, - random_seed = {:?}, - macs = {:?})", - self.vpub_old, - self.vpub_new, - self.anchor, - self.nullifiers, - self.commitments, - self.ephemeral_key, - self.random_seed, - self.macs - ) - } -} - -impl JSDescription { - pub fn read(mut reader: R, use_groth: bool) -> io::Result { - // Consensus rule (§4.3): Canonical encoding is enforced here - let vpub_old = { - let mut tmp = [0u8; 8]; - reader.read_exact(&mut tmp)?; - Amount::from_u64_le_bytes(tmp) - } - .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "vpub_old out of range"))?; - - // Consensus rule (§4.3): Canonical encoding is enforced here - let vpub_new = { - let mut tmp = [0u8; 8]; - reader.read_exact(&mut tmp)?; - Amount::from_u64_le_bytes(tmp) - } - .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "vpub_new out of range"))?; - - // Consensus rule (§4.3): One of vpub_old and vpub_new being zero is - // enforced by CheckTransactionWithoutProofVerification() in zcashd. - - let mut anchor = [0u8; 32]; - reader.read_exact(&mut anchor)?; - - let mut nullifiers = [[0u8; 32]; ZC_NUM_JS_INPUTS]; - nullifiers - .iter_mut() - .map(|nf| reader.read_exact(nf)) - .collect::>()?; - - let mut commitments = [[0u8; 32]; ZC_NUM_JS_OUTPUTS]; - commitments - .iter_mut() - .map(|cm| reader.read_exact(cm)) - .collect::>()?; - - // Consensus rule (§4.3): Canonical encoding is enforced by - // ZCNoteDecryption::decrypt() in zcashd - let mut ephemeral_key = [0u8; 32]; - reader.read_exact(&mut ephemeral_key)?; - - let mut random_seed = [0u8; 32]; - reader.read_exact(&mut random_seed)?; - - let mut macs = [[0u8; 32]; ZC_NUM_JS_INPUTS]; - macs.iter_mut() - .map(|mac| reader.read_exact(mac)) - .collect::>()?; - - let proof = if use_groth { - // Consensus rules (§4.3): - // - Canonical encoding is enforced in librustzcash_sprout_verify() - // - Proof validity is enforced in librustzcash_sprout_verify() - let mut proof = [0u8; GROTH_PROOF_SIZE]; - reader.read_exact(&mut proof)?; - SproutProof::Groth(proof) - } else { - // Consensus rules (§4.3): - // - Canonical encoding is enforced by PHGRProof in zcashd - // - Proof validity is enforced by JSDescription::Verify() in zcashd - let mut proof = [0u8; PHGR_PROOF_SIZE]; - reader.read_exact(&mut proof)?; - SproutProof::PHGR(proof) - }; - - let mut ciphertexts = [[0u8; 601]; ZC_NUM_JS_OUTPUTS]; - ciphertexts - .iter_mut() - .map(|ct| reader.read_exact(ct)) - .collect::>()?; - - Ok(JSDescription { - vpub_old, - vpub_new, - anchor, - nullifiers, - commitments, - ephemeral_key, - random_seed, - macs, - proof, - ciphertexts, - }) - } - - pub fn write(&self, mut writer: W) -> io::Result<()> { - writer.write_all(&self.vpub_old.to_i64_le_bytes())?; - writer.write_all(&self.vpub_new.to_i64_le_bytes())?; - writer.write_all(&self.anchor)?; - writer.write_all(&self.nullifiers[0])?; - writer.write_all(&self.nullifiers[1])?; - writer.write_all(&self.commitments[0])?; - writer.write_all(&self.commitments[1])?; - writer.write_all(&self.ephemeral_key)?; - writer.write_all(&self.random_seed)?; - writer.write_all(&self.macs[0])?; - writer.write_all(&self.macs[1])?; - - match &self.proof { - SproutProof::Groth(p) => writer.write_all(p)?, - SproutProof::PHGR(p) => writer.write_all(p)?, - } - - writer.write_all(&self.ciphertexts[0])?; - writer.write_all(&self.ciphertexts[1]) - } -} diff --git a/zcash_primitives/src/transaction/components/sprout.rs b/zcash_primitives/src/transaction/components/sprout.rs new file mode 100644 index 000000000..d57afc82b --- /dev/null +++ b/zcash_primitives/src/transaction/components/sprout.rs @@ -0,0 +1,172 @@ +//! Structs representing the components within Zcash transactions. + +use std::io::{self, Read, Write}; + +use super::{amount::Amount, GROTH_PROOF_SIZE}; + +// π_A + π_A' + π_B + π_B' + π_C + π_C' + π_K + π_H +const PHGR_PROOF_SIZE: usize = 33 + 33 + 65 + 33 + 33 + 33 + 33 + 33; + +const ZC_NUM_JS_INPUTS: usize = 2; +const ZC_NUM_JS_OUTPUTS: usize = 2; + +#[derive(Clone)] +pub(crate) enum SproutProof { + Groth([u8; GROTH_PROOF_SIZE]), + PHGR([u8; PHGR_PROOF_SIZE]), +} + +impl std::fmt::Debug for SproutProof { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + match self { + SproutProof::Groth(_) => write!(f, "SproutProof::Groth"), + SproutProof::PHGR(_) => write!(f, "SproutProof::PHGR"), + } + } +} + +#[derive(Clone)] +pub struct JSDescription { + pub(crate) vpub_old: Amount, + pub(crate) vpub_new: Amount, + pub(crate) anchor: [u8; 32], + pub(crate) nullifiers: [[u8; 32]; ZC_NUM_JS_INPUTS], + pub(crate) commitments: [[u8; 32]; ZC_NUM_JS_OUTPUTS], + pub(crate) ephemeral_key: [u8; 32], + pub(crate) random_seed: [u8; 32], + pub(crate) macs: [[u8; 32]; ZC_NUM_JS_INPUTS], + pub(crate) proof: SproutProof, + pub(crate) ciphertexts: [[u8; 601]; ZC_NUM_JS_OUTPUTS], +} + +impl std::fmt::Debug for JSDescription { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!( + f, + "JSDescription( + vpub_old = {:?}, vpub_new = {:?}, + anchor = {:?}, + nullifiers = {:?}, + commitments = {:?}, + ephemeral_key = {:?}, + random_seed = {:?}, + macs = {:?})", + self.vpub_old, + self.vpub_new, + self.anchor, + self.nullifiers, + self.commitments, + self.ephemeral_key, + self.random_seed, + self.macs + ) + } +} + +impl JSDescription { + pub fn read(mut reader: R, use_groth: bool) -> io::Result { + // Consensus rule (§4.3): Canonical encoding is enforced here + let vpub_old = { + let mut tmp = [0u8; 8]; + reader.read_exact(&mut tmp)?; + Amount::from_u64_le_bytes(tmp) + } + .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "vpub_old out of range"))?; + + // Consensus rule (§4.3): Canonical encoding is enforced here + let vpub_new = { + let mut tmp = [0u8; 8]; + reader.read_exact(&mut tmp)?; + Amount::from_u64_le_bytes(tmp) + } + .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "vpub_new out of range"))?; + + // Consensus rule (§4.3): One of vpub_old and vpub_new being zero is + // enforced by CheckTransactionWithoutProofVerification() in zcashd. + + let mut anchor = [0u8; 32]; + reader.read_exact(&mut anchor)?; + + let mut nullifiers = [[0u8; 32]; ZC_NUM_JS_INPUTS]; + nullifiers + .iter_mut() + .map(|nf| reader.read_exact(nf)) + .collect::>()?; + + let mut commitments = [[0u8; 32]; ZC_NUM_JS_OUTPUTS]; + commitments + .iter_mut() + .map(|cm| reader.read_exact(cm)) + .collect::>()?; + + // Consensus rule (§4.3): Canonical encoding is enforced by + // ZCNoteDecryption::decrypt() in zcashd + let mut ephemeral_key = [0u8; 32]; + reader.read_exact(&mut ephemeral_key)?; + + let mut random_seed = [0u8; 32]; + reader.read_exact(&mut random_seed)?; + + let mut macs = [[0u8; 32]; ZC_NUM_JS_INPUTS]; + macs.iter_mut() + .map(|mac| reader.read_exact(mac)) + .collect::>()?; + + let proof = if use_groth { + // Consensus rules (§4.3): + // - Canonical encoding is enforced in librustzcash_sprout_verify() + // - Proof validity is enforced in librustzcash_sprout_verify() + let mut proof = [0u8; GROTH_PROOF_SIZE]; + reader.read_exact(&mut proof)?; + SproutProof::Groth(proof) + } else { + // Consensus rules (§4.3): + // - Canonical encoding is enforced by PHGRProof in zcashd + // - Proof validity is enforced by JSDescription::Verify() in zcashd + let mut proof = [0u8; PHGR_PROOF_SIZE]; + reader.read_exact(&mut proof)?; + SproutProof::PHGR(proof) + }; + + let mut ciphertexts = [[0u8; 601]; ZC_NUM_JS_OUTPUTS]; + ciphertexts + .iter_mut() + .map(|ct| reader.read_exact(ct)) + .collect::>()?; + + Ok(JSDescription { + vpub_old, + vpub_new, + anchor, + nullifiers, + commitments, + ephemeral_key, + random_seed, + macs, + proof, + ciphertexts, + }) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + writer.write_all(&self.vpub_old.to_i64_le_bytes())?; + writer.write_all(&self.vpub_new.to_i64_le_bytes())?; + writer.write_all(&self.anchor)?; + writer.write_all(&self.nullifiers[0])?; + writer.write_all(&self.nullifiers[1])?; + writer.write_all(&self.commitments[0])?; + writer.write_all(&self.commitments[1])?; + writer.write_all(&self.ephemeral_key)?; + writer.write_all(&self.random_seed)?; + writer.write_all(&self.macs[0])?; + writer.write_all(&self.macs[1])?; + + match &self.proof { + SproutProof::Groth(p) => writer.write_all(p)?, + SproutProof::PHGR(p) => writer.write_all(p)?, + } + + writer.write_all(&self.ciphertexts[0])?; + writer.write_all(&self.ciphertexts[1]) + } +} From 96709c642384fbac2b5352d11c53e5b6681448df Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 4 Mar 2021 08:31:57 -0700 Subject: [PATCH 3/5] Move Sapling transaction components to a separate module. --- .../src/transaction/components.rs | 198 +----------------- .../src/transaction/components/sapling.rs | 193 +++++++++++++++++ 2 files changed, 200 insertions(+), 191 deletions(-) create mode 100644 zcash_primitives/src/transaction/components/sapling.rs diff --git a/zcash_primitives/src/transaction/components.rs b/zcash_primitives/src/transaction/components.rs index 986b98d59..868582abb 100644 --- a/zcash_primitives/src/transaction/components.rs +++ b/zcash_primitives/src/transaction/components.rs @@ -2,19 +2,12 @@ use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; -use ff::PrimeField; -use group::GroupEncoding; - use std::io::{self, Read, Write}; #[cfg(feature = "zfuture")] use std::convert::TryFrom; -use crate::{ - legacy::Script, - primitives::Nullifier, - redjubjub::{PublicKey, Signature}, -}; +use crate::legacy::Script; #[cfg(feature = "zfuture")] use crate::{ @@ -23,8 +16,13 @@ use crate::{ }; pub mod amount; +pub mod sapling; pub mod sprout; -pub use self::{amount::Amount, sprout::JSDescription}; +pub use self::{ + amount::Amount, + sapling::{OutputDescription, SpendDescription}, + sprout::JSDescription, +}; // π_A + π_B + π_C pub const GROTH_PROOF_SIZE: usize = 48 + 96 + 48; @@ -254,185 +252,3 @@ impl TzeOut { }) } } - -#[derive(Clone)] -pub struct SpendDescription { - pub cv: jubjub::ExtendedPoint, - pub anchor: bls12_381::Scalar, - pub nullifier: Nullifier, - pub rk: PublicKey, - pub zkproof: [u8; GROTH_PROOF_SIZE], - pub spend_auth_sig: Option, -} - -impl std::fmt::Debug for SpendDescription { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - write!( - f, - "SpendDescription(cv = {:?}, anchor = {:?}, nullifier = {:?}, rk = {:?}, spend_auth_sig = {:?})", - self.cv, self.anchor, self.nullifier, self.rk, self.spend_auth_sig - ) - } -} - -impl SpendDescription { - pub fn read(mut reader: &mut R) -> io::Result { - // Consensus rules (§4.4): - // - Canonical encoding is enforced here. - // - "Not small order" is enforced in SaplingVerificationContext::check_spend() - // (located in zcash_proofs::sapling::verifier). - let cv = { - let mut bytes = [0u8; 32]; - reader.read_exact(&mut bytes)?; - let cv = jubjub::ExtendedPoint::from_bytes(&bytes); - if cv.is_none().into() { - return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid cv")); - } - cv.unwrap() - }; - - // Consensus rule (§7.3): Canonical encoding is enforced here - let anchor = { - let mut f = [0u8; 32]; - reader.read_exact(&mut f)?; - bls12_381::Scalar::from_repr(f) - .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "anchor not in field"))? - }; - - let mut nullifier = Nullifier([0u8; 32]); - reader.read_exact(&mut nullifier.0)?; - - // Consensus rules (§4.4): - // - Canonical encoding is enforced here. - // - "Not small order" is enforced in SaplingVerificationContext::check_spend() - let rk = PublicKey::read(&mut reader)?; - - // Consensus rules (§4.4): - // - Canonical encoding is enforced by the API of SaplingVerificationContext::check_spend() - // due to the need to parse this into a bellman::groth16::Proof. - // - Proof validity is enforced in SaplingVerificationContext::check_spend() - let mut zkproof = [0u8; GROTH_PROOF_SIZE]; - reader.read_exact(&mut zkproof)?; - - // Consensus rules (§4.4): - // - Canonical encoding is enforced here. - // - Signature validity is enforced in SaplingVerificationContext::check_spend() - let spend_auth_sig = Some(Signature::read(&mut reader)?); - - Ok(SpendDescription { - cv, - anchor, - nullifier, - rk, - zkproof, - spend_auth_sig, - }) - } - - pub fn write(&self, mut writer: W) -> io::Result<()> { - writer.write_all(&self.cv.to_bytes())?; - writer.write_all(self.anchor.to_repr().as_ref())?; - writer.write_all(&self.nullifier.0)?; - self.rk.write(&mut writer)?; - writer.write_all(&self.zkproof)?; - match self.spend_auth_sig { - Some(sig) => sig.write(&mut writer), - None => Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Missing spend auth signature", - )), - } - } -} - -#[derive(Clone)] -pub struct OutputDescription { - pub cv: jubjub::ExtendedPoint, - pub cmu: bls12_381::Scalar, - pub ephemeral_key: jubjub::ExtendedPoint, - pub enc_ciphertext: [u8; 580], - pub out_ciphertext: [u8; 80], - pub zkproof: [u8; GROTH_PROOF_SIZE], -} - -impl std::fmt::Debug for OutputDescription { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - write!( - f, - "OutputDescription(cv = {:?}, cmu = {:?}, ephemeral_key = {:?})", - self.cv, self.cmu, self.ephemeral_key - ) - } -} - -impl OutputDescription { - pub fn read(reader: &mut R) -> io::Result { - // Consensus rules (§4.5): - // - Canonical encoding is enforced here. - // - "Not small order" is enforced in SaplingVerificationContext::check_output() - // (located in zcash_proofs::sapling::verifier). - let cv = { - let mut bytes = [0u8; 32]; - reader.read_exact(&mut bytes)?; - let cv = jubjub::ExtendedPoint::from_bytes(&bytes); - if cv.is_none().into() { - return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid cv")); - } - cv.unwrap() - }; - - // Consensus rule (§7.4): Canonical encoding is enforced here - let cmu = { - let mut f = [0u8; 32]; - reader.read_exact(&mut f)?; - bls12_381::Scalar::from_repr(f) - .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "cmu not in field"))? - }; - - // Consensus rules (§4.5): - // - Canonical encoding is enforced here. - // - "Not small order" is enforced in SaplingVerificationContext::check_output() - let ephemeral_key = { - let mut bytes = [0u8; 32]; - reader.read_exact(&mut bytes)?; - let ephemeral_key = jubjub::ExtendedPoint::from_bytes(&bytes); - if ephemeral_key.is_none().into() { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "invalid ephemeral_key", - )); - } - ephemeral_key.unwrap() - }; - - let mut enc_ciphertext = [0u8; 580]; - let mut out_ciphertext = [0u8; 80]; - reader.read_exact(&mut enc_ciphertext)?; - reader.read_exact(&mut out_ciphertext)?; - - // Consensus rules (§4.5): - // - Canonical encoding is enforced by the API of SaplingVerificationContext::check_output() - // due to the need to parse this into a bellman::groth16::Proof. - // - Proof validity is enforced in SaplingVerificationContext::check_output() - let mut zkproof = [0u8; GROTH_PROOF_SIZE]; - reader.read_exact(&mut zkproof)?; - - Ok(OutputDescription { - cv, - cmu, - ephemeral_key, - enc_ciphertext, - out_ciphertext, - zkproof, - }) - } - - pub fn write(&self, mut writer: W) -> io::Result<()> { - writer.write_all(&self.cv.to_bytes())?; - writer.write_all(self.cmu.to_repr().as_ref())?; - writer.write_all(&self.ephemeral_key.to_bytes())?; - writer.write_all(&self.enc_ciphertext)?; - writer.write_all(&self.out_ciphertext)?; - writer.write_all(&self.zkproof) - } -} diff --git a/zcash_primitives/src/transaction/components/sapling.rs b/zcash_primitives/src/transaction/components/sapling.rs new file mode 100644 index 000000000..04fc679c3 --- /dev/null +++ b/zcash_primitives/src/transaction/components/sapling.rs @@ -0,0 +1,193 @@ +use ff::PrimeField; +use group::GroupEncoding; + +use std::io::{self, Read, Write}; + +use crate::{ + primitives::Nullifier, + redjubjub::{PublicKey, Signature}, +}; + +use super::GROTH_PROOF_SIZE; + +#[derive(Clone)] +pub struct SpendDescription { + pub cv: jubjub::ExtendedPoint, + pub anchor: bls12_381::Scalar, + pub nullifier: Nullifier, + pub rk: PublicKey, + pub zkproof: [u8; GROTH_PROOF_SIZE], + pub spend_auth_sig: Option, +} + +impl std::fmt::Debug for SpendDescription { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!( + f, + "SpendDescription(cv = {:?}, anchor = {:?}, nullifier = {:?}, rk = {:?}, spend_auth_sig = {:?})", + self.cv, self.anchor, self.nullifier, self.rk, self.spend_auth_sig + ) + } +} + +impl SpendDescription { + pub fn read(mut reader: &mut R) -> io::Result { + // Consensus rules (§4.4): + // - Canonical encoding is enforced here. + // - "Not small order" is enforced in SaplingVerificationContext::check_spend() + // (located in zcash_proofs::sapling::verifier). + let cv = { + let mut bytes = [0u8; 32]; + reader.read_exact(&mut bytes)?; + let cv = jubjub::ExtendedPoint::from_bytes(&bytes); + if cv.is_none().into() { + return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid cv")); + } + cv.unwrap() + }; + + // Consensus rule (§7.3): Canonical encoding is enforced here + let anchor = { + let mut f = [0u8; 32]; + reader.read_exact(&mut f)?; + bls12_381::Scalar::from_repr(f) + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "anchor not in field"))? + }; + + let mut nullifier = Nullifier([0u8; 32]); + reader.read_exact(&mut nullifier.0)?; + + // Consensus rules (§4.4): + // - Canonical encoding is enforced here. + // - "Not small order" is enforced in SaplingVerificationContext::check_spend() + let rk = PublicKey::read(&mut reader)?; + + // Consensus rules (§4.4): + // - Canonical encoding is enforced by the API of SaplingVerificationContext::check_spend() + // due to the need to parse this into a bellman::groth16::Proof. + // - Proof validity is enforced in SaplingVerificationContext::check_spend() + let mut zkproof = [0u8; GROTH_PROOF_SIZE]; + reader.read_exact(&mut zkproof)?; + + // Consensus rules (§4.4): + // - Canonical encoding is enforced here. + // - Signature validity is enforced in SaplingVerificationContext::check_spend() + let spend_auth_sig = Some(Signature::read(&mut reader)?); + + Ok(SpendDescription { + cv, + anchor, + nullifier, + rk, + zkproof, + spend_auth_sig, + }) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + writer.write_all(&self.cv.to_bytes())?; + writer.write_all(self.anchor.to_repr().as_ref())?; + writer.write_all(&self.nullifier.0)?; + self.rk.write(&mut writer)?; + writer.write_all(&self.zkproof)?; + match self.spend_auth_sig { + Some(sig) => sig.write(&mut writer), + None => Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Missing spend auth signature", + )), + } + } +} + +#[derive(Clone)] +pub struct OutputDescription { + pub cv: jubjub::ExtendedPoint, + pub cmu: bls12_381::Scalar, + pub ephemeral_key: jubjub::ExtendedPoint, + pub enc_ciphertext: [u8; 580], + pub out_ciphertext: [u8; 80], + pub zkproof: [u8; GROTH_PROOF_SIZE], +} + +impl std::fmt::Debug for OutputDescription { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!( + f, + "OutputDescription(cv = {:?}, cmu = {:?}, ephemeral_key = {:?})", + self.cv, self.cmu, self.ephemeral_key + ) + } +} + +impl OutputDescription { + pub fn read(reader: &mut R) -> io::Result { + // Consensus rules (§4.5): + // - Canonical encoding is enforced here. + // - "Not small order" is enforced in SaplingVerificationContext::check_output() + // (located in zcash_proofs::sapling::verifier). + let cv = { + let mut bytes = [0u8; 32]; + reader.read_exact(&mut bytes)?; + let cv = jubjub::ExtendedPoint::from_bytes(&bytes); + if cv.is_none().into() { + return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid cv")); + } + cv.unwrap() + }; + + // Consensus rule (§7.4): Canonical encoding is enforced here + let cmu = { + let mut f = [0u8; 32]; + reader.read_exact(&mut f)?; + bls12_381::Scalar::from_repr(f) + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "cmu not in field"))? + }; + + // Consensus rules (§4.5): + // - Canonical encoding is enforced here. + // - "Not small order" is enforced in SaplingVerificationContext::check_output() + let ephemeral_key = { + let mut bytes = [0u8; 32]; + reader.read_exact(&mut bytes)?; + let ephemeral_key = jubjub::ExtendedPoint::from_bytes(&bytes); + if ephemeral_key.is_none().into() { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "invalid ephemeral_key", + )); + } + ephemeral_key.unwrap() + }; + + let mut enc_ciphertext = [0u8; 580]; + let mut out_ciphertext = [0u8; 80]; + reader.read_exact(&mut enc_ciphertext)?; + reader.read_exact(&mut out_ciphertext)?; + + // Consensus rules (§4.5): + // - Canonical encoding is enforced by the API of SaplingVerificationContext::check_output() + // due to the need to parse this into a bellman::groth16::Proof. + // - Proof validity is enforced in SaplingVerificationContext::check_output() + let mut zkproof = [0u8; GROTH_PROOF_SIZE]; + reader.read_exact(&mut zkproof)?; + + Ok(OutputDescription { + cv, + cmu, + ephemeral_key, + enc_ciphertext, + out_ciphertext, + zkproof, + }) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + writer.write_all(&self.cv.to_bytes())?; + writer.write_all(self.cmu.to_repr().as_ref())?; + writer.write_all(&self.ephemeral_key.to_bytes())?; + writer.write_all(&self.enc_ciphertext)?; + writer.write_all(&self.out_ciphertext)?; + writer.write_all(&self.zkproof) + } +} From 97f2502a5e9581f6a6f2ec70faf1eca7b4b776e1 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 4 Mar 2021 09:13:01 -0700 Subject: [PATCH 4/5] Factor transparent transaction components into a separate module. --- .../src/transaction/components.rs | 103 +---------------- .../src/transaction/components/transparent.rs | 106 ++++++++++++++++++ 2 files changed, 109 insertions(+), 100 deletions(-) create mode 100644 zcash_primitives/src/transaction/components/transparent.rs diff --git a/zcash_primitives/src/transaction/components.rs b/zcash_primitives/src/transaction/components.rs index 868582abb..8dd69c1c0 100644 --- a/zcash_primitives/src/transaction/components.rs +++ b/zcash_primitives/src/transaction/components.rs @@ -1,14 +1,12 @@ //! Structs representing the components within Zcash transactions. -use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use byteorder::{ReadBytesExt, WriteBytesExt}; use std::io::{self, Read, Write}; #[cfg(feature = "zfuture")] use std::convert::TryFrom; -use crate::legacy::Script; - #[cfg(feature = "zfuture")] use crate::{ extensions::transparent as tze, @@ -18,112 +16,17 @@ use crate::{ pub mod amount; pub mod sapling; pub mod sprout; +pub mod transparent; pub use self::{ amount::Amount, sapling::{OutputDescription, SpendDescription}, sprout::JSDescription, + transparent::{OutPoint, TxIn, TxOut}, }; // π_A + π_B + π_C pub const GROTH_PROOF_SIZE: usize = 48 + 96 + 48; -#[derive(Clone, Debug, PartialEq)] -pub struct OutPoint { - hash: [u8; 32], - n: u32, -} - -impl OutPoint { - pub fn new(hash: [u8; 32], n: u32) -> Self { - OutPoint { hash, n } - } - - pub fn read(mut reader: R) -> io::Result { - let mut hash = [0u8; 32]; - reader.read_exact(&mut hash)?; - let n = reader.read_u32::()?; - Ok(OutPoint { hash, n }) - } - - pub fn write(&self, mut writer: W) -> io::Result<()> { - writer.write_all(&self.hash)?; - writer.write_u32::(self.n) - } - - pub fn n(&self) -> u32 { - self.n - } - - pub fn hash(&self) -> &[u8; 32] { - &self.hash - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct TxIn { - pub prevout: OutPoint, - pub script_sig: Script, - pub sequence: u32, -} - -impl TxIn { - #[cfg(feature = "transparent-inputs")] - #[cfg_attr(docsrs, doc(cfg(feature = "transparent-inputs")))] - pub fn new(prevout: OutPoint) -> Self { - TxIn { - prevout, - script_sig: Script::default(), - sequence: std::u32::MAX, - } - } - - pub fn read(mut reader: &mut R) -> io::Result { - let prevout = OutPoint::read(&mut reader)?; - let script_sig = Script::read(&mut reader)?; - let sequence = reader.read_u32::()?; - - Ok(TxIn { - prevout, - script_sig, - sequence, - }) - } - - pub fn write(&self, mut writer: W) -> io::Result<()> { - self.prevout.write(&mut writer)?; - self.script_sig.write(&mut writer)?; - writer.write_u32::(self.sequence) - } -} - -#[derive(Clone, Debug, PartialEq)] -pub struct TxOut { - pub value: Amount, - pub script_pubkey: Script, -} - -impl TxOut { - pub fn read(mut reader: &mut R) -> io::Result { - let value = { - let mut tmp = [0u8; 8]; - reader.read_exact(&mut tmp)?; - Amount::from_nonnegative_i64_le_bytes(tmp) - } - .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "value out of range"))?; - let script_pubkey = Script::read(&mut reader)?; - - Ok(TxOut { - value, - script_pubkey, - }) - } - - pub fn write(&self, mut writer: W) -> io::Result<()> { - writer.write_all(&self.value.to_i64_le_bytes())?; - self.script_pubkey.write(&mut writer) - } -} - #[cfg(feature = "zfuture")] fn to_io_error(_: std::num::TryFromIntError) -> io::Error { io::Error::new(io::ErrorKind::InvalidData, "value out of range") diff --git a/zcash_primitives/src/transaction/components/transparent.rs b/zcash_primitives/src/transaction/components/transparent.rs new file mode 100644 index 000000000..829ecec19 --- /dev/null +++ b/zcash_primitives/src/transaction/components/transparent.rs @@ -0,0 +1,106 @@ +//! Structs representing the components within Zcash transactions. + +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; + +use std::io::{self, Read, Write}; + +use crate::legacy::Script; + +use super::amount::Amount; + +#[derive(Clone, Debug, PartialEq)] +pub struct OutPoint { + hash: [u8; 32], + n: u32, +} + +impl OutPoint { + pub fn new(hash: [u8; 32], n: u32) -> Self { + OutPoint { hash, n } + } + + pub fn read(mut reader: R) -> io::Result { + let mut hash = [0u8; 32]; + reader.read_exact(&mut hash)?; + let n = reader.read_u32::()?; + Ok(OutPoint { hash, n }) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + writer.write_all(&self.hash)?; + writer.write_u32::(self.n) + } + + pub fn n(&self) -> u32 { + self.n + } + + pub fn hash(&self) -> &[u8; 32] { + &self.hash + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct TxIn { + pub prevout: OutPoint, + pub script_sig: Script, + pub sequence: u32, +} + +impl TxIn { + #[cfg(feature = "transparent-inputs")] + #[cfg_attr(docsrs, doc(cfg(feature = "transparent-inputs")))] + pub fn new(prevout: OutPoint) -> Self { + TxIn { + prevout, + script_sig: Script::default(), + sequence: std::u32::MAX, + } + } + + pub fn read(mut reader: &mut R) -> io::Result { + let prevout = OutPoint::read(&mut reader)?; + let script_sig = Script::read(&mut reader)?; + let sequence = reader.read_u32::()?; + + Ok(TxIn { + prevout, + script_sig, + sequence, + }) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + self.prevout.write(&mut writer)?; + self.script_sig.write(&mut writer)?; + writer.write_u32::(self.sequence) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct TxOut { + pub value: Amount, + pub script_pubkey: Script, +} + +impl TxOut { + pub fn read(mut reader: &mut R) -> io::Result { + let value = { + let mut tmp = [0u8; 8]; + reader.read_exact(&mut tmp)?; + Amount::from_nonnegative_i64_le_bytes(tmp) + } + .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "value out of range"))?; + let script_pubkey = Script::read(&mut reader)?; + + Ok(TxOut { + value, + script_pubkey, + }) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + writer.write_all(&self.value.to_i64_le_bytes())?; + self.script_pubkey.write(&mut writer) + } +} From c839193b20e14cb70ff2ccf6cfa307488608647e Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 4 Mar 2021 09:35:53 -0700 Subject: [PATCH 5/5] Move TZE components to a separate module & add TzeOutPoint --- zcash_extensions/src/transparent/demo.rs | 22 ++- .../src/extensions/transparent.rs | 4 +- zcash_primitives/src/transaction/builder.rs | 12 +- .../src/transaction/components.rs | 146 +-------------- .../src/transaction/components/tze.rs | 171 ++++++++++++++++++ zcash_primitives/src/transaction/mod.rs | 11 +- 6 files changed, 206 insertions(+), 160 deletions(-) create mode 100644 zcash_primitives/src/transaction/components/tze.rs diff --git a/zcash_extensions/src/transparent/demo.rs b/zcash_extensions/src/transparent/demo.rs index 2f3e5a99a..4668fd346 100644 --- a/zcash_extensions/src/transparent/demo.rs +++ b/zcash_extensions/src/transparent/demo.rs @@ -26,7 +26,7 @@ use blake2b_simd::Params; use zcash_primitives::{ extensions::transparent::{Extension, ExtensionTxBuilder, FromPayload, ToPayload}, - transaction::components::{amount::Amount, OutPoint, TzeOut}, + transaction::components::{amount::Amount, TzeOut, TzeOutPoint}, }; /// Types and constants used for Mode 0 (open a channel) @@ -374,7 +374,7 @@ impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<&mut B> { /// precondition to the transaction under construction. pub fn demo_transfer_to_close( &mut self, - prevout: (OutPoint, TzeOut), + prevout: (TzeOutPoint, TzeOut), transfer_amount: Amount, preimage_1: [u8; 32], hash_2: [u8; 32], @@ -416,7 +416,7 @@ impl<'a, B: ExtensionTxBuilder<'a>> DemoBuilder<&mut B> { /// Add a channel-closing witness to the transaction under construction. pub fn demo_close( &mut self, - prevout: (OutPoint, TzeOut), + prevout: (TzeOutPoint, TzeOut), preimage_2: [u8; 32], ) -> Result<(), DemoBuildError> { let hash_2 = { @@ -477,7 +477,7 @@ mod tests { builder::Builder, components::{ amount::{Amount, DEFAULT_FEE}, - OutPoint, TzeIn, TzeOut, + TzeIn, TzeOut, TzeOutPoint, }, Transaction, TransactionData, }, @@ -625,7 +625,7 @@ mod tests { // let in_b = TzeIn { - prevout: OutPoint::new(tx_a.txid().0, 0), + prevout: TzeOutPoint::new(tx_a.txid().0, 0), witness: tze::Witness::from(0, &Witness::open(preimage_1)), }; let out_b = TzeOut { @@ -642,7 +642,7 @@ mod tests { // let in_c = TzeIn { - prevout: OutPoint::new(tx_b.txid().0, 0), + prevout: TzeOutPoint::new(tx_b.txid().0, 0), witness: tze::Witness::from(0, &Witness::close(preimage_2)), }; @@ -736,7 +736,10 @@ mod tests { txn_builder: &mut builder_b, extension_id: 0, }; - let prevout_a = (OutPoint::new(tx_a.txid().0, 0), tx_a.tze_outputs[0].clone()); + let prevout_a = ( + TzeOutPoint::new(tx_a.txid().0, 0), + tx_a.tze_outputs[0].clone(), + ); let value_xfr = value - DEFAULT_FEE; db_b.demo_transfer_to_close(prevout_a, value_xfr, preimage_1, h2) .map_err(|e| format!("transfer failure: {:?}", e)) @@ -755,7 +758,10 @@ mod tests { txn_builder: &mut builder_c, extension_id: 0, }; - let prevout_b = (OutPoint::new(tx_a.txid().0, 0), tx_b.tze_outputs[0].clone()); + let prevout_b = ( + TzeOutPoint::new(tx_a.txid().0, 0), + tx_b.tze_outputs[0].clone(), + ); db_c.demo_close(prevout_b, preimage_2) .map_err(|e| format!("close failure: {:?}", e)) .unwrap(); diff --git a/zcash_primitives/src/extensions/transparent.rs b/zcash_primitives/src/extensions/transparent.rs index 9fdfe99ec..4b8b31d5f 100644 --- a/zcash_primitives/src/extensions/transparent.rs +++ b/zcash_primitives/src/extensions/transparent.rs @@ -1,6 +1,6 @@ //! Core traits and structs for Transparent Zcash Extensions. -use crate::transaction::components::{Amount, OutPoint, TzeOut}; +use crate::transaction::components::{Amount, TzeOut, TzeOutPoint}; use std::fmt; /// Binary parsing capability for TZE preconditions & witnesses. @@ -178,7 +178,7 @@ pub trait ExtensionTxBuilder<'a> { &mut self, extension_id: u32, mode: u32, - prevout: (OutPoint, TzeOut), + prevout: (TzeOutPoint, TzeOut), witness_builder: WBuilder, ) -> Result<(), Self::BuildError> where diff --git a/zcash_primitives/src/transaction/builder.rs b/zcash_primitives/src/transaction/builder.rs index d8d57fb06..187e398a4 100644 --- a/zcash_primitives/src/transaction/builder.rs +++ b/zcash_primitives/src/transaction/builder.rs @@ -32,17 +32,17 @@ use crate::{ }; #[cfg(feature = "transparent-inputs")] -use crate::{legacy::Script, transaction::components::TxIn}; +use crate::{ + legacy::Script, + transaction::components::{OutPoint, TxIn}, +}; #[cfg(feature = "zfuture")] use crate::{ extensions::transparent::{self as tze, ExtensionTxBuilder, ToPayload}, - transaction::components::{TzeIn, TzeOut}, + transaction::components::{TzeIn, TzeOut, TzeOutPoint}, }; -#[cfg(any(feature = "transparent-inputs", feature = "zfuture"))] -use crate::transaction::components::OutPoint; - #[cfg(any(test, feature = "test-dependencies"))] use crate::prover::mock::MockTxProver; @@ -869,7 +869,7 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> ExtensionTxBuilder<'a &mut self, extension_id: u32, mode: u32, - (outpoint, prevout): (OutPoint, TzeOut), + (outpoint, prevout): (TzeOutPoint, TzeOut), witness_builder: WBuilder, ) -> Result<(), Self::BuildError> where diff --git a/zcash_primitives/src/transaction/components.rs b/zcash_primitives/src/transaction/components.rs index 8dd69c1c0..352166a93 100644 --- a/zcash_primitives/src/transaction/components.rs +++ b/zcash_primitives/src/transaction/components.rs @@ -1,22 +1,10 @@ //! Structs representing the components within Zcash transactions. -use byteorder::{ReadBytesExt, WriteBytesExt}; - -use std::io::{self, Read, Write}; - -#[cfg(feature = "zfuture")] -use std::convert::TryFrom; - -#[cfg(feature = "zfuture")] -use crate::{ - extensions::transparent as tze, - serialize::{CompactSize, Vector}, -}; - pub mod amount; pub mod sapling; pub mod sprout; pub mod transparent; +pub mod tze; pub use self::{ amount::Amount, sapling::{OutputDescription, SpendDescription}, @@ -24,134 +12,8 @@ pub use self::{ transparent::{OutPoint, TxIn, TxOut}, }; +#[cfg(feature = "zfuture")] +pub use self::tze::{TzeIn, TzeOut, TzeOutPoint}; + // π_A + π_B + π_C pub const GROTH_PROOF_SIZE: usize = 48 + 96 + 48; - -#[cfg(feature = "zfuture")] -fn to_io_error(_: std::num::TryFromIntError) -> io::Error { - io::Error::new(io::ErrorKind::InvalidData, "value out of range") -} - -#[derive(Clone, Debug, PartialEq)] -#[cfg(feature = "zfuture")] -pub struct TzeIn { - pub prevout: OutPoint, - pub witness: tze::Witness, -} - -/// Transaction encoding and decoding functions conforming to [ZIP 222]. -/// -/// [ZIP 222]: https://zips.z.cash/zip-0222#encoding-in-transactions -#[cfg(feature = "zfuture")] -impl TzeIn { - /// Convenience constructor - pub fn new(prevout: OutPoint, extension_id: u32, mode: u32) -> Self { - TzeIn { - prevout, - witness: tze::Witness { - extension_id, - mode, - payload: vec![], - }, - } - } - - /// Read witness metadata & payload - /// - /// Used to decode the encoded form used within a serialized - /// transaction. - pub fn read(mut reader: &mut R) -> io::Result { - let prevout = OutPoint::read(&mut reader)?; - - let extension_id = CompactSize::read(&mut reader)?; - let mode = CompactSize::read(&mut reader)?; - let payload = Vector::read(&mut reader, |r| r.read_u8())?; - - Ok(TzeIn { - prevout, - witness: tze::Witness { - extension_id: u32::try_from(extension_id).map_err(to_io_error)?, - mode: u32::try_from(mode).map_err(to_io_error)?, - payload, - }, - }) - } - - /// Write without witness data (for signature hashing) - /// - /// This is also used as the prefix for the encoded form used - /// within a serialized transaction. - pub fn write_without_witness(&self, mut writer: W) -> io::Result<()> { - self.prevout.write(&mut writer)?; - - CompactSize::write( - &mut writer, - usize::try_from(self.witness.extension_id).map_err(to_io_error)?, - )?; - - CompactSize::write( - &mut writer, - usize::try_from(self.witness.mode).map_err(to_io_error)?, - ) - } - - /// Write prevout, extension, and mode followed by witness data. - /// - /// This calls [`write_without_witness`] to serialize witness metadata, - /// then appends the witness bytes themselves. This is the encoded - /// form that is used in a serialized transaction. - /// - /// [`write_without_witness`]: TzeIn::write_without_witness - pub fn write(&self, mut writer: W) -> io::Result<()> { - self.write_without_witness(&mut writer)?; - Vector::write(&mut writer, &self.witness.payload, |w, b| w.write_u8(*b)) - } -} - -#[derive(Clone, Debug, PartialEq)] -#[cfg(feature = "zfuture")] -pub struct TzeOut { - pub value: Amount, - pub precondition: tze::Precondition, -} - -#[cfg(feature = "zfuture")] -impl TzeOut { - pub fn read(mut reader: &mut R) -> io::Result { - let value = { - let mut tmp = [0; 8]; - reader.read_exact(&mut tmp)?; - Amount::from_nonnegative_i64_le_bytes(tmp) - } - .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "value out of range"))?; - - let extension_id = CompactSize::read(&mut reader)?; - let mode = CompactSize::read(&mut reader)?; - let payload = Vector::read(&mut reader, |r| r.read_u8())?; - - Ok(TzeOut { - value, - precondition: tze::Precondition { - extension_id: u32::try_from(extension_id).map_err(to_io_error)?, - mode: u32::try_from(mode).map_err(to_io_error)?, - payload, - }, - }) - } - - pub fn write(&self, mut writer: W) -> io::Result<()> { - writer.write_all(&self.value.to_i64_le_bytes())?; - - CompactSize::write( - &mut writer, - usize::try_from(self.precondition.extension_id).map_err(to_io_error)?, - )?; - CompactSize::write( - &mut writer, - usize::try_from(self.precondition.mode).map_err(to_io_error)?, - )?; - Vector::write(&mut writer, &self.precondition.payload, |w, b| { - w.write_u8(*b) - }) - } -} diff --git a/zcash_primitives/src/transaction/components/tze.rs b/zcash_primitives/src/transaction/components/tze.rs new file mode 100644 index 000000000..d26485825 --- /dev/null +++ b/zcash_primitives/src/transaction/components/tze.rs @@ -0,0 +1,171 @@ +//! Structs representing the TZE components within Zcash transactions. +#![cfg(feature = "zfuture")] + +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; + +use std::io::{self, Read, Write}; + +use std::convert::TryFrom; + +use crate::{ + extensions::transparent as tze, + serialize::{CompactSize, Vector}, +}; + +use super::amount::Amount; + +fn to_io_error(_: std::num::TryFromIntError) -> io::Error { + io::Error::new(io::ErrorKind::InvalidData, "value out of range") +} + +#[derive(Clone, Debug, PartialEq)] +pub struct TzeOutPoint { + hash: [u8; 32], + n: u32, +} + +impl TzeOutPoint { + pub fn new(hash: [u8; 32], n: u32) -> Self { + TzeOutPoint { hash, n } + } + + pub fn read(mut reader: R) -> io::Result { + let mut hash = [0u8; 32]; + reader.read_exact(&mut hash)?; + let n = reader.read_u32::()?; + Ok(TzeOutPoint { hash, n }) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + writer.write_all(&self.hash)?; + writer.write_u32::(self.n) + } + + pub fn n(&self) -> u32 { + self.n + } + + pub fn hash(&self) -> &[u8; 32] { + &self.hash + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct TzeIn { + pub prevout: TzeOutPoint, + pub witness: tze::Witness, +} + +/// Transaction encoding and decoding functions conforming to [ZIP 222]. +/// +/// [ZIP 222]: https://zips.z.cash/zip-0222#encoding-in-transactions +impl TzeIn { + /// Convenience constructor + pub fn new(prevout: TzeOutPoint, extension_id: u32, mode: u32) -> Self { + TzeIn { + prevout, + witness: tze::Witness { + extension_id, + mode, + payload: vec![], + }, + } + } + + /// Read witness metadata & payload + /// + /// Used to decode the encoded form used within a serialized + /// transaction. + pub fn read(mut reader: &mut R) -> io::Result { + let prevout = TzeOutPoint::read(&mut reader)?; + + let extension_id = CompactSize::read(&mut reader)?; + let mode = CompactSize::read(&mut reader)?; + let payload = Vector::read(&mut reader, |r| r.read_u8())?; + + Ok(TzeIn { + prevout, + witness: tze::Witness { + extension_id: u32::try_from(extension_id).map_err(to_io_error)?, + mode: u32::try_from(mode).map_err(to_io_error)?, + payload, + }, + }) + } + + /// Write without witness data (for signature hashing) + /// + /// This is also used as the prefix for the encoded form used + /// within a serialized transaction. + pub fn write_without_witness(&self, mut writer: W) -> io::Result<()> { + self.prevout.write(&mut writer)?; + + CompactSize::write( + &mut writer, + usize::try_from(self.witness.extension_id).map_err(to_io_error)?, + )?; + + CompactSize::write( + &mut writer, + usize::try_from(self.witness.mode).map_err(to_io_error)?, + ) + } + + /// Write prevout, extension, and mode followed by witness data. + /// + /// This calls [`write_without_witness`] to serialize witness metadata, + /// then appends the witness bytes themselves. This is the encoded + /// form that is used in a serialized transaction. + /// + /// [`write_without_witness`]: TzeIn::write_without_witness + pub fn write(&self, mut writer: W) -> io::Result<()> { + self.write_without_witness(&mut writer)?; + Vector::write(&mut writer, &self.witness.payload, |w, b| w.write_u8(*b)) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct TzeOut { + pub value: Amount, + pub precondition: tze::Precondition, +} + +impl TzeOut { + pub fn read(mut reader: &mut R) -> io::Result { + let value = { + let mut tmp = [0; 8]; + reader.read_exact(&mut tmp)?; + Amount::from_nonnegative_i64_le_bytes(tmp) + } + .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "value out of range"))?; + + let extension_id = CompactSize::read(&mut reader)?; + let mode = CompactSize::read(&mut reader)?; + let payload = Vector::read(&mut reader, |r| r.read_u8())?; + + Ok(TzeOut { + value, + precondition: tze::Precondition { + extension_id: u32::try_from(extension_id).map_err(to_io_error)?, + mode: u32::try_from(mode).map_err(to_io_error)?, + payload, + }, + }) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + writer.write_all(&self.value.to_i64_le_bytes())?; + + CompactSize::write( + &mut writer, + usize::try_from(self.precondition.extension_id).map_err(to_io_error)?, + )?; + CompactSize::write( + &mut writer, + usize::try_from(self.precondition.mode).map_err(to_io_error)?, + )?; + Vector::write(&mut writer, &self.precondition.payload, |w, b| { + w.write_u8(*b) + }) + } +} diff --git a/zcash_primitives/src/transaction/mod.rs b/zcash_primitives/src/transaction/mod.rs index 29d6e8c54..61e9999e1 100644 --- a/zcash_primitives/src/transaction/mod.rs +++ b/zcash_primitives/src/transaction/mod.rs @@ -508,7 +508,7 @@ pub mod testing { }; #[cfg(feature = "zfuture")] - use super::components::{TzeIn, TzeOut}; + use super::components::{TzeIn, TzeOut, TzeOutPoint}; pub const VALID_OPCODES: [u8; 8] = [ 0x00, // OP_FALSE, @@ -551,6 +551,13 @@ pub mod testing { } } + #[cfg(feature = "zfuture")] + prop_compose! { + pub fn arb_tzeoutpoint()(hash in prop::array::uniform32(1u8..), n in 1..100u32) -> TzeOutPoint { + TzeOutPoint::new(hash, n) + } + } + #[cfg(feature = "zfuture")] prop_compose! { pub fn arb_witness()(extension_id in 0..100u32, mode in 0..100u32, payload in vec(any::(), 32..256)) -> tze::Witness { @@ -560,7 +567,7 @@ pub mod testing { #[cfg(feature = "zfuture")] prop_compose! { - pub fn arb_tzein()(prevout in arb_outpoint(), witness in arb_witness()) -> TzeIn { + pub fn arb_tzein()(prevout in arb_tzeoutpoint(), witness in arb_witness()) -> TzeIn { TzeIn { prevout, witness } } }