diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e4d611..6b5693a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ The entries below are relative to the `zcash_primitives::sapling` module as of - `SpendDescriptionInfo::value` - `SaplingOutputInfo` - `ProverProgress` + - `PaddingRule` - `sapling_crypto::bundle` module: - The following types moved from `zcash_primitives::transaction::components::sapling`: @@ -102,6 +103,10 @@ The entries below are relative to the `zcash_primitives::sapling` module as of generic parameters and returns `(UnauthorizedBundle, SaplingMetadata)`. The caller can then use `Bundle::>::create_proofs` to create spend and output proofs for the bundle. + - `SaplingBuilder::build` now takes a `PaddingRule` argument that instructs + it how to pad the bundle with dummy outputs. + - `SaplingBuilder::bundle_output_count` has been changed to use a padding + rule to compute its result. - `Error` has new error variants: - `Error::DuplicateSignature` - `Error::InvalidExternalSignature` diff --git a/src/builder.rs b/src/builder.rs index be084af..5966a0ea 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -29,6 +29,36 @@ use crate::{ /// with dummy outputs if necessary. See . const MIN_SHIELDED_OUTPUTS: usize = 2; +/// An enumeration of rules for construction of dummy outputs that may be applied to Sapling bundle +/// construction. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum PaddingRule { + /// A rule that requires that at least the specified number of Sapling outputs is constructed, + /// irrespective of whether any genuine outputs are being included. If no padding is to be + /// performed, `Require(0)` may be used. + Require(usize), + /// A rule that requires that at least the specified number of Sapling outputs are constructed, + /// iff any genuine outputs are being included. + PadTo(usize), +} + +impl PaddingRule { + pub const DEFAULT: PaddingRule = PaddingRule::PadTo(MIN_SHIELDED_OUTPUTS); + + pub fn num_outputs(&self, real_outputs: usize) -> usize { + match self { + PaddingRule::Require(n) => std::cmp::max(real_outputs, *n), + PaddingRule::PadTo(n) => { + if real_outputs == 0 { + 0 + } else { + std::cmp::max(real_outputs, *n) + } + } + } + } +} + #[derive(Debug, PartialEq, Eq)] pub enum Error { AnchorMismatch, @@ -318,12 +348,8 @@ impl SaplingBuilder { /// /// This may be larger than the number of outputs that have been added to the builder, /// depending on whether padding is going to be applied. - pub fn bundle_output_count(&self) -> usize { - // This matches the padding behaviour in `Self::build`. - match self.spends.len() { - 0 => self.outputs.len(), - _ => std::cmp::max(MIN_SHIELDED_OUTPUTS, self.outputs.len()), - } + pub fn bundle_output_count(&self, padding_rule: &PaddingRule) -> usize { + padding_rule.num_outputs(self.outputs.len()) } /// Returns the net value represented by the spends and outputs added to this builder, @@ -405,8 +431,10 @@ impl SaplingBuilder { pub fn build>( self, mut rng: R, + padding_rule: &PaddingRule, ) -> Result, SaplingMetadata)>, Error> { let value_balance = self.try_value_balance()?; + let bundle_output_count = self.bundle_output_count(padding_rule); // Record initial positions of spends and outputs let mut indexed_spends: Vec<_> = self.spends.into_iter().enumerate().collect(); @@ -425,7 +453,7 @@ impl SaplingBuilder { // Pad Sapling outputs if !indexed_spends.is_empty() { - while indexed_outputs.len() < MIN_SHIELDED_OUTPUTS { + while indexed_outputs.len() < bundle_output_count { indexed_outputs.push(None); } } @@ -876,7 +904,7 @@ pub mod testing { frontier::testing::arb_commitment_tree, witness::IncrementalWitness, }; - use super::SaplingBuilder; + use super::{PaddingRule, SaplingBuilder}; #[allow(dead_code)] fn arb_bundle>( @@ -913,7 +941,10 @@ pub mod testing { } let (bundle, _) = builder - .build::(&mut rng) + .build::( + &mut rng, + &PaddingRule::DEFAULT, + ) .unwrap() .unwrap();