1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381
//! Sapling shielded data for `V4` and `V5` `Transaction`s.
//!
//! Zebra uses a generic shielded data type for `V4` and `V5` transactions.
//! The `value_balance` change is handled using the default zero value.
//! The anchor change is handled using the `AnchorVariant` type trait.
use std::{
cmp::{max, Eq, PartialEq},
fmt::{self, Debug},
};
use itertools::Itertools;
#[cfg(any(test, feature = "proptest-impl"))]
use proptest_derive::Arbitrary;
use serde::{de::DeserializeOwned, Serialize};
use crate::{
amount::{Amount, NegativeAllowed},
primitives::{
redjubjub::{Binding, Signature},
Groth16Proof,
},
sapling::{
output::OutputPrefixInTransactionV5, spend::SpendPrefixInTransactionV5, tree, Nullifier,
Output, Spend, ValueCommitment,
},
serialization::{AtLeastOne, TrustedPreallocate},
};
/// Per-Spend Sapling anchors, used in Transaction V4 and the
/// `spends_per_anchor` method.
#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct PerSpendAnchor {}
/// Shared Sapling anchors, used in Transaction V5.
#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct SharedAnchor {}
/// This field is not present in this transaction version.
#[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;
}
/// A type trait to handle structural differences between V4 and V5 Sapling
/// Transaction anchors.
///
/// In Transaction V4, anchors are per-Spend. In Transaction V5, there is a
/// single transaction anchor for all Spends in a transaction.
pub trait AnchorVariant {
/// The type of the shared anchor.
type Shared: Clone + Debug + DeserializeOwned + Serialize + Eq + PartialEq;
/// The type of the per-spend anchor.
type PerSpend: Clone + Debug + DeserializeOwned + Serialize + Eq + PartialEq;
}
/// A bundle of [`Spend`] and [`Output`] descriptions and signature data.
///
/// Spend and Output descriptions are optional, but Zcash transactions must
/// include a binding signature if and only if there is at least one Spend *or*
/// Output description. This wrapper type bundles at least one Spend or Output
/// description with the required signature data, so that an
/// `Option<ShieldedData>` correctly models the presence or absence of any
/// shielded data.
///
/// # Differences between Transaction Versions
///
/// The Sapling `value_balance` field is optional in `Transaction::V5`, but
/// required in `Transaction::V4`. In both cases, if there is no `ShieldedData`,
/// then the field value must be zero. Therefore, we only need to store
/// `value_balance` when there is some Sapling `ShieldedData`.
///
/// In `Transaction::V4`, each `Spend` has its own anchor. In `Transaction::V5`,
/// there is a single `shared_anchor` for the entire transaction, which is only
/// present when there is at least one spend. These structural differences are
/// modeled using the `AnchorVariant` type trait and `TransferData` enum.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct ShieldedData<AnchorV>
where
AnchorV: AnchorVariant + Clone,
{
/// The net value of Sapling spend transfers minus output transfers.
/// Denoted as `valueBalanceSapling` in the spec.
pub value_balance: Amount,
/// A bundle of spends and outputs, containing at least one spend or
/// output, in the order they appear in the transaction.
///
/// In V5 transactions, also contains a shared anchor, if there are any
/// spends.
pub transfers: TransferData<AnchorV>,
/// A signature on the transaction hash.
/// Denoted as `bindingSigSapling` in the spec.
pub binding_sig: Signature<Binding>,
}
/// A bundle of [`Spend`] and [`Output`] descriptions, and a shared anchor.
///
/// This wrapper type bundles at least one Spend or Output description with
/// the required anchor data, so that an `Option<ShieldedData>` (which contains
/// this type) correctly models the presence or absence of any spends and
/// shielded data, across both V4 and V5 transactions.
///
/// Specifically, TransferData ensures that:
/// * there is at least one spend or output, and
/// * the shared anchor is only present when there are spends.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum TransferData<AnchorV>
where
AnchorV: AnchorVariant + Clone,
{
/// A bundle containing at least one spend, and the shared spend anchor.
/// There can also be zero or more outputs.
///
/// In Transaction::V5, if there are any spends, there must also be a shared
/// spend anchor.
SpendsAndMaybeOutputs {
/// The shared anchor for all `Spend`s in this transaction.
///
/// The anchor is the root of the Sapling note commitment tree in a previous
/// block. This root should be in the best chain for a transaction to be
/// mined, and it must be in the relevant chain for a transaction to be
/// valid.
///
/// Some transaction versions have a per-spend anchor, rather than a shared
/// anchor.
///
/// Use the `shared_anchor` method to access this field.
shared_anchor: AnchorV::Shared,
/// At least one spend.
///
/// Use the [`ShieldedData::spends`] method to get an iterator over the
/// [`Spend`]s in this `TransferData`.
spends: AtLeastOne<Spend<AnchorV>>,
/// Maybe some outputs (can be empty), in the order they appear in the
/// transaction.
///
/// Use the [`ShieldedData::outputs`] method to get an iterator over the
/// [`Output`]s in this `TransferData`.
maybe_outputs: Vec<Output>,
},
/// A bundle containing at least one output, with no spends and no shared
/// spend anchor.
///
/// In Transaction::V5, if there are no spends, there must not be a shared
/// anchor.
JustOutputs {
/// At least one output, in the order they appear in the transaction.
///
/// Use the [`ShieldedData::outputs`] method to get an iterator over the
/// [`Output`]s in this `TransferData`.
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)>,
{
/// Iterate over the [`Spend`]s for this transaction, returning deduplicated
/// [`tree::Root`]s, regardless of the underlying transaction version.
pub fn anchors(&self) -> impl Iterator<Item = tree::Root> + '_ {
// TODO: use TransferData::shared_anchor to improve performance for V5 transactions
self.spends_per_anchor()
.map(|spend| spend.per_spend_anchor)
.unique_by(|raw| raw.0.to_bytes())
}
/// Iterate over the [`Spend`]s for this transaction, returning
/// `Spend<PerSpendAnchor>` regardless of the underlying transaction version.
///
/// # Correctness
///
/// Do not use this function for serialization.
pub fn spends_per_anchor(&self) -> impl Iterator<Item = Spend<PerSpendAnchor>> + '_ {
self.transfers.spends_per_anchor()
}
}
impl<AnchorV> ShieldedData<AnchorV>
where
AnchorV: AnchorVariant + Clone,
{
/// Iterate over the [`Spend`]s for this transaction, returning them as
/// their generic type.
///
/// # Correctness
///
/// Use this function for serialization.
pub fn spends(&self) -> impl Iterator<Item = &Spend<AnchorV>> {
self.transfers.spends()
}
/// Iterate over the [`Output`]s for this transaction, in the order they
/// appear in it.
pub fn outputs(&self) -> impl Iterator<Item = &Output> {
self.transfers.outputs()
}
/// Provide the shared anchor for this transaction, if present.
///
/// The shared anchor is only present if:
/// * there is at least one spend, and
/// * this is a `V5` transaction.
pub fn shared_anchor(&self) -> Option<AnchorV::Shared> {
self.transfers.shared_anchor()
}
/// Collect the [`Nullifier`]s for this transaction, if it contains
/// [`Spend`]s.
pub fn nullifiers(&self) -> impl Iterator<Item = &Nullifier> {
self.spends().map(|spend| &spend.nullifier)
}
/// Collect the cm_u's for this transaction, if it contains [`Output`]s,
/// in the order they appear in the transaction.
pub fn note_commitments(&self) -> impl Iterator<Item = &jubjub::Fq> {
self.outputs().map(|output| &output.cm_u)
}
/// Calculate the Spend/Output binding verification key.
///
/// Getting the binding signature validating key from the Spend and Output
/// description value commitments and the balancing value implicitly checks
/// that the balancing value is consistent with the value transferred in the
/// Spend and Output descriptions but also proves that the signer knew the
/// randomness used for the Spend and Output value commitments, which
/// prevents replays of Output descriptions.
///
/// The net value of Spend transfers minus Output transfers in a transaction
/// is called the balancing value, measured in zatoshi as a signed integer
/// v_balance.
///
/// Consistency of v_balance with the value commitments in Spend
/// descriptions and Output descriptions is enforced by the binding
/// signature.
///
/// Instead of generating a key pair at random, we generate it as a function
/// of the value commitments in the Spend descriptions and Output
/// descriptions of the transaction, and the balancing value.
///
/// <https://zips.z.cash/protocol/protocol.pdf#saplingbalance>
pub fn binding_verification_key(&self) -> redjubjub::VerificationKeyBytes<Binding> {
let cv_old: ValueCommitment = self.spends().map(|spend| spend.cv.into()).sum();
let cv_new: ValueCommitment = self.outputs().map(|output| output.cv.into()).sum();
let cv_balance: ValueCommitment =
ValueCommitment::new(jubjub::Fr::zero(), self.value_balance);
let key_bytes: [u8; 32] = (cv_old - cv_new - cv_balance).into();
key_bytes.into()
}
/// Provide access to the `value_balance` field of the shielded data.
///
/// Needed to calculate the sapling value balance.
pub fn value_balance(&self) -> Amount<NegativeAllowed> {
self.value_balance
}
}
impl<AnchorV> TransferData<AnchorV>
where
AnchorV: AnchorVariant + Clone,
Spend<PerSpendAnchor>: From<(Spend<AnchorV>, AnchorV::Shared)>,
{
/// Iterate over the [`Spend`]s for this transaction, returning
/// `Spend<PerSpendAnchor>` regardless of the underlying transaction version.
///
/// Allows generic operations over V4 and V5 transactions, including:
/// * spend verification, and
/// * non-malleable transaction ID generation.
///
/// # Correctness
///
/// Do not use this function for serialization.
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,
{
/// Iterate over the [`Spend`]s for this transaction, returning them as
/// their generic type.
pub fn spends(&self) -> impl Iterator<Item = &Spend<AnchorV>> {
use TransferData::*;
let spends = match self {
SpendsAndMaybeOutputs { spends, .. } => Some(spends.iter()),
JustOutputs { .. } => None,
};
// this awkward construction avoids returning a newtype struct or
// type-erased boxed iterator
spends.into_iter().flatten()
}
/// Iterate over the [`Output`]s for this transaction.
pub fn outputs(&self) -> impl Iterator<Item = &Output> {
use TransferData::*;
match self {
SpendsAndMaybeOutputs { maybe_outputs, .. } => maybe_outputs,
JustOutputs { outputs, .. } => outputs.as_vec(),
}
.iter()
}
/// Provide the shared anchor for this transaction, if present.
///
/// The shared anchor is only present if:
/// * there is at least one spend, and
/// * this is a `V5` transaction.
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 {
// Each V5 transaction proof array entry must have a corresponding
// spend or output prefix. We use the larger limit, so we don't reject
// any valid large blocks.
//
// TODO: put a separate limit on proofs in spends and outputs
max(
SpendPrefixInTransactionV5::max_allocation(),
OutputPrefixInTransactionV5::max_allocation(),
)
}
}