Add explicit control of padding to the Builder API.

This commit is contained in:
Kris Nuttycombe 2023-12-08 13:38:52 -07:00
parent 06cb76168e
commit 0a257d6f68
5 changed files with 67 additions and 20 deletions

View File

@ -6,11 +6,15 @@ and this project adheres to Rust's notion of
[Semantic Versioning](https://semver.org/spec/v2.0.0.html). [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased] ## [Unreleased]
### Added
- `orchard::builder::BundleType`
### Changed ### Changed
- `orchard::builder::Builder::add_recipient` has been renamed to `add_output` - `orchard::builder::Builder::add_recipient` has been renamed to `add_output`
in order to clarify than more than one output of a given transaction may be in order to clarify than more than one output of a given transaction may be
sent to the same recipient. sent to the same recipient.
- `orchard::builder::Builder::build` now takes an additional `BundleType` argument
that specifies how actions should be padded, instead of using hardcoded padding.
## [0.6.0] - 2023-09-08 ## [0.6.0] - 2023-09-08
### Changed ### Changed

View File

@ -7,7 +7,7 @@ use criterion::{BenchmarkId, Criterion};
use pprof::criterion::{Output, PProfProfiler}; use pprof::criterion::{Output, PProfProfiler};
use orchard::{ use orchard::{
builder::Builder, builder::{Builder, BundleType},
bundle::Flags, bundle::Flags,
circuit::{ProvingKey, VerifyingKey}, circuit::{ProvingKey, VerifyingKey},
keys::{FullViewingKey, Scope, SpendingKey}, keys::{FullViewingKey, Scope, SpendingKey},
@ -35,7 +35,7 @@ fn criterion_benchmark(c: &mut Criterion) {
.add_output(None, recipient, NoteValue::from_raw(10), None) .add_output(None, recipient, NoteValue::from_raw(10), None)
.unwrap(); .unwrap();
} }
let bundle: Bundle<_, i64> = builder.build(rng).unwrap(); let bundle: Bundle<_, i64> = builder.build(rng, &BundleType::Transactional).unwrap();
let instances: Vec<_> = bundle let instances: Vec<_> = bundle
.actions() .actions()

View File

@ -1,6 +1,6 @@
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use orchard::{ use orchard::{
builder::Builder, builder::{Builder, BundleType},
bundle::Flags, bundle::Flags,
circuit::ProvingKey, circuit::ProvingKey,
keys::{FullViewingKey, PreparedIncomingViewingKey, Scope, SpendingKey}, keys::{FullViewingKey, PreparedIncomingViewingKey, Scope, SpendingKey},
@ -57,7 +57,7 @@ fn bench_note_decryption(c: &mut Criterion) {
builder builder
.add_output(None, recipient, NoteValue::from_raw(10), None) .add_output(None, recipient, NoteValue::from_raw(10), None)
.unwrap(); .unwrap();
let bundle: Bundle<_, i64> = builder.build(rng).unwrap(); let bundle: Bundle<_, i64> = builder.build(rng, &BundleType::Transactional).unwrap();
bundle bundle
.create_proof(&pk, rng) .create_proof(&pk, rng)
.unwrap() .unwrap()

View File

@ -27,6 +27,42 @@ use crate::{
const MIN_ACTIONS: usize = 2; const MIN_ACTIONS: usize = 2;
/// An enumeration of rules Orchard bundle construction.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BundleType {
/// A transactional bundle will be padded if necessary to contain at least 2 actions,
/// irrespective of whether any genuine actions are required.
Transactional,
/// A coinbase bundle is required to have no non-dummy spends. No padding is performed.
Coinbase,
}
impl BundleType {
/// Returns the number of logical actions that builder will produce in constructing a bundle
/// of this type, given the specified numbers of spends and outputs.
///
/// Returns an error if the specified number of spends and outputs is incompatible with
/// this bundle type.
pub fn num_actions(
&self,
num_spends: usize,
num_outputs: usize,
) -> Result<usize, &'static str> {
let num_real_actions = core::cmp::max(num_spends, num_outputs);
match self {
BundleType::Transactional => Ok(core::cmp::max(num_real_actions, MIN_ACTIONS)),
BundleType::Coinbase => {
if num_spends == 0 {
Ok(num_real_actions)
} else {
Err("Spends not allowed in coinbase bundles")
}
}
}
}
}
/// An error type for the kinds of errors that can occur during bundle construction. /// An error type for the kinds of errors that can occur during bundle construction.
#[derive(Debug)] #[derive(Debug)]
pub enum BuildError { pub enum BuildError {
@ -42,6 +78,8 @@ pub enum BuildError {
/// A signature is valid for more than one input. This should never happen if `alpha` /// A signature is valid for more than one input. This should never happen if `alpha`
/// is sampled correctly, and indicates a critical failure in randomness generation. /// is sampled correctly, and indicates a critical failure in randomness generation.
DuplicateSignature, DuplicateSignature,
/// The bundle being constructed violated the construction rules for the requested bundle type.
BundleTypeNotSatisfiable,
} }
impl Display for BuildError { impl Display for BuildError {
@ -53,6 +91,9 @@ impl Display for BuildError {
ValueSum(_) => f.write_str("Overflow occurred during value construction"), ValueSum(_) => f.write_str("Overflow occurred during value construction"),
InvalidExternalSignature => f.write_str("External signature was invalid"), InvalidExternalSignature => f.write_str("External signature was invalid"),
DuplicateSignature => f.write_str("Signature valid for more than one input"), DuplicateSignature => f.write_str("Signature valid for more than one input"),
BundleTypeNotSatisfiable => {
f.write_str("Bundle structure did not conform to requested bundle type.")
}
} }
} }
} }
@ -388,22 +429,23 @@ impl Builder {
pub fn build<V: TryFrom<i64>>( pub fn build<V: TryFrom<i64>>(
mut self, mut self,
mut rng: impl RngCore, mut rng: impl RngCore,
bundle_type: &BundleType,
) -> Result<Bundle<InProgress<Unproven, Unauthorized>, V>, BuildError> { ) -> Result<Bundle<InProgress<Unproven, Unauthorized>, V>, BuildError> {
let num_real_spends = self.spends.len();
let num_real_outputs = self.outputs.len();
let num_actions = bundle_type
.num_actions(num_real_spends, num_real_outputs)
.map_err(|_| BuildError::BundleTypeNotSatisfiable)?;
// Pair up the spends and outputs, extending with dummy values as necessary. // Pair up the spends and outputs, extending with dummy values as necessary.
let pre_actions: Vec<_> = { let pre_actions: Vec<_> = {
let num_spends = self.spends.len();
let num_outputs = self.outputs.len();
let num_actions = [num_spends, num_outputs, MIN_ACTIONS]
.iter()
.max()
.cloned()
.unwrap();
self.spends.extend( self.spends.extend(
iter::repeat_with(|| SpendInfo::dummy(&mut rng)).take(num_actions - num_spends), iter::repeat_with(|| SpendInfo::dummy(&mut rng))
.take(num_actions - num_real_spends),
); );
self.outputs.extend( self.outputs.extend(
iter::repeat_with(|| OutputInfo::dummy(&mut rng)).take(num_actions - num_outputs), iter::repeat_with(|| OutputInfo::dummy(&mut rng))
.take(num_actions - num_real_outputs),
); );
// Shuffle the spends and outputs, so that learning the position of a // Shuffle the spends and outputs, so that learning the position of a
@ -776,7 +818,7 @@ pub mod testing {
Address, Note, Address, Note,
}; };
use super::Builder; use super::{Builder, BundleType};
/// An intermediate type used for construction of arbitrary /// An intermediate type used for construction of arbitrary
/// bundle values. This type is required because of a limitation /// bundle values. This type is required because of a limitation
@ -817,7 +859,7 @@ pub mod testing {
let pk = ProvingKey::build(); let pk = ProvingKey::build();
builder builder
.build(&mut self.rng) .build(&mut self.rng, &BundleType::Transactional)
.unwrap() .unwrap()
.create_proof(&pk, &mut self.rng) .create_proof(&pk, &mut self.rng)
.unwrap() .unwrap()
@ -898,6 +940,7 @@ mod tests {
use super::Builder; use super::Builder;
use crate::{ use crate::{
builder::BundleType,
bundle::{Authorized, Bundle, Flags}, bundle::{Authorized, Bundle, Flags},
circuit::ProvingKey, circuit::ProvingKey,
constants::MERKLE_DEPTH_ORCHARD, constants::MERKLE_DEPTH_ORCHARD,
@ -927,7 +970,7 @@ mod tests {
assert_eq!(balance, -5000); assert_eq!(balance, -5000);
let bundle: Bundle<Authorized, i64> = builder let bundle: Bundle<Authorized, i64> = builder
.build(&mut rng) .build(&mut rng, &BundleType::Transactional)
.unwrap() .unwrap()
.create_proof(&pk, &mut rng) .create_proof(&pk, &mut rng)
.unwrap() .unwrap()

View File

@ -1,7 +1,7 @@
use bridgetree::BridgeTree; use bridgetree::BridgeTree;
use incrementalmerkletree::Hashable; use incrementalmerkletree::Hashable;
use orchard::{ use orchard::{
builder::Builder, builder::{Builder, BundleType},
bundle::{Authorized, Flags}, bundle::{Authorized, Flags},
circuit::{ProvingKey, VerifyingKey}, circuit::{ProvingKey, VerifyingKey},
keys::{FullViewingKey, PreparedIncomingViewingKey, Scope, SpendAuthorizingKey, SpendingKey}, keys::{FullViewingKey, PreparedIncomingViewingKey, Scope, SpendAuthorizingKey, SpendingKey},
@ -47,7 +47,7 @@ fn bundle_chain() {
builder.add_output(None, recipient, NoteValue::from_raw(5000), None), builder.add_output(None, recipient, NoteValue::from_raw(5000), None),
Ok(()) Ok(())
); );
let unauthorized = builder.build(&mut rng).unwrap(); let unauthorized = builder.build(&mut rng, &BundleType::Transactional).unwrap();
let sighash = unauthorized.commitment().into(); let sighash = unauthorized.commitment().into();
let proven = unauthorized.create_proof(&pk, &mut rng).unwrap(); let proven = unauthorized.create_proof(&pk, &mut rng).unwrap();
proven.apply_signatures(rng, sighash, &[]).unwrap() proven.apply_signatures(rng, sighash, &[]).unwrap()
@ -89,7 +89,7 @@ fn bundle_chain() {
builder.add_output(None, recipient, NoteValue::from_raw(5000), None), builder.add_output(None, recipient, NoteValue::from_raw(5000), None),
Ok(()) Ok(())
); );
let unauthorized = builder.build(&mut rng).unwrap(); let unauthorized = builder.build(&mut rng, &BundleType::Transactional).unwrap();
let sighash = unauthorized.commitment().into(); let sighash = unauthorized.commitment().into();
let proven = unauthorized.create_proof(&pk, &mut rng).unwrap(); let proven = unauthorized.create_proof(&pk, &mut rng).unwrap();
proven proven