diff --git a/zebra-chain/src/transaction/joinsplit.rs b/zebra-chain/src/transaction/joinsplit.rs index 2ac7ea932..a2b38995f 100644 --- a/zebra-chain/src/transaction/joinsplit.rs +++ b/zebra-chain/src/transaction/joinsplit.rs @@ -1,9 +1,23 @@ -use crate::proofs::ZkSnarkProof; +use std::{ + fmt, + io::{self}, +}; + +#[cfg(test)] +use proptest::{collection::vec, prelude::*}; +#[cfg(test)] +use proptest_derive::Arbitrary; + +use crate::{ + proofs::ZkSnarkProof, + serialization::{SerializationError, ZcashDeserialize, ZcashSerialize}, +}; /// A _JoinSplit Description_, as described in [protocol specification ยง7.2][ps]. /// /// [ps]: https://zips.z.cash/protocol/protocol.pdf#joinsplitencoding #[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(test, derive(Arbitrary))] pub struct JoinSplit { /// A value that the JoinSplit transfer removes from the transparent value /// pool. @@ -45,14 +59,12 @@ pub struct JoinSplit { /// [`Bctv14Proof`](crate::proofs::Bctv14Proof). pub zkproof: P, /// A ciphertext component for this output note. - /// - /// XXX refine type to [T; 2] -- there are two ctxts - /// XXX this should be a [[u8; 601]; 2] but we need trait impls. - pub enc_ciphertexts: [Vec; 2], + pub enc_ciphertexts: [EncryptedCiphertext; 2], } /// A bundle of JoinSplit descriptions and signature data. #[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(test, derive(Arbitrary))] pub struct JoinSplitData { /// The first JoinSplit description, using proofs of type `P`. /// @@ -80,3 +92,66 @@ impl JoinSplitData

{ std::iter::once(&self.first).chain(self.rest.iter()) } } + +/// A ciphertext component for encrypted output notes. +pub struct EncryptedCiphertext(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<(), SerializationError> { + 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)] +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()); + return Self(bytes); + }) + .boxed() + } + + type Strategy = BoxedStrategy; +}