From ea7207764cc48c0068330af58f27216488f8a8f8 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 11 Dec 2023 12:36:16 -0700 Subject: [PATCH] Add `builder::PaddingRule` for explicit control over output padding. --- src/builder.rs | 50 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index be084af..f519468 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -29,6 +29,39 @@ 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. + 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), + /// A padding rule that specifies that no additional dummy Sapling outputs are to be + /// constructed. + None, +} + +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) + } + } + PaddingRule::None => real_outputs, + } + } +} + #[derive(Debug, PartialEq, Eq)] pub enum Error { AnchorMismatch, @@ -289,16 +322,18 @@ pub struct SaplingBuilder { spends: Vec, outputs: Vec, zip212_enforcement: Zip212Enforcement, + padding_rule: PaddingRule, } impl SaplingBuilder { - pub fn new(zip212_enforcement: Zip212Enforcement) -> Self { + pub fn new(zip212_enforcement: Zip212Enforcement, padding_rule: PaddingRule) -> Self { SaplingBuilder { anchor: None, value_balance: ValueSum::zero(), spends: vec![], outputs: vec![], zip212_enforcement, + padding_rule, } } @@ -319,11 +354,7 @@ 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()), - } + self.padding_rule.num_outputs(self.outputs.len()) } /// Returns the net value represented by the spends and outputs added to this builder, @@ -407,6 +438,7 @@ impl SaplingBuilder { mut rng: R, ) -> Result, SaplingMetadata)>, Error> { let value_balance = self.try_value_balance()?; + let bundle_output_count = self.bundle_output_count(); // Record initial positions of spends and outputs let mut indexed_spends: Vec<_> = self.spends.into_iter().enumerate().collect(); @@ -425,7 +457,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 +908,7 @@ pub mod testing { frontier::testing::arb_commitment_tree, witness::IncrementalWitness, }; - use super::SaplingBuilder; + use super::{PaddingRule, SaplingBuilder}; #[allow(dead_code)] fn arb_bundle>( @@ -902,7 +934,7 @@ pub mod testing { }) .prop_map( move |(extsk, spendable_notes, commitment_trees, rng_seed, fake_sighash_bytes)| { - let mut builder = SaplingBuilder::new(zip212_enforcement); + let mut builder = SaplingBuilder::new(zip212_enforcement, PaddingRule::DEFAULT); let mut rng = StdRng::from_seed(rng_seed); for (note, path) in spendable_notes