use std::{fmt, io}; use futures::future::Either; #[cfg(test)] use proptest::{arbitrary::Arbitrary, array, collection::vec, prelude::*}; #[cfg(test)] use proptest_derive::Arbitrary; // XXX this name seems too long? use crate::note_commitment_tree::SaplingNoteTreeRootHash; use crate::proofs::Groth16Proof; use crate::redjubjub::{self, Binding, SpendAuth}; use crate::serialization::{SerializationError, ZcashDeserialize, ZcashSerialize}; /// 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)] pub struct SpendDescription { /// A value commitment to the value of the input note. /// /// XXX refine to a specific type. pub cv: [u8; 32], /// A root of the Sapling note commitment tree at some block height in the past. pub anchor: SaplingNoteTreeRootHash, /// The nullifier of the input note. /// /// XXX refine to a specific type. pub nullifier: [u8; 32], /// The randomized public key for `spend_auth_sig`. pub rk: redjubjub::PublicKeyBytes, /// The ZK spend proof. pub zkproof: Groth16Proof, /// A signature authorizing this spend. pub spend_auth_sig: redjubjub::Signature, } #[cfg(test)] impl Arbitrary for SpendDescription { type Parameters = (); fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { ( array::uniform32(any::()), any::(), array::uniform32(any::()), array::uniform32(any::()), any::(), vec(any::(), 64), ) .prop_map( |(cv_bytes, anchor, nullifier_bytes, rpk_bytes, proof, sig_bytes)| { return Self { anchor, cv: cv_bytes, nullifier: nullifier_bytes, rk: redjubjub::PublicKeyBytes::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; } /// 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)] #[cfg_attr(test, derive(Arbitrary))] pub struct OutputDescription { /// A value commitment to the value of the input note. /// /// XXX refine to a specific type. pub cv: [u8; 32], /// The u-coordinate of the note commitment for the output note. /// /// XXX refine to a specific type. pub cmu: [u8; 32], /// An encoding of an ephemeral Jubjub public key. /// /// XXX refine to a Jubjub key agreement type, not RedJubjub. pub ephemeral_key: [u8; 32], /// A ciphertext component for the encrypted output note. pub enc_ciphertext: EncryptedCiphertext, /// A ciphertext component for the encrypted output note. pub out_ciphertext: OutCiphertext, /// The ZK output proof. pub zkproof: Groth16Proof, } /// Sapling-on-Groth16 spend and output descriptions. #[derive(Clone, Debug)] pub struct ShieldedData { /// Either a spend or output description. /// /// Storing this separately ensures that it is impossible to construct /// an invalid `ShieldedData` with no spends or outputs. /// /// However, it's not necessary to access or process `first` and `rest` /// separately, as the [`ShieldedData::spends`] and [`ShieldedData::outputs`] /// methods provide iterators over all of the [`SpendDescription`]s and /// [`OutputDescription`]s. pub first: Either, /// The rest of the [`SpendDescription`]s for this transaction. /// /// Note that the [`ShieldedData::spends`] method provides an iterator /// over all spend descriptions. pub rest_spends: Vec, /// The rest of the [`OutputDescription`]s for this transaction. /// /// Note that the [`ShieldedData::outputs`] method provides an iterator /// over all output descriptions. pub rest_outputs: Vec, /// A signature on the transaction hash. pub binding_sig: redjubjub::Signature, } impl ShieldedData { /// Iterate over the [`SpendDescription`]s for this transaction. pub fn spends(&self) -> impl Iterator { match self.first { Either::Left(ref spend) => Some(spend), Either::Right(_) => None, } .into_iter() .chain(self.rest_spends.iter()) } /// Iterate over the [`OutputDescription`]s for this transaction. pub fn outputs(&self) -> impl Iterator { match self.first { Either::Left(_) => None, Either::Right(ref output) => Some(output), } .into_iter() .chain(self.rest_outputs.iter()) } } // Technically, it's possible to construct two equivalent representations // of a ShieldedData with at least one spend and at least one output, depending // on which goes in the `first` slot. This is annoying but a smallish price to // pay for structural validity. impl std::cmp::PartialEq for ShieldedData { fn eq(&self, other: &Self) -> bool { // First check that the lengths match, so we know it is safe to use zip, // which truncates to the shorter of the two iterators. if self.spends().count() != other.spends().count() { return false; } if self.outputs().count() != other.outputs().count() { return false; } // Now check that the binding_sig, spends, outputs match. self.binding_sig == other.binding_sig && self.spends().zip(other.spends()).all(|(a, b)| a == b) && self.outputs().zip(other.outputs()).all(|(a, b)| a == b) } } impl std::cmp::Eq for ShieldedData {} #[cfg(test)] 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) ], vec(any::(), 0..10), vec(any::(), 0..10), vec(any::(), 64), ) .prop_map(|(first, rest_spends, rest_outputs, sig_bytes)| { return Self { first, rest_spends, rest_outputs, binding_sig: redjubjub::Signature::from({ let mut b = [0u8; 64]; b.copy_from_slice(sig_bytes.as_slice()); b }), }; }) .boxed() } type Strategy = BoxedStrategy; } /// A ciphertext component for encrypted output notes. // XXX move as part of #181 (note encryption implementation) pub struct EncryptedCiphertext(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()); return Self(bytes); }) .boxed() } type Strategy = BoxedStrategy; } /// A ciphertext component for encrypted output notes. pub struct OutCiphertext(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()); return 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]; } #[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]; } }