From adba8d9d208d455847125917620053f1af24e8c2 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Sat, 6 Mar 2021 17:05:26 -0300 Subject: [PATCH 1/3] parse sapling data in transaction v5 --- zebra-chain/src/transaction.rs | 4 + zebra-chain/src/transaction/arbitrary.rs | 14 ++- zebra-chain/src/transaction/serialize.rs | 132 ++++++++++++++++------- 3 files changed, 111 insertions(+), 39 deletions(-) diff --git a/zebra-chain/src/transaction.rs b/zebra-chain/src/transaction.rs index 4f2c1285bab..ea8943fd9b9 100644 --- a/zebra-chain/src/transaction.rs +++ b/zebra-chain/src/transaction.rs @@ -112,6 +112,10 @@ pub enum Transaction { inputs: Vec, /// The transparent outputs from the transaction. outputs: Vec, + /// The shielded data for this transaction, if any. + shielded_data: Option, + /// The net value of Sapling spend transfers minus output transfers. + value_balance: Amount, /// The rest of the transaction as bytes rest: Vec, }, diff --git a/zebra-chain/src/transaction/arbitrary.rs b/zebra-chain/src/transaction/arbitrary.rs index 80950803fbe..ec926550d31 100644 --- a/zebra-chain/src/transaction/arbitrary.rs +++ b/zebra-chain/src/transaction/arbitrary.rs @@ -111,14 +111,26 @@ impl Transaction { any::(), transparent::Input::vec_strategy(ledger_state, 10), vec(any::(), 0..10), + option::of(any::()), + any::(), any::>(), ) .prop_map( - |(lock_time, expiry_height, inputs, outputs, rest)| Transaction::V5 { + |( + lock_time, + expiry_height, + inputs, + outputs, + shielded_data, + value_balance, + rest, + )| Transaction::V5 { lock_time, expiry_height, inputs, outputs, + shielded_data, + value_balance, rest, }, ) diff --git a/zebra-chain/src/transaction/serialize.rs b/zebra-chain/src/transaction/serialize.rs index 742da3ba2c6..4a58689b2d4 100644 --- a/zebra-chain/src/transaction/serialize.rs +++ b/zebra-chain/src/transaction/serialize.rs @@ -66,6 +66,65 @@ impl ZcashDeserialize for Option> { } } +struct SigShieldedData<'a> { + sig: bool, + shielded_data: &'a Option, +} + +impl ZcashSerialize for SigShieldedData<'_> { + fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { + let sig = self.sig; + let shielded_data = self.shielded_data; + match shielded_data { + None => { + // Signal no shielded spends and no shielded outputs. + writer.write_compactsize(0)?; + writer.write_compactsize(0)?; + } + Some(shielded_data) => { + writer.write_compactsize(shielded_data.spends().count() as u64)?; + for spend in shielded_data.spends() { + spend.zcash_serialize(&mut writer)?; + } + writer.write_compactsize(shielded_data.outputs().count() as u64)?; + for output in shielded_data.outputs() { + output.zcash_serialize(&mut writer)?; + } + if sig { + writer.write_all(&<[u8; 64]>::from(shielded_data.binding_sig)[..])? + } + } + } + Ok(()) + } +} + +fn deserialize_shielded_data( + mut reader: R, + mut shielded_spends: Vec, + mut shielded_outputs: Vec, +) -> Result, SerializationError> { + use futures::future::Either::*; + + if !shielded_spends.is_empty() { + Ok(Some(ShieldedData { + first: Left(shielded_spends.remove(0)), + rest_spends: shielded_spends, + rest_outputs: shielded_outputs, + binding_sig: reader.read_64_bytes()?.into(), + })) + } else if !shielded_outputs.is_empty() { + Ok(Some(ShieldedData { + first: Right(shielded_outputs.remove(0)), + rest_spends: shielded_spends, + rest_outputs: shielded_outputs, + binding_sig: reader.read_64_bytes()?.into(), + })) + } else { + Ok(None) + } +} + impl ZcashSerialize for Transaction { fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { // Post-Sapling, transaction size is limited to MAX_BLOCK_BYTES. @@ -152,29 +211,19 @@ impl ZcashSerialize for Transaction { // instead we have to interleave serialization of the // ShieldedData and the JoinSplitData. - match shielded_data { - None => { - // Signal no shielded spends and no shielded outputs. - writer.write_compactsize(0)?; - writer.write_compactsize(0)?; - } - Some(shielded_data) => { - writer.write_compactsize(shielded_data.spends().count() as u64)?; - for spend in shielded_data.spends() { - spend.zcash_serialize(&mut writer)?; - } - writer.write_compactsize(shielded_data.outputs().count() as u64)?; - for output in shielded_data.outputs() { - output.zcash_serialize(&mut writer)?; - } - } - } + // Serialize ShieldedData without doing the binding_sig. + let sig_shielded_data = SigShieldedData { + sig: false, + shielded_data, + }; + sig_shielded_data.zcash_serialize(&mut writer)?; match joinsplit_data { None => writer.write_compactsize(0)?, Some(jsd) => jsd.zcash_serialize(&mut writer)?, } + // Manually write the binding_sig after the JoinSplitData. match shielded_data { Some(sd) => writer.write_all(&<[u8; 64]>::from(sd.binding_sig)[..])?, None => {} @@ -185,6 +234,8 @@ impl ZcashSerialize for Transaction { expiry_height, inputs, outputs, + shielded_data, + value_balance, rest, } => { // Write version 5 and set the fOverwintered bit. @@ -194,6 +245,20 @@ impl ZcashSerialize for Transaction { writer.write_u32::(expiry_height.0)?; inputs.zcash_serialize(&mut writer)?; outputs.zcash_serialize(&mut writer)?; + + // Version 5 internal structure is different from version 4. + // Here we can do the binding signature without interleave + // the serialization. + + // Serialize the ShieldedData including the binding_sig. + let sig_shielded_data = SigShieldedData { + sig: true, + shielded_data, + }; + sig_shielded_data.zcash_serialize(&mut writer)?; + + value_balance.zcash_serialize(&mut writer)?; + // write the rest writer.write_all(rest)?; } @@ -267,28 +332,11 @@ impl ZcashDeserialize for Transaction { let lock_time = LockTime::zcash_deserialize(&mut reader)?; let expiry_height = block::Height(reader.read_u32::()?); let value_balance = (&mut reader).zcash_deserialize_into()?; - let mut shielded_spends = Vec::zcash_deserialize(&mut reader)?; - let mut shielded_outputs = Vec::zcash_deserialize(&mut reader)?; + let shielded_spends = Vec::zcash_deserialize(&mut reader)?; + let shielded_outputs = Vec::zcash_deserialize(&mut reader)?; let joinsplit_data = OptV4Jsd::zcash_deserialize(&mut reader)?; - - use futures::future::Either::*; - let shielded_data = if !shielded_spends.is_empty() { - Some(ShieldedData { - first: Left(shielded_spends.remove(0)), - rest_spends: shielded_spends, - rest_outputs: shielded_outputs, - binding_sig: reader.read_64_bytes()?.into(), - }) - } else if !shielded_outputs.is_empty() { - Some(ShieldedData { - first: Right(shielded_outputs.remove(0)), - rest_spends: shielded_spends, - rest_outputs: shielded_outputs, - binding_sig: reader.read_64_bytes()?.into(), - }) - } else { - None - }; + let shielded_data = + deserialize_shielded_data(&mut reader, shielded_spends, shielded_outputs)?; Ok(Transaction::V4 { inputs, @@ -309,6 +357,12 @@ impl ZcashDeserialize for Transaction { let expiry_height = block::Height(reader.read_u32::()?); let inputs = Vec::zcash_deserialize(&mut reader)?; let outputs = Vec::zcash_deserialize(&mut reader)?; + let shielded_spends = Vec::zcash_deserialize(&mut reader)?; + let shielded_outputs = Vec::zcash_deserialize(&mut reader)?; + let shielded_data = + deserialize_shielded_data(&mut reader, shielded_spends, shielded_outputs)?; + let value_balance = (&mut reader).zcash_deserialize_into()?; + let mut rest = Vec::new(); reader.read_to_end(&mut rest)?; @@ -317,6 +371,8 @@ impl ZcashDeserialize for Transaction { expiry_height, inputs, outputs, + shielded_data, + value_balance, rest, }) } From 696affed988a2b647885c3acd5e1dc7165b37e8c Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Sat, 6 Mar 2021 17:53:25 -0300 Subject: [PATCH 2/3] add shielded data for V5 to sapling_nullifiers() --- zebra-chain/src/transaction.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/zebra-chain/src/transaction.rs b/zebra-chain/src/transaction.rs index ea8943fd9b9..743aead6d4d 100644 --- a/zebra-chain/src/transaction.rs +++ b/zebra-chain/src/transaction.rs @@ -226,21 +226,26 @@ impl Transaction { // This function returns a boxed iterator because the different // transaction variants end up having different iterator types match self { - // JoinSplits with Groth Proofs + // Transactions with ShieldedData Transaction::V4 { shielded_data: Some(shielded_data), .. } => Box::new(shielded_data.nullifiers()), - Transaction::V5 { .. } => { - unimplemented!("v5 transaction format as specified in ZIP-225") - } - // No JoinSplits + Transaction::V5 { + shielded_data: Some(shielded_data), + .. + } => Box::new(shielded_data.nullifiers()), + // No ShieldedData Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } | Transaction::V4 { shielded_data: None, .. + } + | Transaction::V5 { + shielded_data: None, + .. } => Box::new(std::iter::empty()), } } From a42cf36ac293f5942ccd5ccf184ecb25a685a121 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Mon, 8 Mar 2021 16:38:12 -0300 Subject: [PATCH 3/3] change `value_balance` to `sapling_value_balance` everywhere --- zebra-chain/src/transaction.rs | 4 ++-- zebra-chain/src/transaction/arbitrary.rs | 8 ++++---- zebra-chain/src/transaction/serialize.rs | 16 ++++++++-------- zebra-chain/src/transaction/shielded_data.rs | 5 +++-- zebra-chain/src/transaction/sighash.rs | 9 ++++++--- zebra-consensus/src/transaction.rs | 6 +++--- zebra-consensus/src/transaction/check.rs | 4 ++-- 7 files changed, 28 insertions(+), 24 deletions(-) diff --git a/zebra-chain/src/transaction.rs b/zebra-chain/src/transaction.rs index 743aead6d4d..28578d1cf73 100644 --- a/zebra-chain/src/transaction.rs +++ b/zebra-chain/src/transaction.rs @@ -93,7 +93,7 @@ pub enum Transaction { /// The latest block height that this transaction can be added to the chain. expiry_height: block::Height, /// The net value of Sapling spend transfers minus output transfers. - value_balance: Amount, + sapling_value_balance: Amount, /// The JoinSplit data for this transaction, if any. joinsplit_data: Option>, /// The shielded data for this transaction, if any. @@ -115,7 +115,7 @@ pub enum Transaction { /// The shielded data for this transaction, if any. shielded_data: Option, /// The net value of Sapling spend transfers minus output transfers. - value_balance: Amount, + sapling_value_balance: Amount, /// The rest of the transaction as bytes rest: Vec, }, diff --git a/zebra-chain/src/transaction/arbitrary.rs b/zebra-chain/src/transaction/arbitrary.rs index ec926550d31..2f87cbcde51 100644 --- a/zebra-chain/src/transaction/arbitrary.rs +++ b/zebra-chain/src/transaction/arbitrary.rs @@ -89,7 +89,7 @@ impl Transaction { outputs, lock_time, expiry_height, - value_balance, + sapling_value_balance, shielded_data, joinsplit_data, )| Transaction::V4 { @@ -97,7 +97,7 @@ impl Transaction { outputs, lock_time, expiry_height, - value_balance, + sapling_value_balance, shielded_data, joinsplit_data, }, @@ -122,7 +122,7 @@ impl Transaction { inputs, outputs, shielded_data, - value_balance, + sapling_value_balance, rest, )| Transaction::V5 { lock_time, @@ -130,7 +130,7 @@ impl Transaction { inputs, outputs, shielded_data, - value_balance, + sapling_value_balance, rest, }, ) diff --git a/zebra-chain/src/transaction/serialize.rs b/zebra-chain/src/transaction/serialize.rs index 4a58689b2d4..a62789c628d 100644 --- a/zebra-chain/src/transaction/serialize.rs +++ b/zebra-chain/src/transaction/serialize.rs @@ -190,7 +190,7 @@ impl ZcashSerialize for Transaction { outputs, lock_time, expiry_height, - value_balance, + sapling_value_balance, shielded_data, joinsplit_data, } => { @@ -201,7 +201,7 @@ impl ZcashSerialize for Transaction { outputs.zcash_serialize(&mut writer)?; lock_time.zcash_serialize(&mut writer)?; writer.write_u32::(expiry_height.0)?; - value_balance.zcash_serialize(&mut writer)?; + sapling_value_balance.zcash_serialize(&mut writer)?; // The previous match arms serialize in one go, because the // internal structure happens to nicely line up with the @@ -235,7 +235,7 @@ impl ZcashSerialize for Transaction { inputs, outputs, shielded_data, - value_balance, + sapling_value_balance, rest, } => { // Write version 5 and set the fOverwintered bit. @@ -257,7 +257,7 @@ impl ZcashSerialize for Transaction { }; sig_shielded_data.zcash_serialize(&mut writer)?; - value_balance.zcash_serialize(&mut writer)?; + sapling_value_balance.zcash_serialize(&mut writer)?; // write the rest writer.write_all(rest)?; @@ -331,7 +331,7 @@ impl ZcashDeserialize for Transaction { let outputs = Vec::zcash_deserialize(&mut reader)?; let lock_time = LockTime::zcash_deserialize(&mut reader)?; let expiry_height = block::Height(reader.read_u32::()?); - let value_balance = (&mut reader).zcash_deserialize_into()?; + let sapling_value_balance = (&mut reader).zcash_deserialize_into()?; let shielded_spends = Vec::zcash_deserialize(&mut reader)?; let shielded_outputs = Vec::zcash_deserialize(&mut reader)?; let joinsplit_data = OptV4Jsd::zcash_deserialize(&mut reader)?; @@ -343,7 +343,7 @@ impl ZcashDeserialize for Transaction { outputs, lock_time, expiry_height, - value_balance, + sapling_value_balance, shielded_data, joinsplit_data, }) @@ -361,7 +361,7 @@ impl ZcashDeserialize for Transaction { let shielded_outputs = Vec::zcash_deserialize(&mut reader)?; let shielded_data = deserialize_shielded_data(&mut reader, shielded_spends, shielded_outputs)?; - let value_balance = (&mut reader).zcash_deserialize_into()?; + let sapling_value_balance = (&mut reader).zcash_deserialize_into()?; let mut rest = Vec::new(); reader.read_to_end(&mut rest)?; @@ -372,7 +372,7 @@ impl ZcashDeserialize for Transaction { inputs, outputs, shielded_data, - value_balance, + sapling_value_balance, rest, }) } diff --git a/zebra-chain/src/transaction/shielded_data.rs b/zebra-chain/src/transaction/shielded_data.rs index 769daab7813..28e0358c3a5 100644 --- a/zebra-chain/src/transaction/shielded_data.rs +++ b/zebra-chain/src/transaction/shielded_data.rs @@ -98,11 +98,12 @@ impl ShieldedData { /// https://zips.z.cash/protocol/protocol.pdf#saplingbalance pub fn binding_verification_key( &self, - value_balance: Amount, + sapling_value_balance: Amount, ) -> redjubjub::VerificationKeyBytes { let cv_old: ValueCommitment = self.spends().map(|spend| spend.cv).sum(); let cv_new: ValueCommitment = self.outputs().map(|output| output.cv).sum(); - let cv_balance: ValueCommitment = ValueCommitment::new(jubjub::Fr::zero(), value_balance); + let cv_balance: ValueCommitment = + ValueCommitment::new(jubjub::Fr::zero(), sapling_value_balance); let key_bytes: [u8; 32] = (cv_old - cv_new - cv_balance).into(); diff --git a/zebra-chain/src/transaction/sighash.rs b/zebra-chain/src/transaction/sighash.rs index 2b7544b5478..3f51a6c878a 100644 --- a/zebra-chain/src/transaction/sighash.rs +++ b/zebra-chain/src/transaction/sighash.rs @@ -486,13 +486,16 @@ impl<'a> SigHasher<'a> { fn hash_value_balance(&self, mut writer: W) -> Result<(), io::Error> { use Transaction::*; - let value_balance = match self.trans { - V4 { value_balance, .. } => value_balance, + let sapling_value_balance = match self.trans { + V4 { + sapling_value_balance, + .. + } => sapling_value_balance, V5 { .. } => unimplemented!("v5 transaction hash as specified in ZIP-225 and ZIP-244"), V1 { .. } | V2 { .. } | V3 { .. } => unreachable!(ZIP243_EXPLANATION), }; - writer.write_all(&value_balance.to_bytes())?; + writer.write_all(&sapling_value_balance.to_bytes())?; Ok(()) } diff --git a/zebra-consensus/src/transaction.rs b/zebra-consensus/src/transaction.rs index a2716dff12e..dfa5782cdd1 100644 --- a/zebra-consensus/src/transaction.rs +++ b/zebra-consensus/src/transaction.rs @@ -141,7 +141,7 @@ where // outputs, // lock_time, // expiry_height, - value_balance, + sapling_value_balance, joinsplit_data, shielded_data, .. @@ -194,7 +194,7 @@ where } if let Some(shielded_data) = shielded_data { - check::shielded_balances_match(&shielded_data, *value_balance)?; + check::shielded_balances_match(&shielded_data, *sapling_value_balance)?; for spend in shielded_data.spends() { // TODO: check that spend.cv and spend.rk are NOT of small // order. @@ -238,7 +238,7 @@ where // transaction to verify. }); - let bvk = shielded_data.binding_verification_key(*value_balance); + let bvk = shielded_data.binding_verification_key(*sapling_value_balance); let _rsp = redjubjub_verifier .ready_and() .await? diff --git a/zebra-consensus/src/transaction/check.rs b/zebra-consensus/src/transaction/check.rs index 621ac71dd9a..7d2f758a1f8 100644 --- a/zebra-consensus/src/transaction/check.rs +++ b/zebra-consensus/src/transaction/check.rs @@ -82,10 +82,10 @@ pub fn has_inputs_and_outputs(tx: &Transaction) -> Result<(), TransactionError> /// https://zips.z.cash/protocol/protocol.pdf#consensusfrombitcoin pub fn shielded_balances_match( shielded_data: &ShieldedData, - value_balance: Amount, + sapling_value_balance: Amount, ) -> Result<(), TransactionError> { if (shielded_data.spends().count() + shielded_data.outputs().count() != 0) - || i64::from(value_balance) == 0 + || i64::from(sapling_value_balance) == 0 { Ok(()) } else {