From 1fc859d0c5124adb1884cd45edaffe5b6b557892 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Mon, 17 Aug 2020 01:48:47 -0700 Subject: [PATCH] chain: move Spend, Output into sapling The ShieldedData container for the spend and output descriptions of a transaction does *not* move, because it's encoding transaction structure. A good sign that this is the right factoring is that the transaction module now only needs to pull in sapling::{Spend, Output} and not all the internals. --- zebra-chain/src/sapling.rs | 11 +++ zebra-chain/src/sapling/output.rs | 53 ++++++++++++++ zebra-chain/src/sapling/spend.rs | 60 ++++++++++++++++ zebra-chain/src/sapling/tests.rs | 1 + zebra-chain/src/sapling/tests/arbitrary.rs | 66 ++++++++++++++++++ zebra-chain/src/transaction.rs | 2 +- zebra-chain/src/transaction/serialize.rs | 57 --------------- zebra-chain/src/transaction/shielded_data.rs | 50 +------------- .../src/transaction/tests/arbitrary.rs | 69 ++----------------- 9 files changed, 200 insertions(+), 169 deletions(-) create mode 100644 zebra-chain/src/sapling/output.rs create mode 100644 zebra-chain/src/sapling/spend.rs create mode 100644 zebra-chain/src/sapling/tests.rs create mode 100644 zebra-chain/src/sapling/tests/arbitrary.rs diff --git a/zebra-chain/src/sapling.rs b/zebra-chain/src/sapling.rs index 237f8d95..9aca98fe 100644 --- a/zebra-chain/src/sapling.rs +++ b/zebra-chain/src/sapling.rs @@ -1,7 +1,18 @@ //! Sapling-related functionality. +mod spend; +mod output; + +pub use spend::Spend; +pub use output::Output; + +// XXX clean up these modules + pub mod address; pub mod commitment; pub mod keys; pub mod note; pub mod tree; + +#[cfg(test)] +mod tests; \ No newline at end of file diff --git a/zebra-chain/src/sapling/output.rs b/zebra-chain/src/sapling/output.rs new file mode 100644 index 00000000..8e0bb607 --- /dev/null +++ b/zebra-chain/src/sapling/output.rs @@ -0,0 +1,53 @@ +use std::io; + +use crate::{ + primitives::Groth16Proof, + serialization::{serde_helpers, SerializationError, ZcashDeserialize, ZcashSerialize}, +}; + +use super::{commitment, keys, note}; + +/// A _Output Description_, as described in [protocol specification §7.4][ps]. +/// +/// [ps]: https://zips.z.cash/protocol/protocol.pdf#outputencoding +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct Output { + /// A value commitment to the value of the input note. + pub cv: commitment::ValueCommitment, + /// The u-coordinate of the note commitment for the output note. + #[serde(with = "serde_helpers::Fq")] + pub cm_u: jubjub::Fq, + /// An encoding of an ephemeral Jubjub public key. + pub ephemeral_key: keys::EphemeralPublicKey, + /// A ciphertext component for the encrypted output note. + pub enc_ciphertext: note::EncryptedCiphertext, + /// A ciphertext component for the encrypted output note. + pub out_ciphertext: note::OutCiphertext, + /// The ZK output proof. + pub zkproof: Groth16Proof, +} + +impl ZcashSerialize for Output { + fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { + self.cv.zcash_serialize(&mut writer)?; + writer.write_all(&self.cm_u.to_bytes())?; + self.ephemeral_key.zcash_serialize(&mut writer)?; + self.enc_ciphertext.zcash_serialize(&mut writer)?; + self.out_ciphertext.zcash_serialize(&mut writer)?; + self.zkproof.zcash_serialize(&mut writer)?; + Ok(()) + } +} + +impl ZcashDeserialize for Output { + fn zcash_deserialize(mut reader: R) -> Result { + Ok(Output { + cv: commitment::ValueCommitment::zcash_deserialize(&mut reader)?, + cm_u: jubjub::Fq::zcash_deserialize(&mut reader)?, + ephemeral_key: keys::EphemeralPublicKey::zcash_deserialize(&mut reader)?, + enc_ciphertext: note::EncryptedCiphertext::zcash_deserialize(&mut reader)?, + out_ciphertext: note::OutCiphertext::zcash_deserialize(&mut reader)?, + zkproof: Groth16Proof::zcash_deserialize(&mut reader)?, + }) + } +} diff --git a/zebra-chain/src/sapling/spend.rs b/zebra-chain/src/sapling/spend.rs new file mode 100644 index 00000000..da8bf520 --- /dev/null +++ b/zebra-chain/src/sapling/spend.rs @@ -0,0 +1,60 @@ +use std::io; + +use crate::{ + primitives::{ + redjubjub::{self, SpendAuth}, + Groth16Proof, + }, + serialization::{ + ReadZcashExt, SerializationError, WriteZcashExt, ZcashDeserialize, ZcashSerialize, + }, +}; + +use super::{commitment, note, tree}; + +/// A _Spend Description_, as described in [protocol specification §7.3][ps]. +/// +/// [ps]: https://zips.z.cash/protocol/protocol.pdf#spendencoding +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct Spend { + /// A value commitment to the value of the input note. + pub cv: commitment::ValueCommitment, + /// A root of the Sapling note commitment tree at some block height in the past. + pub anchor: tree::SaplingNoteTreeRootHash, + /// The nullifier of the input note. + pub nullifier: note::Nullifier, + /// The randomized public key for `spend_auth_sig`. + pub rk: redjubjub::VerificationKeyBytes, + /// The ZK spend proof. + pub zkproof: Groth16Proof, + /// A signature authorizing this spend. + pub spend_auth_sig: redjubjub::Signature, +} + +impl ZcashSerialize for Spend { + fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { + self.cv.zcash_serialize(&mut writer)?; + writer.write_all(&self.anchor.0[..])?; + writer.write_32_bytes(&self.nullifier.into())?; + writer.write_all(&<[u8; 32]>::from(self.rk)[..])?; + self.zkproof.zcash_serialize(&mut writer)?; + writer.write_all(&<[u8; 64]>::from(self.spend_auth_sig)[..])?; + Ok(()) + } +} + +impl ZcashDeserialize for Spend { + fn zcash_deserialize(mut reader: R) -> Result { + use crate::sapling::{ + commitment::ValueCommitment, note::Nullifier, tree::SaplingNoteTreeRootHash, + }; + Ok(Spend { + cv: ValueCommitment::zcash_deserialize(&mut reader)?, + anchor: SaplingNoteTreeRootHash(reader.read_32_bytes()?), + nullifier: Nullifier::from(reader.read_32_bytes()?), + rk: reader.read_32_bytes()?.into(), + zkproof: Groth16Proof::zcash_deserialize(&mut reader)?, + spend_auth_sig: reader.read_64_bytes()?.into(), + }) + } +} diff --git a/zebra-chain/src/sapling/tests.rs b/zebra-chain/src/sapling/tests.rs new file mode 100644 index 00000000..03dce05a --- /dev/null +++ b/zebra-chain/src/sapling/tests.rs @@ -0,0 +1 @@ +mod arbitrary; \ No newline at end of file diff --git a/zebra-chain/src/sapling/tests/arbitrary.rs b/zebra-chain/src/sapling/tests/arbitrary.rs new file mode 100644 index 00000000..64a64a17 --- /dev/null +++ b/zebra-chain/src/sapling/tests/arbitrary.rs @@ -0,0 +1,66 @@ + +use proptest::{arbitrary::any, array, collection::vec, prelude::*}; + +use crate::primitives::Groth16Proof; + +use super::super::{Spend, Output, keys, tree, commitment, note}; + +impl Arbitrary for Spend { + type Parameters = (); + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + ( + any::(), + any::(), + any::(), + array::uniform32(any::()), + any::(), + vec(any::(), 64), + ) + .prop_map( + |(anchor, cv, nullifier, rpk_bytes, proof, sig_bytes)| Self { + anchor, + cv, + nullifier, + rk: redjubjub::VerificationKeyBytes::from(rpk_bytes), + zkproof: proof, + spend_auth_sig: redjubjub::Signature::from({ + let mut b = [0u8; 64]; + b.copy_from_slice(sig_bytes.as_slice()); + b + }), + }, + ) + .boxed() + } + + type Strategy = BoxedStrategy; +} + +impl Arbitrary for Output { + type Parameters = (); + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + ( + any::(), + any::(), + any::(), + any::(), + any::(), + any::(), + ) + .prop_map( + |(cv, cm, ephemeral_key, enc_ciphertext, out_ciphertext, zkproof)| Self { + cv, + cm_u: cm.extract_u(), + ephemeral_key, + enc_ciphertext, + out_ciphertext, + zkproof, + }, + ) + .boxed() + } + + type Strategy = BoxedStrategy; +} diff --git a/zebra-chain/src/transaction.rs b/zebra-chain/src/transaction.rs index a5273e01..c70d0468 100644 --- a/zebra-chain/src/transaction.rs +++ b/zebra-chain/src/transaction.rs @@ -16,7 +16,7 @@ pub use hash::TransactionHash; pub use joinsplit::{JoinSplit, JoinSplitData}; pub use lock_time::LockTime; pub use memo::Memo; -pub use shielded_data::{Output, ShieldedData, Spend}; +pub use shielded_data::ShieldedData; use crate::{ amount::Amount, diff --git a/zebra-chain/src/transaction/serialize.rs b/zebra-chain/src/transaction/serialize.rs index c3fd65df..4221b0ea 100644 --- a/zebra-chain/src/transaction/serialize.rs +++ b/zebra-chain/src/transaction/serialize.rs @@ -117,63 +117,6 @@ impl ZcashDeserialize for Option> { } } -impl ZcashSerialize for Spend { - fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { - self.cv.zcash_serialize(&mut writer)?; - writer.write_all(&self.anchor.0[..])?; - writer.write_32_bytes(&self.nullifier.into())?; - writer.write_all(&<[u8; 32]>::from(self.rk)[..])?; - self.zkproof.zcash_serialize(&mut writer)?; - writer.write_all(&<[u8; 64]>::from(self.spend_auth_sig)[..])?; - Ok(()) - } -} - -impl ZcashDeserialize for Spend { - fn zcash_deserialize(mut reader: R) -> Result { - use crate::sapling::{ - commitment::ValueCommitment, note::Nullifier, tree::SaplingNoteTreeRootHash, - }; - Ok(Spend { - cv: ValueCommitment::zcash_deserialize(&mut reader)?, - anchor: SaplingNoteTreeRootHash(reader.read_32_bytes()?), - nullifier: Nullifier::from(reader.read_32_bytes()?), - rk: reader.read_32_bytes()?.into(), - zkproof: Groth16Proof::zcash_deserialize(&mut reader)?, - spend_auth_sig: reader.read_64_bytes()?.into(), - }) - } -} - -impl ZcashSerialize for Output { - fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { - self.cv.zcash_serialize(&mut writer)?; - writer.write_all(&self.cm_u.to_bytes())?; - self.ephemeral_key.zcash_serialize(&mut writer)?; - self.enc_ciphertext.zcash_serialize(&mut writer)?; - self.out_ciphertext.zcash_serialize(&mut writer)?; - self.zkproof.zcash_serialize(&mut writer)?; - Ok(()) - } -} - -impl ZcashDeserialize for Output { - fn zcash_deserialize(mut reader: R) -> Result { - use crate::sapling::{ - commitment::ValueCommitment, keys::EphemeralPublicKey, note::EncryptedCiphertext, - note::OutCiphertext, - }; - Ok(Output { - cv: ValueCommitment::zcash_deserialize(&mut reader)?, - cm_u: jubjub::Fq::zcash_deserialize(&mut reader)?, - ephemeral_key: EphemeralPublicKey::zcash_deserialize(&mut reader)?, - enc_ciphertext: EncryptedCiphertext::zcash_deserialize(&mut reader)?, - out_ciphertext: OutCiphertext::zcash_deserialize(&mut reader)?, - zkproof: Groth16Proof::zcash_deserialize(&mut reader)?, - }) - } -} - impl ZcashSerialize for Transaction { fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { // Post-Sapling, transaction size is limited to MAX_BLOCK_BYTES. diff --git a/zebra-chain/src/transaction/shielded_data.rs b/zebra-chain/src/transaction/shielded_data.rs index 891bd5b6..c0c4f1cf 100644 --- a/zebra-chain/src/transaction/shielded_data.rs +++ b/zebra-chain/src/transaction/shielded_data.rs @@ -1,55 +1,11 @@ use futures::future::Either; use crate::{ - primitives::{ - redjubjub::{self, Binding, SpendAuth}, - Groth16Proof, - }, - sapling::{commitment, keys, note, tree}, + primitives::redjubjub::{Binding, Signature}, + sapling::{Output, Spend}, serialization::serde_helpers, }; -/// A _Spend Description_, as described in [protocol specification §7.3][ps]. -/// -/// [ps]: https://zips.z.cash/protocol/protocol.pdf#spendencoding -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct Spend { - /// A value commitment to the value of the input note. - pub cv: commitment::ValueCommitment, - /// A root of the Sapling note commitment tree at some block height in the past. - pub anchor: tree::SaplingNoteTreeRootHash, - /// The nullifier of the input note. - pub nullifier: note::Nullifier, - /// The randomized public key for `spend_auth_sig`. - pub rk: redjubjub::VerificationKeyBytes, - /// The ZK spend proof. - pub zkproof: Groth16Proof, - /// A signature authorizing this spend. - pub spend_auth_sig: redjubjub::Signature, -} - -/// A _Output Description_, as described in [protocol specification §7.4][ps]. -/// -/// [ps]: https://zips.z.cash/protocol/protocol.pdf#outputencoding -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct Output { - /// A value commitment to the value of the input note. - pub cv: commitment::ValueCommitment, - /// The u-coordinate of the note commitment for the output note. - #[serde(with = "serde_helpers::Fq")] - pub cm_u: jubjub::Fq, - /// An encoding of an ephemeral Jubjub public key. - pub ephemeral_key: keys::EphemeralPublicKey, - /// A ciphertext component for the encrypted output note. - pub enc_ciphertext: note::EncryptedCiphertext, - /// A ciphertext component for the encrypted output note. - pub out_ciphertext: note::OutCiphertext, - /// The ZK output proof. - pub zkproof: Groth16Proof, -} - -impl Eq for Output {} - /// Sapling-on-Groth16 spend and output descriptions. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ShieldedData { @@ -75,7 +31,7 @@ pub struct ShieldedData { /// over all output descriptions. pub rest_outputs: Vec, /// A signature on the transaction hash. - pub binding_sig: redjubjub::Signature, + pub binding_sig: Signature, } impl ShieldedData { diff --git a/zebra-chain/src/transaction/tests/arbitrary.rs b/zebra-chain/src/transaction/tests/arbitrary.rs index b6da5e25..935bfde1 100644 --- a/zebra-chain/src/transaction/tests/arbitrary.rs +++ b/zebra-chain/src/transaction/tests/arbitrary.rs @@ -10,7 +10,7 @@ use crate::{ }; use super::super::{ - JoinSplit, JoinSplitData, LockTime, Memo, Output, ShieldedData, Spend, Transaction, + JoinSplit, JoinSplitData, LockTime, Memo, ShieldedData, Transaction, }; impl Transaction { @@ -206,45 +206,17 @@ impl Arbitrary for JoinSplitData

{ type Strategy = BoxedStrategy; } -impl Arbitrary for Output { - type Parameters = (); - - fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { - ( - any::(), - any::(), - any::(), - any::(), - any::(), - any::(), - ) - .prop_map( - |(cv, cm, ephemeral_key, enc_ciphertext, out_ciphertext, zkproof)| Self { - cv, - cm_u: cm.extract_u(), - ephemeral_key, - enc_ciphertext, - out_ciphertext, - zkproof, - }, - ) - .boxed() - } - - type Strategy = BoxedStrategy; -} - impl Arbitrary for ShieldedData { type Parameters = (); fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { ( prop_oneof![ - any::().prop_map(Either::Left), - any::().prop_map(Either::Right) + any::().prop_map(Either::Left), + any::().prop_map(Either::Right) ], - vec(any::(), 0..10), - vec(any::(), 0..10), + vec(any::(), 0..10), + vec(any::(), 0..10), vec(any::(), 64), ) .prop_map(|(first, rest_spends, rest_outputs, sig_bytes)| Self { @@ -263,37 +235,6 @@ impl Arbitrary for ShieldedData { type Strategy = BoxedStrategy; } -impl Arbitrary for Spend { - type Parameters = (); - - fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { - ( - any::(), - any::(), - any::(), - array::uniform32(any::()), - any::(), - vec(any::(), 64), - ) - .prop_map( - |(anchor, cv, nullifier, rpk_bytes, proof, sig_bytes)| Self { - anchor, - cv, - nullifier, - rk: redjubjub::VerificationKeyBytes::from(rpk_bytes), - zkproof: proof, - spend_auth_sig: redjubjub::Signature::from({ - let mut b = [0u8; 64]; - b.copy_from_slice(sig_bytes.as_slice()); - b - }), - }, - ) - .boxed() - } - - type Strategy = BoxedStrategy; -} impl Arbitrary for Transaction { type Parameters = ();