use std::{
cmp::{max, Eq, PartialEq},
fmt::{self, Debug},
};
use derive_getters::Getters;
use itertools::Itertools;
#[cfg(any(test, feature = "proptest-impl"))]
use proptest_derive::Arbitrary;
use serde::{de::DeserializeOwned, Serialize};
use crate::{
amount::Amount,
primitives::{
redjubjub::{Binding, Signature},
Groth16Proof,
},
sapling::{
output::OutputPrefixInTransactionV5, spend::SpendPrefixInTransactionV5, tree, Nullifier,
Output, Spend,
},
serialization::{AtLeastOne, TrustedPreallocate},
};
#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct PerSpendAnchor {}
#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct SharedAnchor {}
#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
pub struct FieldNotPresent;
impl AnchorVariant for PerSpendAnchor {
type Shared = FieldNotPresent;
type PerSpend = tree::Root;
}
impl AnchorVariant for SharedAnchor {
type Shared = tree::Root;
type PerSpend = FieldNotPresent;
}
pub trait AnchorVariant {
type Shared: Clone + Debug + DeserializeOwned + Serialize + Eq + PartialEq;
type PerSpend: Clone + Debug + DeserializeOwned + Serialize + Eq + PartialEq;
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Getters)]
pub struct ShieldedData<AnchorV>
where
AnchorV: AnchorVariant + Clone,
{
pub value_balance: Amount,
pub transfers: TransferData<AnchorV>,
pub binding_sig: Signature<Binding>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum TransferData<AnchorV>
where
AnchorV: AnchorVariant + Clone,
{
SpendsAndMaybeOutputs {
shared_anchor: AnchorV::Shared,
spends: AtLeastOne<Spend<AnchorV>>,
maybe_outputs: Vec<Output>,
},
JustOutputs {
outputs: AtLeastOne<Output>,
},
}
impl<AnchorV> fmt::Display for ShieldedData<AnchorV>
where
AnchorV: AnchorVariant + Clone,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut fmter = f.debug_struct(
format!(
"sapling::ShieldedData<{}>",
std::any::type_name::<AnchorV>()
)
.as_str(),
);
fmter.field("spends", &self.transfers.spends().count());
fmter.field("outputs", &self.transfers.outputs().count());
fmter.field("value_balance", &self.value_balance);
fmter.finish()
}
}
impl<AnchorV> ShieldedData<AnchorV>
where
AnchorV: AnchorVariant + Clone,
Spend<PerSpendAnchor>: From<(Spend<AnchorV>, AnchorV::Shared)>,
{
pub fn anchors(&self) -> impl Iterator<Item = tree::Root> + '_ {
self.spends_per_anchor()
.map(|spend| spend.per_spend_anchor)
.unique_by(|raw| raw.0.to_bytes())
}
pub fn spends_per_anchor(&self) -> impl Iterator<Item = Spend<PerSpendAnchor>> + '_ {
self.transfers.spends_per_anchor()
}
}
impl<AnchorV> ShieldedData<AnchorV>
where
AnchorV: AnchorVariant + Clone,
{
pub fn spends(&self) -> impl Iterator<Item = &Spend<AnchorV>> {
self.transfers.spends()
}
pub fn outputs(&self) -> impl Iterator<Item = &Output> {
self.transfers.outputs()
}
pub fn shared_anchor(&self) -> Option<AnchorV::Shared> {
self.transfers.shared_anchor()
}
pub fn nullifiers(&self) -> impl Iterator<Item = &Nullifier> {
self.spends().map(|spend| &spend.nullifier)
}
pub fn note_commitments(
&self,
) -> impl Iterator<Item = &sapling_crypto::note::ExtractedNoteCommitment> {
self.outputs().map(|output| &output.cm_u)
}
pub fn binding_verification_key(&self) -> redjubjub::VerificationKeyBytes<Binding> {
let cv_old: sapling_crypto::value::CommitmentSum =
self.spends().map(|spend| spend.cv.0.clone()).sum();
let cv_new: sapling_crypto::value::CommitmentSum =
self.outputs().map(|output| output.cv.0.clone()).sum();
(cv_old - cv_new)
.into_bvk(self.value_balance.zatoshis())
.into()
}
}
impl<AnchorV> TransferData<AnchorV>
where
AnchorV: AnchorVariant + Clone,
Spend<PerSpendAnchor>: From<(Spend<AnchorV>, AnchorV::Shared)>,
{
pub fn spends_per_anchor(&self) -> impl Iterator<Item = Spend<PerSpendAnchor>> + '_ {
self.spends().cloned().map(move |spend| {
Spend::<PerSpendAnchor>::from((
spend,
self.shared_anchor()
.expect("shared anchor must be Some if there are any spends"),
))
})
}
}
impl<AnchorV> TransferData<AnchorV>
where
AnchorV: AnchorVariant + Clone,
{
pub fn spends(&self) -> impl Iterator<Item = &Spend<AnchorV>> {
use TransferData::*;
let spends = match self {
SpendsAndMaybeOutputs { spends, .. } => Some(spends.iter()),
JustOutputs { .. } => None,
};
spends.into_iter().flatten()
}
pub fn outputs(&self) -> impl Iterator<Item = &Output> {
use TransferData::*;
match self {
SpendsAndMaybeOutputs { maybe_outputs, .. } => maybe_outputs,
JustOutputs { outputs, .. } => outputs.as_vec(),
}
.iter()
}
pub fn shared_anchor(&self) -> Option<AnchorV::Shared> {
use TransferData::*;
match self {
SpendsAndMaybeOutputs { shared_anchor, .. } => Some(shared_anchor.clone()),
JustOutputs { .. } => None,
}
}
}
impl TrustedPreallocate for Groth16Proof {
fn max_allocation() -> u64 {
max(
SpendPrefixInTransactionV5::max_allocation(),
OutputPrefixInTransactionV5::max_allocation(),
)
}
}