use std::{borrow::Borrow, io, sync::Arc};
use halo2::pasta::{group::ff::PrimeField, pallas};
use hex::FromHex;
use crate::{
block::MAX_BLOCK_BYTES,
primitives::ZkSnarkProof,
serialization::{
ReadZcashExt, SerializationError, TrustedPreallocate, ZcashDeserialize,
ZcashDeserializeInto, ZcashSerialize,
},
};
use super::*;
use crate::sprout;
#[cfg(any(test, feature = "proptest-impl"))]
use crate::{
amount, ironwood, orchard,
primitives::Halo2Proof,
sapling,
serialization::{
zcash_deserialize_external_count, zcash_serialize_empty_list,
zcash_serialize_external_count, AtLeastOne, CompactSizeMessage,
},
};
#[cfg(any(test, feature = "proptest-impl"))]
use reddsa::{orchard::Binding, orchard::SpendAuth, Signature};
impl ZcashDeserialize for jubjub::Fq {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
let possible_scalar = jubjub::Fq::from_bytes(&reader.read_32_bytes()?);
if possible_scalar.is_some().into() {
Ok(possible_scalar.unwrap())
} else {
Err(SerializationError::Parse(
"Invalid jubjub::Fq, input not canonical",
))
}
}
}
impl ZcashDeserialize for pallas::Scalar {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
let possible_scalar = pallas::Scalar::from_repr(reader.read_32_bytes()?);
if possible_scalar.is_some().into() {
Ok(possible_scalar.unwrap())
} else {
Err(SerializationError::Parse(
"Invalid pallas::Scalar, input not canonical",
))
}
}
}
impl ZcashDeserialize for pallas::Base {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
let possible_field_element = pallas::Base::from_repr(reader.read_32_bytes()?);
if possible_field_element.is_some().into() {
Ok(possible_field_element.unwrap())
} else {
Err(SerializationError::Parse(
"Invalid pallas::Base, input not canonical",
))
}
}
}
impl<P: ZkSnarkProof> ZcashSerialize for JoinSplitData<P> {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
let joinsplits: Vec<_> = self.joinsplits().cloned().collect();
joinsplits.zcash_serialize(&mut writer)?;
writer.write_all(&<[u8; 32]>::from(self.pub_key)[..])?;
writer.write_all(&<[u8; 64]>::from(self.sig)[..])?;
Ok(())
}
}
impl<P> ZcashDeserialize for Option<JoinSplitData<P>>
where
P: ZkSnarkProof,
sprout::JoinSplit<P>: TrustedPreallocate,
{
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
let joinsplits: Vec<sprout::JoinSplit<P>> = (&mut reader).zcash_deserialize_into()?;
match joinsplits.split_first() {
None => Ok(None),
Some((first, rest)) => {
let pub_key = reader.read_32_bytes()?.into();
let sig = reader.read_64_bytes()?.into();
Ok(Some(JoinSplitData {
first: first.clone(),
rest: rest.to_vec(),
pub_key,
sig,
}))
}
}
}
}
#[cfg(any(test, feature = "proptest-impl"))]
impl ZcashSerialize for Option<sapling::ShieldedData<sapling::PerSpendAnchor>> {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
match self {
None => {
zcash_serialize_empty_list(&mut writer)?;
zcash_serialize_empty_list(&mut writer)?;
}
Some(sd) => {
sd.zcash_serialize(&mut writer)?;
}
}
Ok(())
}
}
#[cfg(any(test, feature = "proptest-impl"))]
impl ZcashSerialize for sapling::ShieldedData<sapling::PerSpendAnchor> {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
let spends: Vec<_> = self.spends().cloned().collect();
let outputs: Vec<_> = self
.outputs()
.cloned()
.map(sapling::Output::into_v4)
.collect();
spends.zcash_serialize(&mut writer)?;
outputs.zcash_serialize(&mut writer)?;
self.value_balance.zcash_serialize(&mut writer)?;
writer.write_all(&<[u8; 64]>::from(self.binding_sig)[..])?;
Ok(())
}
}
#[cfg(any(test, feature = "proptest-impl"))]
impl ZcashDeserialize for Option<sapling::ShieldedData<sapling::PerSpendAnchor>> {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
let spends: Vec<sapling::Spend<sapling::PerSpendAnchor>> =
(&mut reader).zcash_deserialize_into()?;
let outputs: Vec<sapling::OutputInTransactionV4> =
(&mut reader).zcash_deserialize_into()?;
if spends.is_empty() && outputs.is_empty() {
return Ok(None);
}
let value_balance = (&mut reader).zcash_deserialize_into()?;
let binding_sig = reader.read_64_bytes()?.into();
let outputs: Vec<sapling::Output> = outputs
.into_iter()
.map(sapling::OutputInTransactionV4::into_output)
.collect();
let transfers = match spends.split_first() {
None => sapling::TransferData::JustOutputs {
outputs: outputs.try_into().map_err(|_| {
SerializationError::Parse(
"ShieldedData<PerSpendAnchor> with no spends or outputs",
)
})?,
},
Some((first, rest)) => sapling::TransferData::SpendsAndMaybeOutputs {
shared_anchor: sapling::FieldNotPresent,
spends: std::iter::once(first.clone())
.chain(rest.iter().cloned())
.collect::<Vec<_>>()
.try_into()
.map_err(|_| {
SerializationError::Parse("ShieldedData<PerSpendAnchor> spend list empty")
})?,
maybe_outputs: outputs,
},
};
Ok(Some(sapling::ShieldedData {
value_balance,
transfers,
binding_sig,
}))
}
}
#[cfg(any(test, feature = "proptest-impl"))]
impl ZcashSerialize for Option<sapling::ShieldedData<sapling::SharedAnchor>> {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
match self {
None => {
zcash_serialize_empty_list(&mut writer)?;
zcash_serialize_empty_list(&mut writer)?;
}
Some(sapling_shielded_data) => {
sapling_shielded_data.zcash_serialize(&mut writer)?;
}
}
Ok(())
}
}
#[cfg(any(test, feature = "proptest-impl"))]
impl ZcashSerialize for sapling::ShieldedData<sapling::SharedAnchor> {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
let (spend_prefixes, spend_proofs_sigs): (Vec<_>, Vec<_>) = self
.spends()
.cloned()
.map(sapling::Spend::<sapling::SharedAnchor>::into_v5_parts)
.map(|(prefix, proof, sig)| (prefix, (proof, sig)))
.unzip();
let (spend_proofs, spend_sigs) = spend_proofs_sigs.into_iter().unzip();
let (output_prefixes, output_proofs): (Vec<_>, _) = self
.outputs()
.cloned()
.map(sapling::Output::into_v5_parts)
.unzip();
spend_prefixes.zcash_serialize(&mut writer)?;
output_prefixes.zcash_serialize(&mut writer)?;
self.value_balance.zcash_serialize(&mut writer)?;
if let Some(shared_anchor) = self.shared_anchor() {
writer.write_all(&<[u8; 32]>::from(shared_anchor)[..])?;
}
zcash_serialize_external_count(&spend_proofs, &mut writer)?;
zcash_serialize_external_count(&spend_sigs, &mut writer)?;
zcash_serialize_external_count(&output_proofs, &mut writer)?;
writer.write_all(&<[u8; 64]>::from(self.binding_sig)[..])?;
Ok(())
}
}
#[cfg(any(test, feature = "proptest-impl"))]
impl ZcashDeserialize for Option<sapling::ShieldedData<sapling::SharedAnchor>> {
#[allow(clippy::unwrap_in_result)]
fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> {
deserialize_v5_sapling_shielded_data(reader, false)
}
}
#[allow(clippy::unwrap_in_result)]
#[cfg(any(test, feature = "proptest-impl"))]
fn deserialize_v5_sapling_shielded_data<R: io::Read>(
mut reader: R,
is_coinbase: bool,
) -> Result<Option<sapling::ShieldedData<sapling::SharedAnchor>>, SerializationError> {
let spend_count: CompactSizeMessage = (&mut reader).zcash_deserialize_into()?;
let spend_count: usize = spend_count.into();
if is_coinbase && spend_count > 0 {
return Err(SerializationError::Parse(
"coinbase transaction must not have Sapling spends",
));
}
let spend_prefixes: Vec<sapling::SpendPrefixInTransactionV5> =
zcash_deserialize_external_count(spend_count, &mut reader)?;
let output_prefixes: Vec<_> = (&mut reader).zcash_deserialize_into()?;
let spends_count = spend_prefixes.len();
let outputs_count = output_prefixes.len();
if spend_prefixes.is_empty() && output_prefixes.is_empty() {
return Ok(None);
}
let value_balance = (&mut reader).zcash_deserialize_into()?;
let shared_anchor = if spends_count > 0 {
Some((&mut reader).zcash_deserialize_into()?)
} else {
None
};
let spend_proofs = zcash_deserialize_external_count(spends_count, &mut reader)?;
let spend_sigs = zcash_deserialize_external_count(spends_count, &mut reader)?;
let output_proofs = zcash_deserialize_external_count(outputs_count, &mut reader)?;
let binding_sig = reader.read_64_bytes()?.into();
let spends: Vec<_> = spend_prefixes
.into_iter()
.zip(spend_proofs)
.zip(spend_sigs)
.map(|((prefix, proof), sig)| {
sapling::Spend::<sapling::SharedAnchor>::from_v5_parts(prefix, proof, sig)
})
.collect();
let outputs = output_prefixes
.into_iter()
.zip(output_proofs)
.map(|(prefix, proof)| sapling::Output::from_v5_parts(prefix, proof))
.collect();
let transfers = match shared_anchor {
Some(shared_anchor) => sapling::TransferData::SpendsAndMaybeOutputs {
shared_anchor,
spends: spends
.try_into()
.expect("checked spends when parsing shared anchor"),
maybe_outputs: outputs,
},
None => sapling::TransferData::JustOutputs {
outputs: outputs
.try_into()
.expect("checked spends or outputs and returned early"),
},
};
Ok(Some(sapling::ShieldedData {
value_balance,
transfers,
binding_sig,
}))
}
#[cfg(any(test, feature = "proptest-impl"))]
fn zcash_serialize_optional_orchard_bundle<W: io::Write>(
shielded_data: Option<&orchard::ShieldedData>,
mut writer: W,
) -> Result<(), io::Error> {
match shielded_data {
None => zcash_serialize_empty_list(&mut writer),
Some(shielded_data) => shielded_data.zcash_serialize(&mut writer),
}
}
#[cfg(any(test, feature = "proptest-impl"))]
impl ZcashSerialize for Option<orchard::ShieldedData> {
fn zcash_serialize<W: io::Write>(&self, writer: W) -> Result<(), io::Error> {
zcash_serialize_optional_orchard_bundle(self.as_ref(), writer)
}
}
#[cfg(any(test, feature = "proptest-impl"))]
impl ZcashSerialize for orchard::ShieldedData {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
let (actions, sigs): (Vec<orchard::Action>, Vec<Signature<SpendAuth>>) = self
.actions
.iter()
.cloned()
.map(orchard::AuthorizedAction::into_parts)
.unzip();
actions.zcash_serialize(&mut writer)?;
self.flags.zcash_serialize(&mut writer)?;
self.value_balance.zcash_serialize(&mut writer)?;
self.shared_anchor.zcash_serialize(&mut writer)?;
self.proof.zcash_serialize(&mut writer)?;
zcash_serialize_external_count(&sigs, &mut writer)?;
self.binding_sig.zcash_serialize(&mut writer)?;
Ok(())
}
}
#[cfg(any(test, feature = "proptest-impl"))]
trait V6FlagCodec {
type Codec: ZcashDeserialize + Into<orchard::Flags>;
}
#[cfg(any(test, feature = "proptest-impl"))]
impl V6FlagCodec for orchard::ShieldedDataV6 {
type Codec = orchard::Flags;
}
#[cfg(any(test, feature = "proptest-impl"))]
impl V6FlagCodec for ironwood::ShieldedData {
type Codec = orchard::FlagsV6;
}
#[cfg(any(test, feature = "proptest-impl"))]
fn deserialize_v6_orchard_shielded_data<R, T>(
reader: R,
) -> Result<Option<orchard::ShieldedData>, SerializationError>
where
R: io::Read,
T: V6FlagCodec,
{
deserialize_orchard_shielded_data::<R, T::Codec>(reader)
}
#[cfg(any(test, feature = "proptest-impl"))]
impl ZcashDeserialize for Option<orchard::ShieldedDataV6> {
fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> {
Ok(
deserialize_v6_orchard_shielded_data::<R, orchard::ShieldedDataV6>(reader)?
.map(orchard::ShieldedDataV6::new),
)
}
}
#[cfg(any(test, feature = "proptest-impl"))]
impl ZcashSerialize for Option<orchard::ShieldedDataV6> {
fn zcash_serialize<W: io::Write>(&self, writer: W) -> Result<(), io::Error> {
zcash_serialize_optional_orchard_bundle(self.as_ref().map(|data| data.data()), writer)
}
}
#[cfg(any(test, feature = "proptest-impl"))]
impl ZcashDeserialize for Option<ironwood::ShieldedData> {
fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> {
Ok(
deserialize_v6_orchard_shielded_data::<R, ironwood::ShieldedData>(reader)?
.map(orchard::ShieldedDataV6::new)
.map(ironwood::ShieldedData::new),
)
}
}
#[cfg(any(test, feature = "proptest-impl"))]
impl ZcashSerialize for Option<ironwood::ShieldedData> {
fn zcash_serialize<W: io::Write>(&self, writer: W) -> Result<(), io::Error> {
zcash_serialize_optional_orchard_bundle(self.as_ref().map(|data| data.data()), writer)
}
}
#[cfg(any(test, feature = "proptest-impl"))]
impl ZcashDeserialize for Option<orchard::ShieldedData> {
fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> {
deserialize_orchard_shielded_data::<R, orchard::Flags>(reader)
}
}
#[cfg(any(test, feature = "proptest-impl"))]
pub(crate) fn deserialize_orchard_shielded_data<R, F>(
mut reader: R,
) -> Result<Option<orchard::ShieldedData>, SerializationError>
where
R: io::Read,
F: ZcashDeserialize + Into<orchard::Flags>,
{
let actions: Vec<orchard::Action> = (&mut reader).zcash_deserialize_into()?;
if actions.is_empty() {
return Ok(None);
}
let flags: orchard::Flags = F::zcash_deserialize(&mut reader)?.into();
let value_balance: amount::Amount = (&mut reader).zcash_deserialize_into()?;
let shared_anchor: orchard::tree::Root = (&mut reader).zcash_deserialize_into()?;
let proof: Halo2Proof = (&mut reader).zcash_deserialize_into()?;
let sigs: Vec<Signature<SpendAuth>> =
zcash_deserialize_external_count(actions.len(), &mut reader)?;
let binding_sig: Signature<Binding> = (&mut reader).zcash_deserialize_into()?;
let authorized_actions: Vec<orchard::AuthorizedAction> = actions
.into_iter()
.zip(sigs)
.map(|(action, spend_auth_sig)| {
orchard::AuthorizedAction::from_parts(action, spend_auth_sig)
})
.collect();
let actions: AtLeastOne<orchard::AuthorizedAction> = authorized_actions.try_into()?;
Ok(Some(orchard::ShieldedData {
flags,
value_balance,
shared_anchor,
proof,
actions,
binding_sig,
}))
}
impl<T: reddsa::SigType> ZcashSerialize for reddsa::Signature<T> {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
writer.write_all(&<[u8; 64]>::from(*self)[..])?;
Ok(())
}
}
impl<T: reddsa::SigType> ZcashDeserialize for reddsa::Signature<T> {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
Ok(reader.read_64_bytes()?.into())
}
}
impl<T> ZcashDeserialize for Arc<T>
where
T: ZcashDeserialize,
{
fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> {
Ok(Arc::new(T::zcash_deserialize(reader)?))
}
}
impl<T> ZcashSerialize for Arc<T>
where
T: ZcashSerialize,
{
fn zcash_serialize<W: io::Write>(&self, writer: W) -> Result<(), io::Error> {
T::zcash_serialize(self, writer)
}
}
pub(crate) const MIN_TRANSPARENT_INPUT_SIZE: u64 = 32 + 4 + 4 + 1;
pub(crate) const MIN_TRANSPARENT_OUTPUT_SIZE: u64 = 8 + 1;
pub const MIN_TRANSPARENT_TX_SIZE: u64 =
MIN_TRANSPARENT_INPUT_SIZE + 4 + MIN_TRANSPARENT_OUTPUT_SIZE;
pub const MIN_TRANSPARENT_TX_V4_SIZE: u64 = MIN_TRANSPARENT_TX_SIZE + 4;
pub const MIN_TRANSPARENT_TX_V5_SIZE: u64 = MIN_TRANSPARENT_TX_SIZE + 4 + 4;
impl TrustedPreallocate for Transaction {
fn max_allocation() -> u64 {
MAX_BLOCK_BYTES / MIN_TRANSPARENT_TX_SIZE
}
}
impl TrustedPreallocate for transparent::Input {
fn max_allocation() -> u64 {
MAX_BLOCK_BYTES / MIN_TRANSPARENT_INPUT_SIZE
}
}
impl TrustedPreallocate for transparent::Output {
fn max_allocation() -> u64 {
MAX_BLOCK_BYTES / MIN_TRANSPARENT_OUTPUT_SIZE
}
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct SerializedTransaction {
bytes: Vec<u8>,
}
impl fmt::Display for SerializedTransaction {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&hex::encode(&self.bytes))
}
}
impl fmt::Debug for SerializedTransaction {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut data_truncated = hex::encode(&self.bytes);
if data_truncated.len() > 1003 {
let end = data_truncated.len() - 500;
data_truncated.replace_range(500..=end, "...");
}
f.debug_tuple("SerializedTransaction")
.field(&data_truncated)
.finish()
}
}
impl<B: Borrow<Transaction>> From<B> for SerializedTransaction {
fn from(tx: B) -> Self {
SerializedTransaction {
bytes: tx
.borrow()
.zcash_serialize_to_vec()
.expect("Writing to a `Vec` should never fail"),
}
}
}
impl AsRef<[u8]> for SerializedTransaction {
fn as_ref(&self) -> &[u8] {
self.bytes.as_ref()
}
}
impl From<Vec<u8>> for SerializedTransaction {
fn from(bytes: Vec<u8>) -> Self {
Self { bytes }
}
}
impl FromHex for SerializedTransaction {
type Error = <Vec<u8> as FromHex>::Error;
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
let bytes = <Vec<u8>>::from_hex(hex)?;
Ok(bytes.into())
}
}