From 823b06b219990395d48ffffbeea717267ae5121e Mon Sep 17 00:00:00 2001 From: Deirdre Connolly Date: Wed, 15 Jul 2020 04:50:05 -0400 Subject: [PATCH] Break out subtypes into modules, including arbitrary's --- zebra-chain/src/notes.rs | 14 +- zebra-chain/src/notes/arbitrary.rs | 19 ++ zebra-chain/src/notes/memo.rs | 22 +- zebra-chain/src/notes/sapling.rs | 304 +----------------- .../src/notes/{tests => sapling}/arbitrary.rs | 34 +- zebra-chain/src/notes/sapling/ciphertexts.rs | 131 ++++++++ zebra-chain/src/notes/sapling/commitments.rs | 114 +++++++ zebra-chain/src/notes/sapling/nullifiers.rs | 31 ++ zebra-chain/src/notes/sprout.rs | 194 +---------- zebra-chain/src/notes/sprout/arbitrary.rs | 35 ++ zebra-chain/src/notes/sprout/ciphertexts.rs | 74 +++++ zebra-chain/src/notes/sprout/commitments.rs | 32 ++ zebra-chain/src/notes/sprout/nullifiers.rs | 89 +++++ zebra-chain/src/notes/tests.rs | 1 - 14 files changed, 579 insertions(+), 515 deletions(-) create mode 100644 zebra-chain/src/notes/arbitrary.rs rename zebra-chain/src/notes/{tests => sapling}/arbitrary.rs (50%) create mode 100644 zebra-chain/src/notes/sapling/ciphertexts.rs create mode 100644 zebra-chain/src/notes/sapling/commitments.rs create mode 100644 zebra-chain/src/notes/sapling/nullifiers.rs create mode 100644 zebra-chain/src/notes/sprout/arbitrary.rs create mode 100644 zebra-chain/src/notes/sprout/ciphertexts.rs create mode 100644 zebra-chain/src/notes/sprout/commitments.rs create mode 100644 zebra-chain/src/notes/sprout/nullifiers.rs delete mode 100644 zebra-chain/src/notes/tests.rs diff --git a/zebra-chain/src/notes.rs b/zebra-chain/src/notes.rs index 0ea10ac9..5594ef8c 100644 --- a/zebra-chain/src/notes.rs +++ b/zebra-chain/src/notes.rs @@ -1,17 +1,5 @@ //! Note encryption types. mod memo; + pub mod sapling; pub mod sprout; - -#[cfg(test)] -mod tests; - -/// The randomness used in the Pedersen Hash for note commitment. -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct NoteCommitmentRandomness(pub [u8; 32]); - -impl AsRef<[u8]> for NoteCommitmentRandomness { - fn as_ref(&self) -> &[u8] { - &self.0 - } -} diff --git a/zebra-chain/src/notes/arbitrary.rs b/zebra-chain/src/notes/arbitrary.rs new file mode 100644 index 00000000..7ca9994c --- /dev/null +++ b/zebra-chain/src/notes/arbitrary.rs @@ -0,0 +1,19 @@ +use crate::notes::memo::Memo; + +use proptest::{arbitrary::any, array, collection::vec, prelude::*}; + +impl Arbitrary for Memo { + type Parameters = (); + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + (vec(any::(), 512)) + .prop_map(|v| { + let mut bytes = [0; 512]; + bytes.copy_from_slice(v.as_slice()); + Memo(Box::new(bytes)) + }) + .boxed() + } + + type Strategy = BoxedStrategy; +} diff --git a/zebra-chain/src/notes/memo.rs b/zebra-chain/src/notes/memo.rs index 9d9bfd1e..dec549a8 100644 --- a/zebra-chain/src/notes/memo.rs +++ b/zebra-chain/src/notes/memo.rs @@ -1,8 +1,5 @@ use std::{cmp, convert::TryFrom, fmt}; -#[cfg(test)] -use proptest::{arbitrary::Arbitrary, collection::vec, prelude::*}; - /// A 512-byte _Memo_ field associated with a note, as described in /// [protocol specification §5.5][ps]. /// @@ -13,7 +10,7 @@ use proptest::{arbitrary::Arbitrary, collection::vec, prelude::*}; /// /// [ps]: https://zips.z.cash/protocol/protocol.pdf#notept #[derive(Clone)] -pub struct Memo(Box<[u8; 512]>); +pub struct Memo(pub(crate) Box<[u8; 512]>); impl<'a> TryFrom<&'a [u8]> for Memo { type Error = &'static str; @@ -51,23 +48,6 @@ impl fmt::Debug for Memo { } } -#[cfg(test)] -impl Arbitrary for Memo { - type Parameters = (); - - fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { - (vec(any::(), 512)) - .prop_map(|v| { - let mut bytes = [0; 512]; - bytes.copy_from_slice(v.as_slice()); - Memo(Box::new(bytes)) - }) - .boxed() - } - - type Strategy = BoxedStrategy; -} - #[test] fn memo_fmt() { let memo = Memo(Box::new( diff --git a/zebra-chain/src/notes/sapling.rs b/zebra-chain/src/notes/sapling.rs index 788033f4..f949df08 100644 --- a/zebra-chain/src/notes/sapling.rs +++ b/zebra-chain/src/notes/sapling.rs @@ -1,47 +1,21 @@ #![allow(clippy::unit_arg)] #![allow(dead_code)] -use std::{fmt, io}; - #[cfg(test)] -use proptest::{arbitrary::Arbitrary, collection::vec, prelude::*}; +mod arbitrary; +mod ciphertexts; +mod commitments; +mod nullifiers; -use super::*; use crate::{ keys::sapling::{Diversifier, TransmissionKey}, - serde_helpers, - serialization::{ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize}, + notes::memo::Memo, types::amount::{Amount, NonNegative}, }; -/// A Nullifier for Sapling transactions -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -#[cfg_attr(test, derive(proptest_derive::Arbitrary))] -pub struct Nullifier([u8; 32]); - -impl From<[u8; 32]> for Nullifier { - fn from(buf: [u8; 32]) -> Self { - Self(buf) - } -} - -impl ZcashDeserialize for Nullifier { - fn zcash_deserialize(mut reader: R) -> Result { - let bytes = reader.read_32_bytes()?; - - Ok(Self(bytes)) - } -} - -impl ZcashSerialize for Nullifier { - fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { - writer.write_all(&self.0[..]) - } -} - -/// The randomness used in the Pedersen Hash for note commitment. -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct CommitmentRandomness(redjubjub::Randomizer); +pub use ciphertexts::{EncryptedCiphertext, OutCiphertext}; +pub use commitments::{CommitmentRandomness, NoteCommitment, ValueCommitment}; +pub use nullifiers::Nullifier; /// A Note represents that a value is spendable by the recipient who /// holds the spending key corresponding to a given shielded payment @@ -64,270 +38,10 @@ impl Note { } } -/// -#[derive(Clone, Copy, Deserialize, PartialEq, Serialize)] -//#[cfg_attr(test, derive(proptest_derive::Arbitrary))] -pub struct NoteCommitment(#[serde(with = "serde_helpers::AffinePoint")] pub jubjub::AffinePoint); - -impl fmt::Debug for NoteCommitment { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("NoteCommitment") - .field("u", &hex::encode(self.0.get_u().to_bytes())) - .field("v", &hex::encode(self.0.get_v().to_bytes())) - .finish() - } -} - -impl From<[u8; 32]> for NoteCommitment { - fn from(bytes: [u8; 32]) -> Self { - Self(jubjub::AffinePoint::from_bytes(bytes).unwrap()) - } -} - -impl Eq for NoteCommitment {} - -impl From for [u8; 32] { - fn from(cm: NoteCommitment) -> [u8; 32] { - cm.0.to_bytes() - } -} - -impl ZcashSerialize for NoteCommitment { - // The u-coordinate of the note commitment, for the output note - // LEBS2OSP256(cm_u) where cm_u = Extract_J(r)(cm). ??? - fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { - writer.write_all(&self.0.to_bytes())?; - Ok(()) - } -} - -impl ZcashDeserialize for NoteCommitment { - fn zcash_deserialize(mut reader: R) -> Result { - Ok(Self( - jubjub::AffinePoint::from_bytes(reader.read_32_bytes()?).unwrap(), - )) - } -} - -impl NoteCommitment { - /// Hash Extractor for Jubjub (?) - /// - /// https://zips.z.cash/protocol/protocol.pdf#concreteextractorjubjub - pub fn extract_u(self) -> jubjub::Fq { - self.0.get_u() - } -} - /// The decrypted form of encrypted Sapling notes on the blockchain. pub struct NotePlaintext { diversifier: Diversifier, value: Amount, rcm: CommitmentRandomness, - memo: memo::Memo, -} - -/// A ciphertext component for encrypted output notes. -#[derive(Deserialize, Serialize)] -pub struct EncryptedCiphertext(#[serde(with = "serde_helpers::BigArray")] pub [u8; 580]); - -impl fmt::Debug for EncryptedCiphertext { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_tuple("EncryptedCiphertext") - .field(&hex::encode(&self.0[..])) - .finish() - } -} - -// These impls all only exist because of array length restrictions. - -impl Copy for EncryptedCiphertext {} - -impl Clone for EncryptedCiphertext { - fn clone(&self) -> Self { - let mut bytes = [0; 580]; - bytes[..].copy_from_slice(&self.0[..]); - Self(bytes) - } -} - -impl PartialEq for EncryptedCiphertext { - fn eq(&self, other: &Self) -> bool { - self.0[..] == other.0[..] - } -} - -impl Eq for EncryptedCiphertext {} - -impl ZcashSerialize for EncryptedCiphertext { - fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { - writer.write_all(&self.0[..])?; - Ok(()) - } -} - -impl ZcashDeserialize for EncryptedCiphertext { - fn zcash_deserialize(mut reader: R) -> Result { - let mut bytes = [0; 580]; - reader.read_exact(&mut bytes[..])?; - Ok(Self(bytes)) - } -} - -#[cfg(test)] -impl Arbitrary for EncryptedCiphertext { - type Parameters = (); - - fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { - (vec(any::(), 580)) - .prop_map(|v| { - let mut bytes = [0; 580]; - bytes.copy_from_slice(v.as_slice()); - Self(bytes) - }) - .boxed() - } - - type Strategy = BoxedStrategy; -} - -/// A ciphertext component for encrypted output notes. -#[derive(Deserialize, Serialize)] -pub struct OutCiphertext(#[serde(with = "serde_helpers::BigArray")] pub [u8; 80]); - -impl fmt::Debug for OutCiphertext { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_tuple("OutCiphertext") - .field(&hex::encode(&self.0[..])) - .finish() - } -} - -// These impls all only exist because of array length restrictions. - -impl Copy for OutCiphertext {} - -impl Clone for OutCiphertext { - fn clone(&self) -> Self { - let mut bytes = [0; 80]; - bytes[..].copy_from_slice(&self.0[..]); - Self(bytes) - } -} - -impl PartialEq for OutCiphertext { - fn eq(&self, other: &Self) -> bool { - self.0[..] == other.0[..] - } -} - -impl Eq for OutCiphertext {} - -impl ZcashSerialize for OutCiphertext { - fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { - writer.write_all(&self.0[..])?; - Ok(()) - } -} - -impl ZcashDeserialize for OutCiphertext { - fn zcash_deserialize(mut reader: R) -> Result { - let mut bytes = [0; 80]; - reader.read_exact(&mut bytes[..])?; - Ok(Self(bytes)) - } -} - -#[cfg(test)] -impl Arbitrary for OutCiphertext { - type Parameters = (); - - fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { - (vec(any::(), 80)) - .prop_map(|v| { - let mut bytes = [0; 80]; - bytes.copy_from_slice(v.as_slice()); - Self(bytes) - }) - .boxed() - } - - type Strategy = BoxedStrategy; -} - -/// A Homomorphic Pedersen commitment to the value of a note, used in -/// Spend and Output Descriptions. -/// -/// https://zips.z.cash/protocol/protocol.pdf#concretehomomorphiccommit -#[derive(Clone, Deserialize, PartialEq, Serialize)] -//#[cfg_attr(test, derive(proptest_derive::Arbitrary))] -pub struct ValueCommitment(#[serde(with = "serde_helpers::AffinePoint")] pub jubjub::AffinePoint); - -impl fmt::Debug for ValueCommitment { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("ValueCommitment") - .field("u", &hex::encode(self.0.get_u().to_bytes())) - .field("v", &hex::encode(self.0.get_v().to_bytes())) - .finish() - } -} - -impl From<[u8; 32]> for ValueCommitment { - fn from(bytes: [u8; 32]) -> Self { - Self(jubjub::AffinePoint::from_bytes(bytes).unwrap()) - } -} - -impl Eq for ValueCommitment {} - -impl From for [u8; 32] { - fn from(cm: ValueCommitment) -> [u8; 32] { - cm.0.to_bytes() - } -} - -/// LEBS2OSP256(repr_J(cv)) -/// -/// https://zips.z.cash/protocol/protocol.pdf#spendencoding -/// https://zips.z.cash/protocol/protocol.pdf#jubjub -impl ZcashSerialize for ValueCommitment { - fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { - writer.write_all(&self.0.to_bytes())?; - Ok(()) - } -} - -impl ZcashDeserialize for ValueCommitment { - fn zcash_deserialize(mut reader: R) -> Result { - Ok(Self( - jubjub::AffinePoint::from_bytes(reader.read_32_bytes()?).unwrap(), - )) - } -} - -#[cfg(test)] -proptest! { - - #[test] - fn encrypted_ciphertext_roundtrip(ec in any::()) { - - let mut data = Vec::new(); - - ec.zcash_serialize(&mut data).expect("EncryptedCiphertext should serialize"); - - let ec2 = EncryptedCiphertext::zcash_deserialize(&data[..]).expect("randomized EncryptedCiphertext should deserialize"); - - prop_assert_eq![ec, ec2]; - } - - #[test] - fn out_ciphertext_roundtrip(oc in any::()) { - - let mut data = Vec::new(); - - oc.zcash_serialize(&mut data).expect("OutCiphertext should serialize"); - - let oc2 = OutCiphertext::zcash_deserialize(&data[..]).expect("randomized OutCiphertext should deserialize"); - - prop_assert_eq![oc, oc2]; - } + memo: Memo, } diff --git a/zebra-chain/src/notes/tests/arbitrary.rs b/zebra-chain/src/notes/sapling/arbitrary.rs similarity index 50% rename from zebra-chain/src/notes/tests/arbitrary.rs rename to zebra-chain/src/notes/sapling/arbitrary.rs index f3b04daf..1526f1a2 100644 --- a/zebra-chain/src/notes/tests/arbitrary.rs +++ b/zebra-chain/src/notes/sapling/arbitrary.rs @@ -1,6 +1,38 @@ +use proptest::{arbitrary::any, array, collection::vec, prelude::*}; + use crate::notes::sapling; -use proptest::{array, prelude::*}; +impl Arbitrary for sapling::EncryptedCiphertext { + type Parameters = (); + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + (vec(any::(), 580)) + .prop_map(|v| { + let mut bytes = [0; 580]; + bytes.copy_from_slice(v.as_slice()); + Self(bytes) + }) + .boxed() + } + + type Strategy = BoxedStrategy; +} + +impl Arbitrary for sapling::OutCiphertext { + type Parameters = (); + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + (vec(any::(), 80)) + .prop_map(|v| { + let mut bytes = [0; 80]; + bytes.copy_from_slice(v.as_slice()); + Self(bytes) + }) + .boxed() + } + + type Strategy = BoxedStrategy; +} impl Arbitrary for sapling::NoteCommitment { type Parameters = (); diff --git a/zebra-chain/src/notes/sapling/ciphertexts.rs b/zebra-chain/src/notes/sapling/ciphertexts.rs new file mode 100644 index 00000000..e68f90f6 --- /dev/null +++ b/zebra-chain/src/notes/sapling/ciphertexts.rs @@ -0,0 +1,131 @@ +use std::{fmt, io}; + +#[cfg(test)] +use proptest::{arbitrary::any, prelude::*}; + +use crate::{ + serde_helpers, + serialization::{SerializationError, ZcashDeserialize, ZcashSerialize}, +}; + +/// A ciphertext component for encrypted output notes. +#[derive(Deserialize, Serialize)] +pub struct EncryptedCiphertext(#[serde(with = "serde_helpers::BigArray")] pub [u8; 580]); + +impl fmt::Debug for EncryptedCiphertext { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("EncryptedCiphertext") + .field(&hex::encode(&self.0[..])) + .finish() + } +} + +// These impls all only exist because of array length restrictions. + +impl Copy for EncryptedCiphertext {} + +impl Clone for EncryptedCiphertext { + fn clone(&self) -> Self { + let mut bytes = [0; 580]; + bytes[..].copy_from_slice(&self.0[..]); + Self(bytes) + } +} + +impl PartialEq for EncryptedCiphertext { + fn eq(&self, other: &Self) -> bool { + self.0[..] == other.0[..] + } +} + +impl Eq for EncryptedCiphertext {} + +impl ZcashSerialize for EncryptedCiphertext { + fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { + writer.write_all(&self.0[..])?; + Ok(()) + } +} + +impl ZcashDeserialize for EncryptedCiphertext { + fn zcash_deserialize(mut reader: R) -> Result { + let mut bytes = [0; 580]; + reader.read_exact(&mut bytes[..])?; + Ok(Self(bytes)) + } +} + +/// A ciphertext component for encrypted output notes. +#[derive(Deserialize, Serialize)] +pub struct OutCiphertext(#[serde(with = "serde_helpers::BigArray")] pub [u8; 80]); + +impl fmt::Debug for OutCiphertext { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("OutCiphertext") + .field(&hex::encode(&self.0[..])) + .finish() + } +} + +// These impls all only exist because of array length restrictions. + +impl Copy for OutCiphertext {} + +impl Clone for OutCiphertext { + fn clone(&self) -> Self { + let mut bytes = [0; 80]; + bytes[..].copy_from_slice(&self.0[..]); + Self(bytes) + } +} + +impl PartialEq for OutCiphertext { + fn eq(&self, other: &Self) -> bool { + self.0[..] == other.0[..] + } +} + +impl Eq for OutCiphertext {} + +impl ZcashSerialize for OutCiphertext { + fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { + writer.write_all(&self.0[..])?; + Ok(()) + } +} + +impl ZcashDeserialize for OutCiphertext { + fn zcash_deserialize(mut reader: R) -> Result { + let mut bytes = [0; 80]; + reader.read_exact(&mut bytes[..])?; + Ok(Self(bytes)) + } +} + +#[cfg(test)] +proptest! { + + #[test] + fn encrypted_ciphertext_roundtrip(ec in any::()) { + + let mut data = Vec::new(); + + ec.zcash_serialize(&mut data).expect("EncryptedCiphertext should serialize"); + + let ec2 = EncryptedCiphertext::zcash_deserialize(&data[..]).expect("randomized EncryptedCiphertext should deserialize"); + + prop_assert_eq![ec, ec2]; + } + + #[test] + fn out_ciphertext_roundtrip(oc in any::()) { + + let mut data = Vec::new(); + + oc.zcash_serialize(&mut data).expect("OutCiphertext should serialize"); + + let oc2 = OutCiphertext::zcash_deserialize(&data[..]).expect("randomized OutCiphertext should deserialize"); + + prop_assert_eq![oc, oc2]; + } +} diff --git a/zebra-chain/src/notes/sapling/commitments.rs b/zebra-chain/src/notes/sapling/commitments.rs new file mode 100644 index 00000000..57d75b67 --- /dev/null +++ b/zebra-chain/src/notes/sapling/commitments.rs @@ -0,0 +1,114 @@ +use std::{fmt, io}; + +use crate::{ + serde_helpers, + serialization::{ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize}, +}; + +/// The randomness used in the Pedersen Hash for note commitment. +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct CommitmentRandomness(redjubjub::Randomizer); + +/// Note commitments for the output notes. +#[derive(Clone, Copy, Deserialize, PartialEq, Serialize)] +//#[cfg_attr(test, derive(proptest_derive::Arbitrary))] +pub struct NoteCommitment(#[serde(with = "serde_helpers::AffinePoint")] pub jubjub::AffinePoint); + +impl fmt::Debug for NoteCommitment { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("NoteCommitment") + .field("u", &hex::encode(self.0.get_u().to_bytes())) + .field("v", &hex::encode(self.0.get_v().to_bytes())) + .finish() + } +} + +impl From<[u8; 32]> for NoteCommitment { + fn from(bytes: [u8; 32]) -> Self { + Self(jubjub::AffinePoint::from_bytes(bytes).unwrap()) + } +} + +impl Eq for NoteCommitment {} + +impl From for [u8; 32] { + fn from(cm: NoteCommitment) -> [u8; 32] { + cm.0.to_bytes() + } +} + +impl ZcashSerialize for NoteCommitment { + // The u-coordinate of the note commitment, for the output note + // LEBS2OSP256(cm_u) where cm_u = Extract_J(r)(cm). ??? + fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { + writer.write_all(&self.0.to_bytes())?; + Ok(()) + } +} + +impl ZcashDeserialize for NoteCommitment { + fn zcash_deserialize(mut reader: R) -> Result { + Ok(Self( + jubjub::AffinePoint::from_bytes(reader.read_32_bytes()?).unwrap(), + )) + } +} + +impl NoteCommitment { + /// Hash Extractor for Jubjub (?) + /// + /// https://zips.z.cash/protocol/protocol.pdf#concreteextractorjubjub + pub fn extract_u(self) -> jubjub::Fq { + self.0.get_u() + } +} + +/// A Homomorphic Pedersen commitment to the value of a note, used in +/// Spend and Output Descriptions. +/// +/// https://zips.z.cash/protocol/protocol.pdf#concretehomomorphiccommit +#[derive(Clone, Deserialize, PartialEq, Serialize)] +//#[cfg_attr(test, derive(proptest_derive::Arbitrary))] +pub struct ValueCommitment(#[serde(with = "serde_helpers::AffinePoint")] pub jubjub::AffinePoint); + +impl fmt::Debug for ValueCommitment { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("ValueCommitment") + .field("u", &hex::encode(self.0.get_u().to_bytes())) + .field("v", &hex::encode(self.0.get_v().to_bytes())) + .finish() + } +} + +impl From<[u8; 32]> for ValueCommitment { + fn from(bytes: [u8; 32]) -> Self { + Self(jubjub::AffinePoint::from_bytes(bytes).unwrap()) + } +} + +impl Eq for ValueCommitment {} + +impl From for [u8; 32] { + fn from(cm: ValueCommitment) -> [u8; 32] { + cm.0.to_bytes() + } +} + +/// LEBS2OSP256(repr_J(cv)) +/// +/// https://zips.z.cash/protocol/protocol.pdf#spendencoding +/// https://zips.z.cash/protocol/protocol.pdf#jubjub +impl ZcashSerialize for ValueCommitment { + fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { + writer.write_all(&self.0.to_bytes())?; + Ok(()) + } +} + +impl ZcashDeserialize for ValueCommitment { + fn zcash_deserialize(mut reader: R) -> Result { + Ok(Self( + jubjub::AffinePoint::from_bytes(reader.read_32_bytes()?).unwrap(), + )) + } +} diff --git a/zebra-chain/src/notes/sapling/nullifiers.rs b/zebra-chain/src/notes/sapling/nullifiers.rs new file mode 100644 index 00000000..0e466c03 --- /dev/null +++ b/zebra-chain/src/notes/sapling/nullifiers.rs @@ -0,0 +1,31 @@ +#![allow(clippy::unit_arg)] +#![allow(dead_code)] + +use std::io; + +use crate::serialization::{ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize}; + +/// A Nullifier for Sapling transactions +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(test, derive(proptest_derive::Arbitrary))] +pub struct Nullifier([u8; 32]); + +impl From<[u8; 32]> for Nullifier { + fn from(buf: [u8; 32]) -> Self { + Self(buf) + } +} + +impl ZcashDeserialize for Nullifier { + fn zcash_deserialize(mut reader: R) -> Result { + let bytes = reader.read_32_bytes()?; + + Ok(Self(bytes)) + } +} + +impl ZcashSerialize for Nullifier { + fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { + writer.write_all(&self.0[..]) + } +} diff --git a/zebra-chain/src/notes/sprout.rs b/zebra-chain/src/notes/sprout.rs index 71bc46bb..bf45dfc4 100644 --- a/zebra-chain/src/notes/sprout.rs +++ b/zebra-chain/src/notes/sprout.rs @@ -1,113 +1,23 @@ -//! #![allow(clippy::unit_arg)] #![allow(dead_code)] -use std::{fmt, io}; +#[cfg(test)] +mod arbitrary; +mod ciphertexts; +mod commitments; +mod nullifiers; -use byteorder::{ByteOrder, LittleEndian}; -use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use crate::{ - keys::sprout::{PayingKey, SpendingKey}, - serde_helpers, - serialization::{ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize}, + keys::sprout::PayingKey, + notes::memo::Memo, types::amount::{Amount, NonNegative}, }; -use super::memo::Memo; - -/// PRF^nf is used to derive a Sprout nullifer from the receiver's -/// spending key a_sk and a nullifier seed ρ, instantiated using the -/// SHA-256 compression function. -/// -/// https://zips.z.cash/protocol/protocol.pdf#abstractprfs -/// https://zips.z.cash/protocol/protocol.pdf#commitmentsandnullifiers -fn prf_nf(a_sk: [u8; 32], rho: [u8; 32]) -> [u8; 32] { - let mut state = [0u32; 8]; - let mut block = [0u8; 64]; - - block[0..32].copy_from_slice(&a_sk[..]); - // The first four bits –i.e. the most signicant four bits of the - // first byte– are used to separate distinct uses - // ofSHA256Compress, ensuring that the functions are independent. - block[0] |= 0b1110_0000; - - block[32..].copy_from_slice(&rho[..]); - - sha2::compress256(&mut state, &block); - - let mut derived_bytes = [0u8; 32]; - LittleEndian::write_u32_into(&state, &mut derived_bytes); - - derived_bytes -} - -/// Nullifier seed, named rho in the [spec][ps]. -/// -/// [ps]: https://zips.z.cash/protocol/protocol.pdf#sproutkeycomponents -#[derive(Clone, Copy, Debug)] -#[cfg_attr(test, derive(proptest_derive::Arbitrary))] -pub struct NullifierSeed([u8; 32]); - -impl AsRef<[u8]> for NullifierSeed { - fn as_ref(&self) -> &[u8] { - &self.0 - } -} - -impl From<[u8; 32]> for NullifierSeed { - fn from(bytes: [u8; 32]) -> Self { - Self(bytes) - } -} - -/// A Nullifier for Sprout transactions -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[cfg_attr(test, derive(proptest_derive::Arbitrary))] -pub struct Nullifier([u8; 32]); - -impl From for [u8; 32] { - fn from(rho: NullifierSeed) -> Self { - rho.0 - } -} - -impl From<(SpendingKey, NullifierSeed)> for Nullifier { - fn from((a_sk, rho): (SpendingKey, NullifierSeed)) -> Self { - Self(prf_nf(a_sk.into(), rho.into())) - } -} - -impl ZcashDeserialize for Nullifier { - fn zcash_deserialize(mut reader: R) -> Result { - let bytes = reader.read_32_bytes()?; - - Ok(Self(bytes)) - } -} - -impl ZcashSerialize for Nullifier { - fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { - writer.write_all(&self.0[..]) - } -} - -/// The randomness used in the Pedersen Hash for note commitment. -#[derive(Copy, Clone, Debug, PartialEq)] -#[cfg_attr(test, derive(proptest_derive::Arbitrary))] -pub struct CommitmentRandomness(pub [u8; 32]); - -impl AsRef<[u8]> for CommitmentRandomness { - fn as_ref(&self) -> &[u8] { - &self.0 - } -} - -/// -#[derive(Clone, Copy, Debug)] -#[cfg_attr(test, derive(proptest_derive::Arbitrary))] -pub struct NoteCommitment([u8; 32]); +pub use ciphertexts::EncryptedCiphertext; +pub use commitments::{CommitmentRandomness, NoteCommitment}; +pub use nullifiers::{Nullifier, NullifierSeed}; /// A Note represents that a value is spendable by the recipient who /// holds the spending key corresponding to a given shielded payment @@ -137,7 +47,6 @@ impl Note { hasher.input(self.value.to_bytes()); hasher.input(self.rho); hasher.input(self.rcm); - NoteCommitment(hasher.result().into()) } } @@ -151,86 +60,3 @@ pub struct NotePlaintext { rcm: CommitmentRandomness, memo: Memo, } - -/// A ciphertext component for encrypted output notes. -#[derive(Serialize, Deserialize)] -pub struct EncryptedCiphertext(#[serde(with = "serde_helpers::BigArray")] pub [u8; 601]); - -impl fmt::Debug for EncryptedCiphertext { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_tuple("EncryptedCiphertext") - .field(&hex::encode(&self.0[..])) - .finish() - } -} - -// These impls all only exist because of array length restrictions. - -impl Copy for EncryptedCiphertext {} - -impl Clone for EncryptedCiphertext { - fn clone(&self) -> Self { - let mut bytes = [0; 601]; - bytes[..].copy_from_slice(&self.0[..]); - Self(bytes) - } -} - -impl PartialEq for EncryptedCiphertext { - fn eq(&self, other: &Self) -> bool { - self.0[..] == other.0[..] - } -} - -impl Eq for EncryptedCiphertext {} - -impl ZcashSerialize for EncryptedCiphertext { - fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { - writer.write_all(&self.0[..])?; - Ok(()) - } -} - -impl ZcashDeserialize for EncryptedCiphertext { - fn zcash_deserialize(mut reader: R) -> Result { - let mut bytes = [0; 601]; - reader.read_exact(&mut bytes[..])?; - Ok(Self(bytes)) - } -} - -#[cfg(test)] -use proptest::{collection::vec, prelude::*}; - -#[cfg(test)] -impl Arbitrary for EncryptedCiphertext { - type Parameters = (); - - fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { - (vec(any::(), 601)) - .prop_map(|v| { - let mut bytes = [0; 601]; - bytes.copy_from_slice(v.as_slice()); - Self(bytes) - }) - .boxed() - } - - type Strategy = BoxedStrategy; -} - -#[cfg(test)] -proptest! { - - #[test] - fn encrypted_ciphertext_roundtrip(ec in any::()) { - - let mut data = Vec::new(); - - ec.zcash_serialize(&mut data).expect("EncryptedCiphertext should serialize"); - - let ec2 = EncryptedCiphertext::zcash_deserialize(&data[..]).expect("randomized EncryptedCiphertext should deserialize"); - - prop_assert_eq![ec, ec2]; - } -} diff --git a/zebra-chain/src/notes/sprout/arbitrary.rs b/zebra-chain/src/notes/sprout/arbitrary.rs new file mode 100644 index 00000000..68470d87 --- /dev/null +++ b/zebra-chain/src/notes/sprout/arbitrary.rs @@ -0,0 +1,35 @@ +use proptest::{arbitrary::any, collection::vec, prelude::*}; + +use crate::notes::{memo::Memo, sprout}; + +impl Arbitrary for Memo { + type Parameters = (); + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + (vec(any::(), 512)) + .prop_map(|v| { + let mut bytes = [0; 512]; + bytes.copy_from_slice(v.as_slice()); + Memo(Box::new(bytes)) + }) + .boxed() + } + + type Strategy = BoxedStrategy; +} + +impl Arbitrary for sprout::EncryptedCiphertext { + type Parameters = (); + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + (vec(any::(), 601)) + .prop_map(|v| { + let mut bytes = [0; 601]; + bytes.copy_from_slice(v.as_slice()); + Self(bytes) + }) + .boxed() + } + + type Strategy = BoxedStrategy; +} diff --git a/zebra-chain/src/notes/sprout/ciphertexts.rs b/zebra-chain/src/notes/sprout/ciphertexts.rs new file mode 100644 index 00000000..f99a2cd7 --- /dev/null +++ b/zebra-chain/src/notes/sprout/ciphertexts.rs @@ -0,0 +1,74 @@ +use std::{fmt, io}; + +#[cfg(test)] +use proptest::{arbitrary::any, prelude::*}; + +use serde::{Deserialize, Serialize}; + +use crate::{ + serde_helpers, + serialization::{SerializationError, ZcashDeserialize, ZcashSerialize}, +}; + +/// A ciphertext component for encrypted output notes. +#[derive(Serialize, Deserialize)] +pub struct EncryptedCiphertext(#[serde(with = "serde_helpers::BigArray")] pub [u8; 601]); + +impl fmt::Debug for EncryptedCiphertext { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("EncryptedCiphertext") + .field(&hex::encode(&self.0[..])) + .finish() + } +} + +// These impls all only exist because of array length restrictions. + +impl Copy for EncryptedCiphertext {} + +impl Clone for EncryptedCiphertext { + fn clone(&self) -> Self { + let mut bytes = [0; 601]; + bytes[..].copy_from_slice(&self.0[..]); + Self(bytes) + } +} + +impl PartialEq for EncryptedCiphertext { + fn eq(&self, other: &Self) -> bool { + self.0[..] == other.0[..] + } +} + +impl Eq for EncryptedCiphertext {} + +impl ZcashSerialize for EncryptedCiphertext { + fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { + writer.write_all(&self.0[..])?; + Ok(()) + } +} + +impl ZcashDeserialize for EncryptedCiphertext { + fn zcash_deserialize(mut reader: R) -> Result { + let mut bytes = [0; 601]; + reader.read_exact(&mut bytes[..])?; + Ok(Self(bytes)) + } +} + +#[cfg(test)] +proptest! { + + #[test] + fn encrypted_ciphertext_roundtrip(ec in any::()) { + + let mut data = Vec::new(); + + ec.zcash_serialize(&mut data).expect("EncryptedCiphertext should serialize"); + + let ec2 = EncryptedCiphertext::zcash_deserialize(&data[..]).expect("randomized EncryptedCiphertext should deserialize"); + + prop_assert_eq![ec, ec2]; + } +} diff --git a/zebra-chain/src/notes/sprout/commitments.rs b/zebra-chain/src/notes/sprout/commitments.rs new file mode 100644 index 00000000..931d9c85 --- /dev/null +++ b/zebra-chain/src/notes/sprout/commitments.rs @@ -0,0 +1,32 @@ +use std::io; + +use crate::serialization::{ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize}; + +/// The randomness used in the Pedersen Hash for note commitment. +#[derive(Copy, Clone, Debug, PartialEq)] +#[cfg_attr(test, derive(proptest_derive::Arbitrary))] +pub struct CommitmentRandomness(pub [u8; 32]); + +impl AsRef<[u8]> for CommitmentRandomness { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +/// Note commitments for the output notes. +#[derive(Clone, Copy, Debug)] +#[cfg_attr(test, derive(proptest_derive::Arbitrary))] +pub struct NoteCommitment(pub(crate) [u8; 32]); + +impl ZcashSerialize for NoteCommitment { + fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { + writer.write_all(&self.0[..])?; + Ok(()) + } +} + +impl ZcashDeserialize for NoteCommitment { + fn zcash_deserialize(mut reader: R) -> Result { + Ok(Self(reader.read_32_bytes()?)) + } +} diff --git a/zebra-chain/src/notes/sprout/nullifiers.rs b/zebra-chain/src/notes/sprout/nullifiers.rs new file mode 100644 index 00000000..18f30b9a --- /dev/null +++ b/zebra-chain/src/notes/sprout/nullifiers.rs @@ -0,0 +1,89 @@ +//! +#![allow(clippy::unit_arg)] +#![allow(dead_code)] + +use std::io; + +use byteorder::{ByteOrder, LittleEndian}; +use serde::{Deserialize, Serialize}; + +use crate::{ + keys::sprout::SpendingKey, + serialization::{ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize}, +}; + +/// PRF^nf is used to derive a Sprout nullifer from the receiver's +/// spending key a_sk and a nullifier seed ρ, instantiated using the +/// SHA-256 compression function. +/// +/// https://zips.z.cash/protocol/protocol.pdf#abstractprfs +/// https://zips.z.cash/protocol/protocol.pdf#commitmentsandnullifiers +fn prf_nf(a_sk: [u8; 32], rho: [u8; 32]) -> [u8; 32] { + let mut state = [0u32; 8]; + let mut block = [0u8; 64]; + + block[0..32].copy_from_slice(&a_sk[..]); + // The first four bits –i.e. the most signicant four bits of the + // first byte– are used to separate distinct uses + // ofSHA256Compress, ensuring that the functions are independent. + block[0] |= 0b1110_0000; + + block[32..].copy_from_slice(&rho[..]); + + sha2::compress256(&mut state, &block); + + let mut derived_bytes = [0u8; 32]; + LittleEndian::write_u32_into(&state, &mut derived_bytes); + + derived_bytes +} + +/// Nullifier seed, named rho in the [spec][ps]. +/// +/// [ps]: https://zips.z.cash/protocol/protocol.pdf#sproutkeycomponents +#[derive(Clone, Copy, Debug)] +#[cfg_attr(test, derive(proptest_derive::Arbitrary))] +pub struct NullifierSeed([u8; 32]); + +impl AsRef<[u8]> for NullifierSeed { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl From<[u8; 32]> for NullifierSeed { + fn from(bytes: [u8; 32]) -> Self { + Self(bytes) + } +} + +/// A Nullifier for Sprout transactions +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(test, derive(proptest_derive::Arbitrary))] +pub struct Nullifier([u8; 32]); + +impl From for [u8; 32] { + fn from(rho: NullifierSeed) -> Self { + rho.0 + } +} + +impl From<(SpendingKey, NullifierSeed)> for Nullifier { + fn from((a_sk, rho): (SpendingKey, NullifierSeed)) -> Self { + Self(prf_nf(a_sk.into(), rho.into())) + } +} + +impl ZcashDeserialize for Nullifier { + fn zcash_deserialize(mut reader: R) -> Result { + let bytes = reader.read_32_bytes()?; + + Ok(Self(bytes)) + } +} + +impl ZcashSerialize for Nullifier { + fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { + writer.write_all(&self.0[..]) + } +} diff --git a/zebra-chain/src/notes/tests.rs b/zebra-chain/src/notes/tests.rs deleted file mode 100644 index be9770e9..00000000 --- a/zebra-chain/src/notes/tests.rs +++ /dev/null @@ -1 +0,0 @@ -mod arbitrary;