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).
## [Unreleased]
### Added
- `orchard::builder::BundleType`
### Changed
- `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
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
### Changed

View File

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

View File

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

View File

@ -27,6 +27,42 @@ use crate::{
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.
#[derive(Debug)]
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`
/// is sampled correctly, and indicates a critical failure in randomness generation.
DuplicateSignature,
/// The bundle being constructed violated the construction rules for the requested bundle type.
BundleTypeNotSatisfiable,
}
impl Display for BuildError {
@ -53,6 +91,9 @@ impl Display for BuildError {
ValueSum(_) => f.write_str("Overflow occurred during value construction"),
InvalidExternalSignature => f.write_str("External signature was invalid"),
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>>(
mut self,
mut rng: impl RngCore,
bundle_type: &BundleType,
) -> 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.
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(
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(
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
@ -776,7 +818,7 @@ pub mod testing {
Address, Note,
};
use super::Builder;
use super::{Builder, BundleType};
/// An intermediate type used for construction of arbitrary
/// bundle values. This type is required because of a limitation
@ -817,7 +859,7 @@ pub mod testing {
let pk = ProvingKey::build();
builder
.build(&mut self.rng)
.build(&mut self.rng, &BundleType::Transactional)
.unwrap()
.create_proof(&pk, &mut self.rng)
.unwrap()
@ -898,6 +940,7 @@ mod tests {
use super::Builder;
use crate::{
builder::BundleType,
bundle::{Authorized, Bundle, Flags},
circuit::ProvingKey,
constants::MERKLE_DEPTH_ORCHARD,
@ -927,7 +970,7 @@ mod tests {
assert_eq!(balance, -5000);
let bundle: Bundle<Authorized, i64> = builder
.build(&mut rng)
.build(&mut rng, &BundleType::Transactional)
.unwrap()
.create_proof(&pk, &mut rng)
.unwrap()

View File

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