mirror of https://github.com/zcash/orchard.git
Modify `BundleType` to exclude the anchor & allow no bundle to be produced.
This adds a flag to `BundleType` that, when set, requires a dummy-only bundle to be produced even if no spends or outputs are added to the builder, and when unset results in standard padding.
This commit is contained in:
parent
78f598616a
commit
3845686a6e
|
@ -8,7 +8,6 @@ use pprof::criterion::{Output, PProfProfiler};
|
||||||
|
|
||||||
use orchard::{
|
use orchard::{
|
||||||
builder::{Builder, BundleType},
|
builder::{Builder, BundleType},
|
||||||
bundle::Flags,
|
|
||||||
circuit::{ProvingKey, VerifyingKey},
|
circuit::{ProvingKey, VerifyingKey},
|
||||||
keys::{FullViewingKey, Scope, SpendingKey},
|
keys::{FullViewingKey, Scope, SpendingKey},
|
||||||
value::NoteValue,
|
value::NoteValue,
|
||||||
|
@ -26,10 +25,7 @@ fn criterion_benchmark(c: &mut Criterion) {
|
||||||
let pk = ProvingKey::build();
|
let pk = ProvingKey::build();
|
||||||
|
|
||||||
let create_bundle = |num_recipients| {
|
let create_bundle = |num_recipients| {
|
||||||
let mut builder = Builder::new(BundleType::Transactional(
|
let mut builder = Builder::new(BundleType::DEFAULT, Anchor::from_bytes([0; 32]).unwrap());
|
||||||
Flags::ENABLED,
|
|
||||||
Anchor::from_bytes([0; 32]).unwrap(),
|
|
||||||
));
|
|
||||||
for _ in 0..num_recipients {
|
for _ in 0..num_recipients {
|
||||||
builder
|
builder
|
||||||
.add_output(None, recipient, NoteValue::from_raw(10), None)
|
.add_output(None, recipient, NoteValue::from_raw(10), None)
|
||||||
|
|
|
@ -1,7 +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, BundleType},
|
builder::{Builder, BundleType},
|
||||||
bundle::Flags,
|
|
||||||
circuit::ProvingKey,
|
circuit::ProvingKey,
|
||||||
keys::{FullViewingKey, PreparedIncomingViewingKey, Scope, SpendingKey},
|
keys::{FullViewingKey, PreparedIncomingViewingKey, Scope, SpendingKey},
|
||||||
note_encryption::{CompactAction, OrchardDomain},
|
note_encryption::{CompactAction, OrchardDomain},
|
||||||
|
@ -45,10 +44,7 @@ fn bench_note_decryption(c: &mut Criterion) {
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let bundle = {
|
let bundle = {
|
||||||
let mut builder = Builder::new(BundleType::Transactional(
|
let mut builder = Builder::new(BundleType::DEFAULT, Anchor::from_bytes([0; 32]).unwrap());
|
||||||
Flags::ENABLED,
|
|
||||||
Anchor::from_bytes([0; 32]).unwrap(),
|
|
||||||
));
|
|
||||||
// The builder pads to two actions, and shuffles their order. Add two recipients
|
// The builder pads to two actions, and shuffles their order. Add two recipients
|
||||||
// so the first action is always decryptable.
|
// so the first action is always decryptable.
|
||||||
builder
|
builder
|
||||||
|
|
|
@ -32,12 +32,33 @@ const MIN_ACTIONS: usize = 2;
|
||||||
pub enum BundleType {
|
pub enum BundleType {
|
||||||
/// A transactional bundle will be padded if necessary to contain at least 2 actions,
|
/// A transactional bundle will be padded if necessary to contain at least 2 actions,
|
||||||
/// irrespective of whether any genuine actions are required.
|
/// irrespective of whether any genuine actions are required.
|
||||||
Transactional(Flags, Anchor),
|
Transactional {
|
||||||
|
/// The flags that control whether spends and/or outputs are enabled for the bundle.
|
||||||
|
flags: Flags,
|
||||||
|
/// A flag that, when set to `true`, indicates that a bundle should be produced even if no
|
||||||
|
/// spends or outputs have been added to the bundle; in such a circumstance, all of the
|
||||||
|
/// actions in the resulting bundle will be dummies.
|
||||||
|
bundle_required: bool,
|
||||||
|
},
|
||||||
/// A coinbase bundle is required to have no non-dummy spends. No padding is performed.
|
/// A coinbase bundle is required to have no non-dummy spends. No padding is performed.
|
||||||
Coinbase,
|
Coinbase,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BundleType {
|
impl BundleType {
|
||||||
|
/// The default bundle type has all flags enabled, and does not require a bundle to be produced
|
||||||
|
/// if no spends or outputs have been added to the bundle.
|
||||||
|
pub const DEFAULT: BundleType = BundleType::Transactional {
|
||||||
|
flags: Flags::ENABLED,
|
||||||
|
bundle_required: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The DISABLED bundle type does not permit any bundle to be produced, and when used in the
|
||||||
|
/// builder will prevent any spends or outputs from being added.
|
||||||
|
pub const DISABLED: BundleType = BundleType::Transactional {
|
||||||
|
flags: Flags::from_parts(false, false),
|
||||||
|
bundle_required: false,
|
||||||
|
};
|
||||||
|
|
||||||
/// Returns the number of logical actions that builder will produce in constructing a bundle
|
/// 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.
|
/// of this type, given the specified numbers of spends and outputs.
|
||||||
///
|
///
|
||||||
|
@ -51,13 +72,20 @@ impl BundleType {
|
||||||
let num_requested_actions = core::cmp::max(num_spends, num_outputs);
|
let num_requested_actions = core::cmp::max(num_spends, num_outputs);
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
BundleType::Transactional(flags, _) => {
|
BundleType::Transactional {
|
||||||
|
flags,
|
||||||
|
bundle_required,
|
||||||
|
} => {
|
||||||
if !flags.spends_enabled() && num_spends > 0 {
|
if !flags.spends_enabled() && num_spends > 0 {
|
||||||
Err("Spends are disabled, so num_spends must be zero")
|
Err("Spends are disabled, so num_spends must be zero")
|
||||||
} else if !flags.outputs_enabled() && num_outputs > 0 {
|
} else if !flags.outputs_enabled() && num_outputs > 0 {
|
||||||
Err("Outputs are disabled, so num_outputs must be zero")
|
Err("Outputs are disabled, so num_outputs must be zero")
|
||||||
} else {
|
} else {
|
||||||
Ok(core::cmp::max(num_requested_actions, MIN_ACTIONS))
|
Ok(if *bundle_required || num_requested_actions > 0 {
|
||||||
|
core::cmp::max(num_requested_actions, MIN_ACTIONS)
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BundleType::Coinbase => {
|
BundleType::Coinbase => {
|
||||||
|
@ -71,10 +99,10 @@ impl BundleType {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the set of flags and the anchor that will be used for bundle construction.
|
/// Returns the set of flags and the anchor that will be used for bundle construction.
|
||||||
pub fn bundle_config(&self) -> (Flags, Anchor) {
|
pub fn flags(&self) -> Flags {
|
||||||
match self {
|
match self {
|
||||||
BundleType::Transactional(flags, anchor) => (*flags, *anchor),
|
BundleType::Transactional { flags, .. } => *flags,
|
||||||
BundleType::Coinbase => (Flags::SPENDS_DISABLED, Anchor::empty_tree()),
|
BundleType::Coinbase => Flags::SPENDS_DISABLED,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -224,13 +252,13 @@ impl SpendInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_matching_anchor(&self, anchor: Anchor) -> bool {
|
fn has_matching_anchor(&self, anchor: &Anchor) -> bool {
|
||||||
if self.note.value() == NoteValue::zero() {
|
if self.note.value() == NoteValue::zero() {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
let cm = self.note.commitment();
|
let cm = self.note.commitment();
|
||||||
let path_root = self.merkle_path.root(cm.into());
|
let path_root = self.merkle_path.root(cm.into());
|
||||||
path_root == anchor
|
&path_root == anchor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -352,15 +380,17 @@ pub struct Builder {
|
||||||
spends: Vec<SpendInfo>,
|
spends: Vec<SpendInfo>,
|
||||||
outputs: Vec<OutputInfo>,
|
outputs: Vec<OutputInfo>,
|
||||||
bundle_type: BundleType,
|
bundle_type: BundleType,
|
||||||
|
anchor: Anchor,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Builder {
|
impl Builder {
|
||||||
/// Constructs a new empty builder for an Orchard bundle.
|
/// Constructs a new empty builder for an Orchard bundle.
|
||||||
pub fn new(bundle_type: BundleType) -> Self {
|
pub fn new(bundle_type: BundleType, anchor: Anchor) -> Self {
|
||||||
Builder {
|
Builder {
|
||||||
spends: vec![],
|
spends: vec![],
|
||||||
outputs: vec![],
|
outputs: vec![],
|
||||||
bundle_type,
|
bundle_type,
|
||||||
|
anchor,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -382,7 +412,7 @@ impl Builder {
|
||||||
note: Note,
|
note: Note,
|
||||||
merkle_path: MerklePath,
|
merkle_path: MerklePath,
|
||||||
) -> Result<(), SpendError> {
|
) -> Result<(), SpendError> {
|
||||||
let (flags, anchor) = self.bundle_type.bundle_config();
|
let flags = self.bundle_type.flags();
|
||||||
if !flags.spends_enabled() {
|
if !flags.spends_enabled() {
|
||||||
return Err(SpendError::SpendsDisabled);
|
return Err(SpendError::SpendsDisabled);
|
||||||
}
|
}
|
||||||
|
@ -390,7 +420,7 @@ impl Builder {
|
||||||
let spend = SpendInfo::new(fvk, note, merkle_path).ok_or(SpendError::FvkMismatch)?;
|
let spend = SpendInfo::new(fvk, note, merkle_path).ok_or(SpendError::FvkMismatch)?;
|
||||||
|
|
||||||
// Consistency check: all anchors must be equal.
|
// Consistency check: all anchors must be equal.
|
||||||
if !spend.has_matching_anchor(anchor) {
|
if !spend.has_matching_anchor(&self.anchor) {
|
||||||
return Err(SpendError::AnchorMismatch);
|
return Err(SpendError::AnchorMismatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -407,7 +437,7 @@ impl Builder {
|
||||||
value: NoteValue,
|
value: NoteValue,
|
||||||
memo: Option<[u8; 512]>,
|
memo: Option<[u8; 512]>,
|
||||||
) -> Result<(), OutputError> {
|
) -> Result<(), OutputError> {
|
||||||
let (flags, _) = self.bundle_type.bundle_config();
|
let flags = self.bundle_type.flags();
|
||||||
if !flags.outputs_enabled() {
|
if !flags.outputs_enabled() {
|
||||||
return Err(OutputError);
|
return Err(OutputError);
|
||||||
}
|
}
|
||||||
|
@ -463,7 +493,13 @@ impl Builder {
|
||||||
self,
|
self,
|
||||||
rng: impl RngCore,
|
rng: impl RngCore,
|
||||||
) -> Result<Option<UnauthorizedBundle<V>>, BuildError> {
|
) -> Result<Option<UnauthorizedBundle<V>>, BuildError> {
|
||||||
bundle(rng, self.spends, self.outputs, self.bundle_type)
|
bundle(
|
||||||
|
rng,
|
||||||
|
self.anchor,
|
||||||
|
self.bundle_type,
|
||||||
|
self.spends,
|
||||||
|
self.outputs,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -473,11 +509,12 @@ impl Builder {
|
||||||
/// [`Bundle::create_proof`] and [`Bundle::apply_signatures`] respectively.
|
/// [`Bundle::create_proof`] and [`Bundle::apply_signatures`] respectively.
|
||||||
pub fn bundle<V: TryFrom<i64>>(
|
pub fn bundle<V: TryFrom<i64>>(
|
||||||
mut rng: impl RngCore,
|
mut rng: impl RngCore,
|
||||||
|
anchor: Anchor,
|
||||||
|
bundle_type: BundleType,
|
||||||
mut spends: Vec<SpendInfo>,
|
mut spends: Vec<SpendInfo>,
|
||||||
mut outputs: Vec<OutputInfo>,
|
mut outputs: Vec<OutputInfo>,
|
||||||
bundle_type: BundleType,
|
|
||||||
) -> Result<Option<UnauthorizedBundle<V>>, BuildError> {
|
) -> Result<Option<UnauthorizedBundle<V>>, BuildError> {
|
||||||
let (flags, anchor) = bundle_type.bundle_config();
|
let flags = bundle_type.flags();
|
||||||
|
|
||||||
let num_requested_spends = spends.len();
|
let num_requested_spends = spends.len();
|
||||||
if !flags.spends_enabled() && num_requested_spends > 0 {
|
if !flags.spends_enabled() && num_requested_spends > 0 {
|
||||||
|
@ -485,7 +522,7 @@ pub fn bundle<V: TryFrom<i64>>(
|
||||||
}
|
}
|
||||||
|
|
||||||
for spend in &spends {
|
for spend in &spends {
|
||||||
if !spend.has_matching_anchor(anchor) {
|
if !spend.has_matching_anchor(&anchor) {
|
||||||
return Err(BuildError::AnchorMismatch);
|
return Err(BuildError::AnchorMismatch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -868,7 +905,7 @@ pub mod testing {
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
address::testing::arb_address,
|
address::testing::arb_address,
|
||||||
bundle::{Authorized, Bundle, Flags},
|
bundle::{Authorized, Bundle},
|
||||||
circuit::ProvingKey,
|
circuit::ProvingKey,
|
||||||
keys::{testing::arb_spending_key, FullViewingKey, SpendAuthorizingKey, SpendingKey},
|
keys::{testing::arb_spending_key, FullViewingKey, SpendAuthorizingKey, SpendingKey},
|
||||||
note::testing::arb_note,
|
note::testing::arb_note,
|
||||||
|
@ -900,8 +937,7 @@ pub mod testing {
|
||||||
/// Create a bundle from the set of arbitrary bundle inputs.
|
/// Create a bundle from the set of arbitrary bundle inputs.
|
||||||
fn into_bundle<V: TryFrom<i64>>(mut self) -> Bundle<Authorized, V> {
|
fn into_bundle<V: TryFrom<i64>>(mut self) -> Bundle<Authorized, V> {
|
||||||
let fvk = FullViewingKey::from(&self.sk);
|
let fvk = FullViewingKey::from(&self.sk);
|
||||||
let flags = Flags::from_parts(true, true);
|
let mut builder = Builder::new(BundleType::DEFAULT, self.anchor);
|
||||||
let mut builder = Builder::new(BundleType::Transactional(flags, self.anchor));
|
|
||||||
|
|
||||||
for (note, path) in self.notes.into_iter() {
|
for (note, path) in self.notes.into_iter() {
|
||||||
builder.add_spend(fvk.clone(), note, path).unwrap();
|
builder.add_spend(fvk.clone(), note, path).unwrap();
|
||||||
|
@ -1001,7 +1037,7 @@ mod tests {
|
||||||
use super::Builder;
|
use super::Builder;
|
||||||
use crate::{
|
use crate::{
|
||||||
builder::BundleType,
|
builder::BundleType,
|
||||||
bundle::{Authorized, Bundle, Flags},
|
bundle::{Authorized, Bundle},
|
||||||
circuit::ProvingKey,
|
circuit::ProvingKey,
|
||||||
constants::MERKLE_DEPTH_ORCHARD,
|
constants::MERKLE_DEPTH_ORCHARD,
|
||||||
keys::{FullViewingKey, Scope, SpendingKey},
|
keys::{FullViewingKey, Scope, SpendingKey},
|
||||||
|
@ -1018,10 +1054,10 @@ mod tests {
|
||||||
let fvk = FullViewingKey::from(&sk);
|
let fvk = FullViewingKey::from(&sk);
|
||||||
let recipient = fvk.address_at(0u32, Scope::External);
|
let recipient = fvk.address_at(0u32, Scope::External);
|
||||||
|
|
||||||
let mut builder = Builder::new(BundleType::Transactional(
|
let mut builder = Builder::new(
|
||||||
Flags::from_parts(true, true),
|
BundleType::DEFAULT,
|
||||||
EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(),
|
EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(),
|
||||||
));
|
);
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.add_output(None, recipient, NoteValue::from_raw(5000), None)
|
.add_output(None, recipient, NoteValue::from_raw(5000), None)
|
||||||
|
|
|
@ -64,7 +64,7 @@ const FLAGS_EXPECTED_UNSET: u8 = !(FLAG_SPENDS_ENABLED | FLAG_OUTPUTS_ENABLED);
|
||||||
|
|
||||||
impl Flags {
|
impl Flags {
|
||||||
/// Construct a set of flags from its constituent parts
|
/// Construct a set of flags from its constituent parts
|
||||||
pub(crate) fn from_parts(spends_enabled: bool, outputs_enabled: bool) -> Self {
|
pub(crate) const fn from_parts(spends_enabled: bool, outputs_enabled: bool) -> Self {
|
||||||
Flags {
|
Flags {
|
||||||
spends_enabled,
|
spends_enabled,
|
||||||
outputs_enabled,
|
outputs_enabled,
|
||||||
|
|
|
@ -42,7 +42,13 @@ fn bundle_chain() {
|
||||||
// Use the empty tree.
|
// Use the empty tree.
|
||||||
let anchor = MerkleHashOrchard::empty_root(32.into()).into();
|
let anchor = MerkleHashOrchard::empty_root(32.into()).into();
|
||||||
|
|
||||||
let mut builder = Builder::new(BundleType::Transactional(Flags::SPENDS_DISABLED, anchor));
|
let mut builder = Builder::new(
|
||||||
|
BundleType::Transactional {
|
||||||
|
flags: Flags::SPENDS_DISABLED,
|
||||||
|
bundle_required: false,
|
||||||
|
},
|
||||||
|
anchor,
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
builder.add_output(None, recipient, NoteValue::from_raw(5000), None),
|
builder.add_output(None, recipient, NoteValue::from_raw(5000), None),
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -83,7 +89,7 @@ fn bundle_chain() {
|
||||||
let anchor = root.into();
|
let anchor = root.into();
|
||||||
assert_eq!(anchor, merkle_path.root(cmx));
|
assert_eq!(anchor, merkle_path.root(cmx));
|
||||||
|
|
||||||
let mut builder = Builder::new(BundleType::Transactional(Flags::ENABLED, anchor));
|
let mut builder = Builder::new(BundleType::DEFAULT, anchor);
|
||||||
assert_eq!(builder.add_spend(fvk, note, merkle_path), Ok(()));
|
assert_eq!(builder.add_spend(fvk, note, merkle_path), Ok(()));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
builder.add_output(None, recipient, NoteValue::from_raw(5000), None),
|
builder.add_output(None, recipient, NoteValue::from_raw(5000), None),
|
||||||
|
|
Loading…
Reference in New Issue