use std::{fmt, io};
use serde::{Deserialize, Serialize};
use crate::{
amount::{Amount, NegativeAllowed, NonNegative},
block::MAX_BLOCK_BYTES,
fmt::HexDebug,
primitives::{x25519, Bctv14Proof, Groth16Proof, ZkSnarkProof},
serialization::{
ReadZcashExt, SerializationError, TrustedPreallocate, WriteZcashExt, ZcashDeserialize,
ZcashDeserializeInto, ZcashSerialize,
},
};
use super::{commitment, note, tree};
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[cfg_attr(
any(test, feature = "proptest-impl"),
derive(proptest_derive::Arbitrary)
)]
pub struct RandomSeed(HexDebug<[u8; 32]>);
impl RandomSeed {
pub fn bytes_in_display_order(&self) -> [u8; 32] {
let mut root: [u8; 32] = self.into();
root.reverse();
root
}
}
impl From<[u8; 32]> for RandomSeed {
fn from(bytes: [u8; 32]) -> Self {
Self(bytes.into())
}
}
impl From<RandomSeed> for [u8; 32] {
fn from(rt: RandomSeed) -> [u8; 32] {
*rt.0
}
}
impl From<&RandomSeed> for [u8; 32] {
fn from(random_seed: &RandomSeed) -> Self {
random_seed.clone().into()
}
}
#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct JoinSplit<P: ZkSnarkProof> {
pub vpub_old: Amount<NonNegative>,
pub vpub_new: Amount<NonNegative>,
pub anchor: tree::Root,
pub nullifiers: [note::Nullifier; 2],
pub commitments: [commitment::NoteCommitment; 2],
pub ephemeral_key: x25519::PublicKey,
pub random_seed: RandomSeed,
pub vmacs: [note::Mac; 2],
#[serde(bound(serialize = "P: ZkSnarkProof", deserialize = "P: ZkSnarkProof"))]
pub zkproof: P,
pub enc_ciphertexts: [note::EncryptedNote; 2],
}
#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct GenericJoinSplit {
pub vpub_old: Amount<NonNegative>,
pub vpub_new: Amount<NonNegative>,
pub anchor: tree::Root,
pub nullifiers: [note::Nullifier; 2],
pub commitments: [commitment::NoteCommitment; 2],
pub ephemeral_key: x25519::PublicKey,
pub random_seed: RandomSeed,
pub vmacs: [note::Mac; 2],
pub zkproof: Option<Vec<u8>>,
pub enc_ciphertexts: [note::EncryptedNote; 2],
}
impl<P: ZkSnarkProof> fmt::Debug for JoinSplit<P> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("JoinSplit")
.field("vpub_old", &self.vpub_old)
.field("vpub_new", &self.vpub_new)
.field("anchor", &self.anchor)
.field("nullifiers", &self.nullifiers)
.field("commitments", &self.commitments)
.field("ephemeral_key", &HexDebug(self.ephemeral_key.as_bytes()))
.field("random_seed", &self.random_seed)
.field("vmacs", &self.vmacs)
.field("zkproof", &self.zkproof)
.field("enc_ciphertexts", &self.enc_ciphertexts)
.finish()
}
}
impl<P: ZkSnarkProof> ZcashSerialize for JoinSplit<P> {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
self.vpub_old.zcash_serialize(&mut writer)?;
self.vpub_new.zcash_serialize(&mut writer)?;
writer.write_32_bytes(&self.anchor.into())?;
writer.write_32_bytes(&self.nullifiers[0].into())?;
writer.write_32_bytes(&self.nullifiers[1].into())?;
writer.write_32_bytes(&self.commitments[0].into())?;
writer.write_32_bytes(&self.commitments[1].into())?;
writer.write_all(&self.ephemeral_key.as_bytes()[..])?;
#[allow(clippy::needless_borrow)]
writer.write_32_bytes(&(&self.random_seed).into())?;
self.vmacs[0].zcash_serialize(&mut writer)?;
self.vmacs[1].zcash_serialize(&mut writer)?;
self.zkproof.zcash_serialize(&mut writer)?;
self.enc_ciphertexts[0].zcash_serialize(&mut writer)?;
self.enc_ciphertexts[1].zcash_serialize(&mut writer)?;
Ok(())
}
}
impl<P: ZkSnarkProof> JoinSplit<P> {
pub fn value_balance(&self) -> Amount<NegativeAllowed> {
let vpub_new = self
.vpub_new
.constrain()
.expect("constrain::NegativeAllowed is always valid");
let vpub_old = self
.vpub_old
.constrain()
.expect("constrain::NegativeAllowed is always valid");
(vpub_new - vpub_old).expect("subtraction of two valid amounts is a valid NegativeAllowed")
}
}
impl<P: ZkSnarkProof> ZcashDeserialize for JoinSplit<P> {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
Ok(JoinSplit::<P> {
vpub_old: (&mut reader).zcash_deserialize_into()?,
vpub_new: (&mut reader).zcash_deserialize_into()?,
anchor: tree::Root::from(reader.read_32_bytes()?),
nullifiers: [
reader.read_32_bytes()?.into(),
reader.read_32_bytes()?.into(),
],
commitments: [
commitment::NoteCommitment::from(reader.read_32_bytes()?),
commitment::NoteCommitment::from(reader.read_32_bytes()?),
],
ephemeral_key: x25519_dalek::PublicKey::from(reader.read_32_bytes()?),
random_seed: RandomSeed::from(reader.read_32_bytes()?),
vmacs: [
note::Mac::zcash_deserialize(&mut reader)?,
note::Mac::zcash_deserialize(&mut reader)?,
],
zkproof: P::zcash_deserialize(&mut reader)?,
enc_ciphertexts: [
note::EncryptedNote::zcash_deserialize(&mut reader)?,
note::EncryptedNote::zcash_deserialize(&mut reader)?,
],
})
}
}
const JOINSPLIT_SIZE_WITHOUT_ZKPROOF: u64 =
8 + 8 + 32 + (32 * 2) + (32 * 2) + 32 + 32 + (32 * 2) + (601 * 2);
pub(crate) const BCTV14_JOINSPLIT_SIZE: u64 = JOINSPLIT_SIZE_WITHOUT_ZKPROOF + 296;
pub(crate) const GROTH16_JOINSPLIT_SIZE: u64 = JOINSPLIT_SIZE_WITHOUT_ZKPROOF + 192;
impl TrustedPreallocate for JoinSplit<Bctv14Proof> {
fn max_allocation() -> u64 {
(MAX_BLOCK_BYTES - 1) / BCTV14_JOINSPLIT_SIZE
}
}
impl TrustedPreallocate for JoinSplit<Groth16Proof> {
fn max_allocation() -> u64 {
(MAX_BLOCK_BYTES - 1) / GROTH16_JOINSPLIT_SIZE
}
}
impl<P> From<JoinSplit<P>> for GenericJoinSplit
where
P: ZkSnarkProof,
{
fn from(val: JoinSplit<P>) -> Self {
GenericJoinSplit {
vpub_old: val.vpub_old,
vpub_new: val.vpub_new,
anchor: val.anchor,
nullifiers: val.nullifiers,
commitments: val.commitments,
ephemeral_key: val.ephemeral_key,
random_seed: val.random_seed,
vmacs: val.vmacs,
zkproof: val.zkproof.zcash_serialize_to_vec().ok(),
enc_ciphertexts: val.enc_ciphertexts,
}
}
}