Merge pull request #404 from nuttycom/builder_functions

Add a public bundle-builder function as an alternative to the mutable builder.
This commit is contained in:
str4d 2023-12-19 20:03:08 +00:00 committed by GitHub
commit 78f598616a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 326 additions and 178 deletions

View File

@ -6,6 +6,31 @@ 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::bundle`
- `orchard::builder::BundleType`
- `orchard::builder::OutputInfo`
- `orchard::bundle::Flags::{ENABLED, SPENDS_DISABLED, OUTPUTS_DISABLED}`
### Changed
- `orchard::builder::Builder::new` now takes the bundle type to be used
in bundle construction, instead of taking the flags and anchor separately.
- `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.
It also now returns a `Result<Option<Bundle<...>>, ...>` instead of a
`Result<Bundle<...>, ...>`.
- `orchard::builder::BuildError` has additional variants:
- `SpendsDisabled`
- `OutputsDisabled`
- `AnchorMismatch`
- `orchard::builder::SpendInfo::new` now returns a `Result<SpendInfo, SpendError>`
instead of an `Option`.
### Removed
- `orchard::bundle::Flags::from_parts`
## [0.6.0] - 2023-09-08 ## [0.6.0] - 2023-09-08
### Changed ### Changed
@ -22,8 +47,8 @@ and this project adheres to Rust's notion of
- `orchard::builder`: - `orchard::builder`:
- `{SpendInfo::new, InputView, OutputView}` - `{SpendInfo::new, InputView, OutputView}`
- `Builder::{spends, outputs}` - `Builder::{spends, outputs}`
- `SpendError` - `SpendError`
- `OutputError` - `OutputError`
### Changed ### Changed
- MSRV is now 1.60.0. - MSRV is now 1.60.0.

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},
@ -26,16 +26,16 @@ 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( let mut builder = Builder::new(BundleType::Transactional(
Flags::from_parts(true, true), Flags::ENABLED,
Anchor::from_bytes([0; 32]).unwrap(), Anchor::from_bytes([0; 32]).unwrap(),
); ));
for _ in 0..num_recipients { for _ in 0..num_recipients {
builder builder
.add_recipient(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).unwrap().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},
@ -45,19 +45,19 @@ fn bench_note_decryption(c: &mut Criterion) {
.collect(); .collect();
let bundle = { let bundle = {
let mut builder = Builder::new( let mut builder = Builder::new(BundleType::Transactional(
Flags::from_parts(true, true), Flags::ENABLED,
Anchor::from_bytes([0; 32]).unwrap(), 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
.add_recipient(None, recipient, NoteValue::from_raw(10), None) .add_output(None, recipient, NoteValue::from_raw(10), None)
.unwrap(); .unwrap();
builder builder
.add_recipient(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).unwrap().unwrap();
bundle bundle
.create_proof(&pk, rng) .create_proof(&pk, rng)
.unwrap() .unwrap()

View File

@ -27,9 +27,67 @@ use crate::{
const MIN_ACTIONS: usize = 2; const MIN_ACTIONS: usize = 2;
/// An enumeration of rules for 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(Flags, Anchor),
/// 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_requested_actions = core::cmp::max(num_spends, num_outputs);
match self {
BundleType::Transactional(flags, _) => {
if !flags.spends_enabled() && num_spends > 0 {
Err("Spends are disabled, so num_spends must be zero")
} else if !flags.outputs_enabled() && num_outputs > 0 {
Err("Outputs are disabled, so num_outputs must be zero")
} else {
Ok(core::cmp::max(num_requested_actions, MIN_ACTIONS))
}
}
BundleType::Coinbase => {
if num_spends > 0 {
Err("Coinbase bundles have spends disabled, so num_spends must be zero")
} else {
Ok(num_outputs)
}
}
}
}
/// Returns the set of flags and the anchor that will be used for bundle construction.
pub fn bundle_config(&self) -> (Flags, Anchor) {
match self {
BundleType::Transactional(flags, anchor) => (*flags, *anchor),
BundleType::Coinbase => (Flags::SPENDS_DISABLED, Anchor::empty_tree()),
}
}
}
/// 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 {
/// Spends are disabled for the provided bundle type.
SpendsDisabled,
/// Spends are disabled for the provided bundle type.
OutputsDisabled,
/// The anchor provided to this builder doesn't match the Merkle path used to add a spend.
AnchorMismatch,
/// A bundle could not be built because required signatures were missing. /// A bundle could not be built because required signatures were missing.
MissingSignatures, MissingSignatures,
/// An error occurred in the process of producing a proof for a bundle. /// An error occurred in the process of producing a proof for a bundle.
@ -42,6 +100,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,12 +113,32 @@ 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.")
}
SpendsDisabled => f.write_str("Spends are not enabled for the requested bundle type."),
OutputsDisabled => f.write_str("Spends are not enabled for the requested bundle type."),
AnchorMismatch => {
f.write_str("All spends must share the anchor requested for the transaction.")
}
} }
} }
} }
impl std::error::Error for BuildError {} impl std::error::Error for BuildError {}
impl From<halo2_proofs::plonk::Error> for BuildError {
fn from(e: halo2_proofs::plonk::Error) -> Self {
BuildError::Proof(e)
}
}
impl From<value::OverflowError> for BuildError {
fn from(e: value::OverflowError) -> Self {
BuildError::ValueSum(e)
}
}
/// An error type for adding a spend to the builder. /// An error type for adding a spend to the builder.
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum SpendError { pub enum SpendError {
@ -95,18 +175,6 @@ impl Display for OutputError {
impl std::error::Error for OutputError {} impl std::error::Error for OutputError {}
impl From<halo2_proofs::plonk::Error> for BuildError {
fn from(e: halo2_proofs::plonk::Error) -> Self {
BuildError::Proof(e)
}
}
impl From<value::OverflowError> for BuildError {
fn from(e: value::OverflowError) -> Self {
BuildError::ValueSum(e)
}
}
/// Information about a specific note to be spent in an [`Action`]. /// Information about a specific note to be spent in an [`Action`].
#[derive(Debug)] #[derive(Debug)]
pub struct SpendInfo { pub struct SpendInfo {
@ -155,31 +223,55 @@ impl SpendInfo {
merkle_path, merkle_path,
} }
} }
fn has_matching_anchor(&self, anchor: Anchor) -> bool {
if self.note.value() == NoteValue::zero() {
true
} else {
let cm = self.note.commitment();
let path_root = self.merkle_path.root(cm.into());
path_root == anchor
}
}
} }
/// Information about a specific recipient to receive funds in an [`Action`]. /// Information about a specific output to receive funds in an [`Action`].
#[derive(Debug)] #[derive(Debug)]
struct RecipientInfo { pub struct OutputInfo {
ovk: Option<OutgoingViewingKey>, ovk: Option<OutgoingViewingKey>,
recipient: Address, recipient: Address,
value: NoteValue, value: NoteValue,
memo: Option<[u8; 512]>, memo: [u8; 512],
} }
impl RecipientInfo { impl OutputInfo {
/// Constructs a new OutputInfo from its constituent parts.
pub fn new(
ovk: Option<OutgoingViewingKey>,
recipient: Address,
value: NoteValue,
memo: Option<[u8; 512]>,
) -> Self {
Self {
ovk,
recipient,
value,
memo: memo.unwrap_or_else(|| {
let mut memo = [0; 512];
memo[0] = 0xf6;
memo
}),
}
}
/// Defined in [Zcash Protocol Spec § 4.8.3: Dummy Notes (Orchard)][orcharddummynotes]. /// Defined in [Zcash Protocol Spec § 4.8.3: Dummy Notes (Orchard)][orcharddummynotes].
/// ///
/// [orcharddummynotes]: https://zips.z.cash/protocol/nu5.pdf#orcharddummynotes /// [orcharddummynotes]: https://zips.z.cash/protocol/nu5.pdf#orcharddummynotes
fn dummy(rng: &mut impl RngCore) -> Self { pub fn dummy(rng: &mut impl RngCore) -> Self {
let fvk: FullViewingKey = (&SpendingKey::random(rng)).into(); let fvk: FullViewingKey = (&SpendingKey::random(rng)).into();
let recipient = fvk.address_at(0u32, Scope::External); let recipient = fvk.address_at(0u32, Scope::External);
RecipientInfo { Self::new(None, recipient, NoteValue::zero(), None)
ovk: None,
recipient,
value: NoteValue::zero(),
memo: None,
}
} }
} }
@ -187,12 +279,12 @@ impl RecipientInfo {
#[derive(Debug)] #[derive(Debug)]
struct ActionInfo { struct ActionInfo {
spend: SpendInfo, spend: SpendInfo,
output: RecipientInfo, output: OutputInfo,
rcv: ValueCommitTrapdoor, rcv: ValueCommitTrapdoor,
} }
impl ActionInfo { impl ActionInfo {
fn new(spend: SpendInfo, output: RecipientInfo, rng: impl RngCore) -> Self { fn new(spend: SpendInfo, output: OutputInfo, rng: impl RngCore) -> Self {
ActionInfo { ActionInfo {
spend, spend,
output, output,
@ -223,15 +315,7 @@ impl ActionInfo {
let cm_new = note.commitment(); let cm_new = note.commitment();
let cmx = cm_new.into(); let cmx = cm_new.into();
let encryptor = OrchardNoteEncryption::new( let encryptor = OrchardNoteEncryption::new(self.output.ovk, note, self.output.memo);
self.output.ovk,
note,
self.output.memo.unwrap_or_else(|| {
let mut memo = [0; 512];
memo[0] = 0xf6;
memo
}),
);
let encrypted_note = TransmittedNoteCiphertext { let encrypted_note = TransmittedNoteCiphertext {
epk_bytes: encryptor.epk().to_bytes().0, epk_bytes: encryptor.epk().to_bytes().0,
@ -256,24 +340,27 @@ impl ActionInfo {
} }
} }
/// A builder that constructs a [`Bundle`] from a set of notes to be spent, and recipients /// Type alias for an in-progress bundle that has no proofs or signatures.
///
/// This is returned by [`Builder::build`].
pub type UnauthorizedBundle<V> = Bundle<InProgress<Unproven, Unauthorized>, V>;
/// A builder that constructs a [`Bundle`] from a set of notes to be spent, and outputs
/// to receive funds. /// to receive funds.
#[derive(Debug)] #[derive(Debug)]
pub struct Builder { pub struct Builder {
spends: Vec<SpendInfo>, spends: Vec<SpendInfo>,
recipients: Vec<RecipientInfo>, outputs: Vec<OutputInfo>,
flags: Flags, 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(flags: Flags, anchor: Anchor) -> Self { pub fn new(bundle_type: BundleType) -> Self {
Builder { Builder {
spends: vec![], spends: vec![],
recipients: vec![], outputs: vec![],
flags, bundle_type,
anchor,
} }
} }
@ -295,51 +382,38 @@ impl Builder {
note: Note, note: Note,
merkle_path: MerklePath, merkle_path: MerklePath,
) -> Result<(), SpendError> { ) -> Result<(), SpendError> {
if !self.flags.spends_enabled() { let (flags, anchor) = self.bundle_type.bundle_config();
if !flags.spends_enabled() {
return Err(SpendError::SpendsDisabled); return Err(SpendError::SpendsDisabled);
} }
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.
let cm = note.commitment(); if !spend.has_matching_anchor(anchor) {
let path_root = merkle_path.root(cm.into());
if path_root != self.anchor {
return Err(SpendError::AnchorMismatch); return Err(SpendError::AnchorMismatch);
} }
// Check if note is internal or external. self.spends.push(spend);
let scope = fvk
.scope_for_address(&note.recipient())
.ok_or(SpendError::FvkMismatch)?;
self.spends.push(SpendInfo {
dummy_sk: None,
fvk,
scope,
note,
merkle_path,
});
Ok(()) Ok(())
} }
/// Adds an address which will receive funds in this transaction. /// Adds an address which will receive funds in this transaction.
pub fn add_recipient( pub fn add_output(
&mut self, &mut self,
ovk: Option<OutgoingViewingKey>, ovk: Option<OutgoingViewingKey>,
recipient: Address, recipient: Address,
value: NoteValue, value: NoteValue,
memo: Option<[u8; 512]>, memo: Option<[u8; 512]>,
) -> Result<(), OutputError> { ) -> Result<(), OutputError> {
if !self.flags.outputs_enabled() { let (flags, _) = self.bundle_type.bundle_config();
if !flags.outputs_enabled() {
return Err(OutputError); return Err(OutputError);
} }
self.recipients.push(RecipientInfo { self.outputs
ovk, .push(OutputInfo::new(ovk, recipient, value, memo));
recipient,
value,
memo,
});
Ok(()) Ok(())
} }
@ -353,7 +427,7 @@ impl Builder {
/// Returns the action output components that will be produced by the /// Returns the action output components that will be produced by the
/// transaction being constructed /// transaction being constructed
pub fn outputs(&self) -> &Vec<impl OutputView> { pub fn outputs(&self) -> &Vec<impl OutputView> {
&self.recipients &self.outputs
} }
/// The net value of the bundle to be built. The value of all spends, /// The net value of the bundle to be built. The value of all spends,
@ -372,89 +446,115 @@ impl Builder {
.iter() .iter()
.map(|spend| spend.note.value() - NoteValue::zero()) .map(|spend| spend.note.value() - NoteValue::zero())
.chain( .chain(
self.recipients self.outputs
.iter() .iter()
.map(|recipient| NoteValue::zero() - recipient.value), .map(|output| NoteValue::zero() - output.value),
) )
.fold(Some(ValueSum::zero()), |acc, note_value| acc? + note_value) .fold(Some(ValueSum::zero()), |acc, note_value| acc? + note_value)
.ok_or(OverflowError)?; .ok_or(OverflowError)?;
i64::try_from(value_balance).and_then(|i| V::try_from(i).map_err(|_| value::OverflowError)) i64::try_from(value_balance).and_then(|i| V::try_from(i).map_err(|_| value::OverflowError))
} }
/// Builds a bundle containing the given spent notes and recipients. /// Builds a bundle containing the given spent notes and outputs.
/// ///
/// The returned bundle will have no proof or signatures; these can be applied with /// The returned bundle will have no proof or signatures; these can be applied with
/// [`Bundle::create_proof`] and [`Bundle::apply_signatures`] respectively. /// [`Bundle::create_proof`] and [`Bundle::apply_signatures`] respectively.
pub fn build<V: TryFrom<i64>>( pub fn build<V: TryFrom<i64>>(
mut self, self,
mut rng: impl RngCore, rng: impl RngCore,
) -> Result<Bundle<InProgress<Unproven, Unauthorized>, V>, BuildError> { ) -> Result<Option<UnauthorizedBundle<V>>, BuildError> {
// Pair up the spends and recipients, extending with dummy values as necessary. bundle(rng, self.spends, self.outputs, self.bundle_type)
let pre_actions: Vec<_> = { }
let num_spends = self.spends.len(); }
let num_recipients = self.recipients.len();
let num_actions = [num_spends, num_recipients, MIN_ACTIONS]
.iter()
.max()
.cloned()
.unwrap();
self.spends.extend( /// Builds a bundle containing the given spent notes and outputs.
iter::repeat_with(|| SpendInfo::dummy(&mut rng)).take(num_actions - num_spends), ///
); /// The returned bundle will have no proof or signatures; these can be applied with
self.recipients.extend( /// [`Bundle::create_proof`] and [`Bundle::apply_signatures`] respectively.
iter::repeat_with(|| RecipientInfo::dummy(&mut rng)) pub fn bundle<V: TryFrom<i64>>(
.take(num_actions - num_recipients), mut rng: impl RngCore,
); mut spends: Vec<SpendInfo>,
mut outputs: Vec<OutputInfo>,
bundle_type: BundleType,
) -> Result<Option<UnauthorizedBundle<V>>, BuildError> {
let (flags, anchor) = bundle_type.bundle_config();
// Shuffle the spends and recipients, so that learning the position of a let num_requested_spends = spends.len();
// specific spent note or output note doesn't reveal anything on its own about if !flags.spends_enabled() && num_requested_spends > 0 {
// the meaning of that note in the transaction context. return Err(BuildError::SpendsDisabled);
self.spends.shuffle(&mut rng); }
self.recipients.shuffle(&mut rng);
self.spends for spend in &spends {
.into_iter() if !spend.has_matching_anchor(anchor) {
.zip(self.recipients.into_iter()) return Err(BuildError::AnchorMismatch);
.map(|(spend, recipient)| ActionInfo::new(spend, recipient, &mut rng)) }
.collect() }
};
// Move some things out of self that we will need. let num_requested_outputs = outputs.len();
let flags = self.flags; if !flags.outputs_enabled() && num_requested_outputs > 0 {
let anchor = self.anchor; return Err(BuildError::OutputsDisabled);
}
// Determine the value balance for this bundle, ensuring it is valid. let num_actions = bundle_type
let value_balance = pre_actions .num_actions(num_requested_spends, num_requested_outputs)
.iter() .map_err(|_| BuildError::BundleTypeNotSatisfiable)?;
.fold(Some(ValueSum::zero()), |acc, action| {
acc? + action.value_sum()
})
.ok_or(OverflowError)?;
let result_value_balance: V = i64::try_from(value_balance) // Pair up the spends and outputs, extending with dummy values as necessary.
.map_err(BuildError::ValueSum) let pre_actions: Vec<_> = {
.and_then(|i| V::try_from(i).map_err(|_| BuildError::ValueSum(value::OverflowError)))?; spends.extend(
iter::repeat_with(|| SpendInfo::dummy(&mut rng))
.take(num_actions - num_requested_spends),
);
outputs.extend(
iter::repeat_with(|| OutputInfo::dummy(&mut rng))
.take(num_actions - num_requested_outputs),
);
// Compute the transaction binding signing key. // Shuffle the spends and outputs, so that learning the position of a
let bsk = pre_actions // specific spent note or output note doesn't reveal anything on its own about
.iter() // the meaning of that note in the transaction context.
.map(|a| &a.rcv) spends.shuffle(&mut rng);
.sum::<ValueCommitTrapdoor>() outputs.shuffle(&mut rng);
.into_bsk();
// Create the actions. spends
let (actions, circuits): (Vec<_>, Vec<_>) = .into_iter()
pre_actions.into_iter().map(|a| a.build(&mut rng)).unzip(); .zip(outputs.into_iter())
.map(|(spend, output)| ActionInfo::new(spend, output, &mut rng))
.collect()
};
// Verify that bsk and bvk are consistent. // Determine the value balance for this bundle, ensuring it is valid.
let bvk = (actions.iter().map(|a| a.cv_net()).sum::<ValueCommitment>() let value_balance = pre_actions
- ValueCommitment::derive(value_balance, ValueCommitTrapdoor::zero())) .iter()
.into_bvk(); .fold(Some(ValueSum::zero()), |acc, action| {
assert_eq!(redpallas::VerificationKey::from(&bsk), bvk); acc? + action.value_sum()
})
.ok_or(OverflowError)?;
Ok(Bundle::from_parts( let result_value_balance: V = i64::try_from(value_balance)
NonEmpty::from_vec(actions).unwrap(), .map_err(BuildError::ValueSum)
.and_then(|i| V::try_from(i).map_err(|_| BuildError::ValueSum(value::OverflowError)))?;
// Compute the transaction binding signing key.
let bsk = pre_actions
.iter()
.map(|a| &a.rcv)
.sum::<ValueCommitTrapdoor>()
.into_bsk();
// Create the actions.
let (actions, circuits): (Vec<_>, Vec<_>) =
pre_actions.into_iter().map(|a| a.build(&mut rng)).unzip();
// Verify that bsk and bvk are consistent.
let bvk = (actions.iter().map(|a| a.cv_net()).sum::<ValueCommitment>()
- ValueCommitment::derive(value_balance, ValueCommitTrapdoor::zero()))
.into_bvk();
assert_eq!(redpallas::VerificationKey::from(&bsk), bvk);
Ok(NonEmpty::from_vec(actions).map(|actions| {
Bundle::from_parts(
actions,
flags, flags,
result_value_balance, result_value_balance,
anchor, anchor,
@ -462,8 +562,8 @@ impl Builder {
proof: Unproven { circuits }, proof: Unproven { circuits },
sigs: Unauthorized { bsk }, sigs: Unauthorized { bsk },
}, },
)) )
} }))
} }
/// Marker trait representing bundle signatures in the process of being created. /// Marker trait representing bundle signatures in the process of being created.
@ -749,7 +849,7 @@ pub trait OutputView {
fn value<V: From<u64>>(&self) -> V; fn value<V: From<u64>>(&self) -> V;
} }
impl OutputView for RecipientInfo { impl OutputView for OutputInfo {
fn value<V: From<u64>>(&self) -> V { fn value<V: From<u64>>(&self) -> V {
V::from(self.value.inner()) V::from(self.value.inner())
} }
@ -777,7 +877,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
@ -793,7 +893,7 @@ pub mod testing {
sk: SpendingKey, sk: SpendingKey,
anchor: Anchor, anchor: Anchor,
notes: Vec<(Note, MerklePath)>, notes: Vec<(Note, MerklePath)>,
recipient_amounts: Vec<(Address, NoteValue)>, output_amounts: Vec<(Address, NoteValue)>,
} }
impl<R: RngCore + CryptoRng> ArbitraryBundleInputs<R> { impl<R: RngCore + CryptoRng> ArbitraryBundleInputs<R> {
@ -801,18 +901,18 @@ pub mod testing {
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 flags = Flags::from_parts(true, true);
let mut builder = Builder::new(flags, 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();
} }
for (addr, value) in self.recipient_amounts.into_iter() { for (addr, value) in self.output_amounts.into_iter() {
let scope = fvk.scope_for_address(&addr).unwrap(); let scope = fvk.scope_for_address(&addr).unwrap();
let ovk = fvk.to_ovk(scope); let ovk = fvk.to_ovk(scope);
builder builder
.add_recipient(Some(ovk.clone()), addr, value, None) .add_output(Some(ovk.clone()), addr, value, None)
.unwrap(); .unwrap();
} }
@ -820,6 +920,7 @@ pub mod testing {
builder builder
.build(&mut self.rng) .build(&mut self.rng)
.unwrap() .unwrap()
.unwrap()
.create_proof(&pk, &mut self.rng) .create_proof(&pk, &mut self.rng)
.unwrap() .unwrap()
.prepare(&mut self.rng, [0; 32]) .prepare(&mut self.rng, [0; 32])
@ -834,7 +935,7 @@ pub mod testing {
fn arb_bundle_inputs(sk: SpendingKey) fn arb_bundle_inputs(sk: SpendingKey)
( (
n_notes in 1usize..30, n_notes in 1usize..30,
n_recipients in 1..30, n_outputs in 1..30,
) )
( (
@ -843,12 +944,12 @@ pub mod testing {
arb_positive_note_value(MAX_NOTE_VALUE / n_notes as u64).prop_flat_map(arb_note), arb_positive_note_value(MAX_NOTE_VALUE / n_notes as u64).prop_flat_map(arb_note),
n_notes n_notes
), ),
recipient_amounts in vec( output_amounts in vec(
arb_address().prop_flat_map(move |a| { arb_address().prop_flat_map(move |a| {
arb_positive_note_value(MAX_NOTE_VALUE / n_recipients as u64) arb_positive_note_value(MAX_NOTE_VALUE / n_outputs as u64)
.prop_map(move |v| (a, v)) .prop_map(move |v| (a, v))
}), }),
n_recipients as usize n_outputs as usize
), ),
rng_seed in prop::array::uniform32(prop::num::u8::ANY) rng_seed in prop::array::uniform32(prop::num::u8::ANY)
) -> ArbitraryBundleInputs<StdRng> { ) -> ArbitraryBundleInputs<StdRng> {
@ -873,7 +974,7 @@ pub mod testing {
sk, sk,
anchor: frontier.root().into(), anchor: frontier.root().into(),
notes: notes_and_auth_paths, notes: notes_and_auth_paths,
recipient_amounts output_amounts
} }
} }
} }
@ -899,6 +1000,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,
@ -916,13 +1018,13 @@ 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( let mut builder = Builder::new(BundleType::Transactional(
Flags::from_parts(true, true), Flags::from_parts(true, true),
EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(), EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(),
); ));
builder builder
.add_recipient(None, recipient, NoteValue::from_raw(5000), None) .add_output(None, recipient, NoteValue::from_raw(5000), None)
.unwrap(); .unwrap();
let balance: i64 = builder.value_balance().unwrap(); let balance: i64 = builder.value_balance().unwrap();
assert_eq!(balance, -5000); assert_eq!(balance, -5000);
@ -930,6 +1032,7 @@ mod tests {
let bundle: Bundle<Authorized, i64> = builder let bundle: Bundle<Authorized, i64> = builder
.build(&mut rng) .build(&mut rng)
.unwrap() .unwrap()
.unwrap()
.create_proof(&pk, &mut rng) .create_proof(&pk, &mut rng)
.unwrap() .unwrap()
.prepare(rng, [0; 32]) .prepare(rng, [0; 32])

View File

@ -42,7 +42,7 @@ impl<T> Action<T> {
} }
/// Orchard-specific flags. /// Orchard-specific flags.
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Flags { pub struct Flags {
/// Flag denoting whether Orchard spends are enabled in the transaction. /// Flag denoting whether Orchard spends are enabled in the transaction.
/// ///
@ -64,13 +64,31 @@ 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 fn from_parts(spends_enabled: bool, outputs_enabled: bool) -> Self { pub(crate) fn from_parts(spends_enabled: bool, outputs_enabled: bool) -> Self {
Flags { Flags {
spends_enabled, spends_enabled,
outputs_enabled, outputs_enabled,
} }
} }
/// The flag set with both spends and outputs enabled.
pub const ENABLED: Flags = Flags {
spends_enabled: true,
outputs_enabled: true,
};
/// The flag set with spends disabled.
pub const SPENDS_DISABLED: Flags = Flags {
spends_enabled: false,
outputs_enabled: true,
};
/// The flag set with outputs disabled.
pub const OUTPUTS_DISABLED: Flags = Flags {
spends_enabled: true,
outputs_enabled: false,
};
/// Flag denoting whether Orchard spends are enabled in the transaction. /// Flag denoting whether Orchard spends are enabled in the transaction.
/// ///
/// If `false`, spent notes within [`Action`]s in the transaction's [`Bundle`] are /// If `false`, spent notes within [`Action`]s in the transaction's [`Bundle`] are
@ -113,10 +131,10 @@ impl Flags {
pub fn from_byte(value: u8) -> Option<Self> { pub fn from_byte(value: u8) -> Option<Self> {
// https://p.z.cash/TCR:bad-txns-v5-reserved-bits-nonzero // https://p.z.cash/TCR:bad-txns-v5-reserved-bits-nonzero
if value & FLAGS_EXPECTED_UNSET == 0 { if value & FLAGS_EXPECTED_UNSET == 0 {
Some(Self::from_parts( Some(Self {
value & FLAG_SPENDS_ENABLED != 0, spends_enabled: value & FLAG_SPENDS_ENABLED != 0,
value & FLAG_OUTPUTS_ENABLED != 0, outputs_enabled: value & FLAG_OUTPUTS_ENABLED != 0,
)) })
} else { } else {
None None
} }

View File

@ -58,12 +58,14 @@ impl From<MerkleHashOrchard> for Anchor {
} }
impl Anchor { impl Anchor {
pub(crate) fn empty_tree() -> Anchor {
Anchor(MerkleHashOrchard::empty_root(Level::from(MERKLE_DEPTH_ORCHARD as u8)).0)
}
pub(crate) fn inner(&self) -> pallas::Base { pub(crate) fn inner(&self) -> pallas::Base {
self.0 self.0
} }
}
impl Anchor {
/// Parses an Orchard anchor from a byte encoding. /// Parses an Orchard anchor from a byte encoding.
pub fn from_bytes(bytes: [u8; 32]) -> CtOption<Anchor> { pub fn from_bytes(bytes: [u8; 32]) -> CtOption<Anchor> {
pallas::Base::from_repr(bytes).map(Anchor) pallas::Base::from_repr(bytes).map(Anchor)

View File

@ -16,7 +16,7 @@
//! - Define your `valueBalanceOrchard` type to enforce your valid value range. This can //! - Define your `valueBalanceOrchard` type to enforce your valid value range. This can
//! be checked in its `TryFrom<i64>` implementation. //! be checked in its `TryFrom<i64>` implementation.
//! - Define your own "amount" type for note values, and convert it to `NoteValue` prior //! - Define your own "amount" type for note values, and convert it to `NoteValue` prior
//! to calling [`Builder::add_recipient`]. //! to calling [`Builder::add_output`].
//! //!
//! Inside the circuit, note values are constrained to be unsigned 64-bit integers. //! Inside the circuit, note values are constrained to be unsigned 64-bit integers.
//! //!
@ -34,7 +34,7 @@
//! [`Bundle`]: crate::bundle::Bundle //! [`Bundle`]: crate::bundle::Bundle
//! [`Bundle::value_balance`]: crate::bundle::Bundle::value_balance //! [`Bundle::value_balance`]: crate::bundle::Bundle::value_balance
//! [`Builder::value_balance`]: crate::builder::Builder::value_balance //! [`Builder::value_balance`]: crate::builder::Builder::value_balance
//! [`Builder::add_recipient`]: crate::builder::Builder::add_recipient //! [`Builder::add_output`]: crate::builder::Builder::add_output
//! [Rust documentation]: https://doc.rust-lang.org/stable/std/primitive.i64.html //! [Rust documentation]: https://doc.rust-lang.org/stable/std/primitive.i64.html
use core::fmt::{self, Debug}; use core::fmt::{self, Debug};
@ -83,7 +83,7 @@ impl fmt::Display for OverflowError {
impl std::error::Error for OverflowError {} impl std::error::Error for OverflowError {}
/// The non-negative value of an individual Orchard note. /// The non-negative value of an individual Orchard note.
#[derive(Clone, Copy, Debug, Default)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct NoteValue(u64); pub struct NoteValue(u64);
impl NoteValue { impl NoteValue {

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},
@ -42,12 +42,12 @@ 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(Flags::from_parts(false, true), anchor); let mut builder = Builder::new(BundleType::Transactional(Flags::SPENDS_DISABLED, anchor));
assert_eq!( assert_eq!(
builder.add_recipient(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).unwrap().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()
@ -83,13 +83,13 @@ 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(Flags::from_parts(true, true), anchor); let mut builder = Builder::new(BundleType::Transactional(Flags::ENABLED, 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_recipient(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).unwrap().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