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).
## [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
### Changed
@ -22,8 +47,8 @@ and this project adheres to Rust's notion of
- `orchard::builder`:
- `{SpendInfo::new, InputView, OutputView}`
- `Builder::{spends, outputs}`
- `SpendError`
- `OutputError`
- `SpendError`
- `OutputError`
### Changed
- MSRV is now 1.60.0.

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},
@ -26,16 +26,16 @@ fn criterion_benchmark(c: &mut Criterion) {
let pk = ProvingKey::build();
let create_bundle = |num_recipients| {
let mut builder = Builder::new(
Flags::from_parts(true, true),
let mut builder = Builder::new(BundleType::Transactional(
Flags::ENABLED,
Anchor::from_bytes([0; 32]).unwrap(),
);
));
for _ in 0..num_recipients {
builder
.add_recipient(None, recipient, NoteValue::from_raw(10), None)
.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).unwrap().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},
@ -45,19 +45,19 @@ fn bench_note_decryption(c: &mut Criterion) {
.collect();
let bundle = {
let mut builder = Builder::new(
Flags::from_parts(true, true),
let mut builder = Builder::new(BundleType::Transactional(
Flags::ENABLED,
Anchor::from_bytes([0; 32]).unwrap(),
);
));
// The builder pads to two actions, and shuffles their order. Add two recipients
// so the first action is always decryptable.
builder
.add_recipient(None, recipient, NoteValue::from_raw(10), None)
.add_output(None, recipient, NoteValue::from_raw(10), None)
.unwrap();
builder
.add_recipient(None, recipient, NoteValue::from_raw(10), None)
.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).unwrap().unwrap();
bundle
.create_proof(&pk, rng)
.unwrap()

View File

@ -27,9 +27,67 @@ use crate::{
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.
#[derive(Debug)]
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.
MissingSignatures,
/// 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`
/// 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,12 +113,32 @@ 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.")
}
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 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.
#[derive(Debug, PartialEq, Eq)]
pub enum SpendError {
@ -95,18 +175,6 @@ impl Display 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`].
#[derive(Debug)]
pub struct SpendInfo {
@ -155,31 +223,55 @@ impl SpendInfo {
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)]
struct RecipientInfo {
pub struct OutputInfo {
ovk: Option<OutgoingViewingKey>,
recipient: Address,
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].
///
/// [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 recipient = fvk.address_at(0u32, Scope::External);
RecipientInfo {
ovk: None,
recipient,
value: NoteValue::zero(),
memo: None,
}
Self::new(None, recipient, NoteValue::zero(), None)
}
}
@ -187,12 +279,12 @@ impl RecipientInfo {
#[derive(Debug)]
struct ActionInfo {
spend: SpendInfo,
output: RecipientInfo,
output: OutputInfo,
rcv: ValueCommitTrapdoor,
}
impl ActionInfo {
fn new(spend: SpendInfo, output: RecipientInfo, rng: impl RngCore) -> Self {
fn new(spend: SpendInfo, output: OutputInfo, rng: impl RngCore) -> Self {
ActionInfo {
spend,
output,
@ -223,15 +315,7 @@ impl ActionInfo {
let cm_new = note.commitment();
let cmx = cm_new.into();
let encryptor = OrchardNoteEncryption::new(
self.output.ovk,
note,
self.output.memo.unwrap_or_else(|| {
let mut memo = [0; 512];
memo[0] = 0xf6;
memo
}),
);
let encryptor = OrchardNoteEncryption::new(self.output.ovk, note, self.output.memo);
let encrypted_note = TransmittedNoteCiphertext {
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.
#[derive(Debug)]
pub struct Builder {
spends: Vec<SpendInfo>,
recipients: Vec<RecipientInfo>,
flags: Flags,
anchor: Anchor,
outputs: Vec<OutputInfo>,
bundle_type: BundleType,
}
impl Builder {
/// 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 {
spends: vec![],
recipients: vec![],
flags,
anchor,
outputs: vec![],
bundle_type,
}
}
@ -295,51 +382,38 @@ impl Builder {
note: Note,
merkle_path: MerklePath,
) -> Result<(), SpendError> {
if !self.flags.spends_enabled() {
let (flags, anchor) = self.bundle_type.bundle_config();
if !flags.spends_enabled() {
return Err(SpendError::SpendsDisabled);
}
let spend = SpendInfo::new(fvk, note, merkle_path).ok_or(SpendError::FvkMismatch)?;
// Consistency check: all anchors must be equal.
let cm = note.commitment();
let path_root = merkle_path.root(cm.into());
if path_root != self.anchor {
if !spend.has_matching_anchor(anchor) {
return Err(SpendError::AnchorMismatch);
}
// Check if note is internal or external.
let scope = fvk
.scope_for_address(&note.recipient())
.ok_or(SpendError::FvkMismatch)?;
self.spends.push(SpendInfo {
dummy_sk: None,
fvk,
scope,
note,
merkle_path,
});
self.spends.push(spend);
Ok(())
}
/// Adds an address which will receive funds in this transaction.
pub fn add_recipient(
pub fn add_output(
&mut self,
ovk: Option<OutgoingViewingKey>,
recipient: Address,
value: NoteValue,
memo: Option<[u8; 512]>,
) -> Result<(), OutputError> {
if !self.flags.outputs_enabled() {
let (flags, _) = self.bundle_type.bundle_config();
if !flags.outputs_enabled() {
return Err(OutputError);
}
self.recipients.push(RecipientInfo {
ovk,
recipient,
value,
memo,
});
self.outputs
.push(OutputInfo::new(ovk, recipient, value, memo));
Ok(())
}
@ -353,7 +427,7 @@ impl Builder {
/// Returns the action output components that will be produced by the
/// transaction being constructed
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,
@ -372,89 +446,115 @@ impl Builder {
.iter()
.map(|spend| spend.note.value() - NoteValue::zero())
.chain(
self.recipients
self.outputs
.iter()
.map(|recipient| NoteValue::zero() - recipient.value),
.map(|output| NoteValue::zero() - output.value),
)
.fold(Some(ValueSum::zero()), |acc, note_value| acc? + note_value)
.ok_or(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
/// [`Bundle::create_proof`] and [`Bundle::apply_signatures`] respectively.
pub fn build<V: TryFrom<i64>>(
mut self,
mut rng: impl RngCore,
) -> Result<Bundle<InProgress<Unproven, Unauthorized>, V>, BuildError> {
// Pair up the spends and recipients, extending with dummy values as necessary.
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,
rng: impl RngCore,
) -> Result<Option<UnauthorizedBundle<V>>, BuildError> {
bundle(rng, self.spends, self.outputs, self.bundle_type)
}
}
self.spends.extend(
iter::repeat_with(|| SpendInfo::dummy(&mut rng)).take(num_actions - num_spends),
);
self.recipients.extend(
iter::repeat_with(|| RecipientInfo::dummy(&mut rng))
.take(num_actions - num_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
/// [`Bundle::create_proof`] and [`Bundle::apply_signatures`] respectively.
pub fn bundle<V: TryFrom<i64>>(
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
// specific spent note or output note doesn't reveal anything on its own about
// the meaning of that note in the transaction context.
self.spends.shuffle(&mut rng);
self.recipients.shuffle(&mut rng);
let num_requested_spends = spends.len();
if !flags.spends_enabled() && num_requested_spends > 0 {
return Err(BuildError::SpendsDisabled);
}
self.spends
.into_iter()
.zip(self.recipients.into_iter())
.map(|(spend, recipient)| ActionInfo::new(spend, recipient, &mut rng))
.collect()
};
for spend in &spends {
if !spend.has_matching_anchor(anchor) {
return Err(BuildError::AnchorMismatch);
}
}
// Move some things out of self that we will need.
let flags = self.flags;
let anchor = self.anchor;
let num_requested_outputs = outputs.len();
if !flags.outputs_enabled() && num_requested_outputs > 0 {
return Err(BuildError::OutputsDisabled);
}
// Determine the value balance for this bundle, ensuring it is valid.
let value_balance = pre_actions
.iter()
.fold(Some(ValueSum::zero()), |acc, action| {
acc? + action.value_sum()
})
.ok_or(OverflowError)?;
let num_actions = bundle_type
.num_actions(num_requested_spends, num_requested_outputs)
.map_err(|_| BuildError::BundleTypeNotSatisfiable)?;
let result_value_balance: V = i64::try_from(value_balance)
.map_err(BuildError::ValueSum)
.and_then(|i| V::try_from(i).map_err(|_| BuildError::ValueSum(value::OverflowError)))?;
// Pair up the spends and outputs, extending with dummy values as necessary.
let pre_actions: Vec<_> = {
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.
let bsk = pre_actions
.iter()
.map(|a| &a.rcv)
.sum::<ValueCommitTrapdoor>()
.into_bsk();
// Shuffle the spends and outputs, so that learning the position of a
// specific spent note or output note doesn't reveal anything on its own about
// the meaning of that note in the transaction context.
spends.shuffle(&mut rng);
outputs.shuffle(&mut rng);
// Create the actions.
let (actions, circuits): (Vec<_>, Vec<_>) =
pre_actions.into_iter().map(|a| a.build(&mut rng)).unzip();
spends
.into_iter()
.zip(outputs.into_iter())
.map(|(spend, output)| ActionInfo::new(spend, output, &mut rng))
.collect()
};
// 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);
// Determine the value balance for this bundle, ensuring it is valid.
let value_balance = pre_actions
.iter()
.fold(Some(ValueSum::zero()), |acc, action| {
acc? + action.value_sum()
})
.ok_or(OverflowError)?;
Ok(Bundle::from_parts(
NonEmpty::from_vec(actions).unwrap(),
let result_value_balance: V = i64::try_from(value_balance)
.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,
result_value_balance,
anchor,
@ -462,8 +562,8 @@ impl Builder {
proof: Unproven { circuits },
sigs: Unauthorized { bsk },
},
))
}
)
}))
}
/// 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;
}
impl OutputView for RecipientInfo {
impl OutputView for OutputInfo {
fn value<V: From<u64>>(&self) -> V {
V::from(self.value.inner())
}
@ -777,7 +877,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
@ -793,7 +893,7 @@ pub mod testing {
sk: SpendingKey,
anchor: Anchor,
notes: Vec<(Note, MerklePath)>,
recipient_amounts: Vec<(Address, NoteValue)>,
output_amounts: Vec<(Address, NoteValue)>,
}
impl<R: RngCore + CryptoRng> ArbitraryBundleInputs<R> {
@ -801,18 +901,18 @@ pub mod testing {
fn into_bundle<V: TryFrom<i64>>(mut self) -> Bundle<Authorized, V> {
let fvk = FullViewingKey::from(&self.sk);
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() {
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 ovk = fvk.to_ovk(scope);
builder
.add_recipient(Some(ovk.clone()), addr, value, None)
.add_output(Some(ovk.clone()), addr, value, None)
.unwrap();
}
@ -820,6 +920,7 @@ pub mod testing {
builder
.build(&mut self.rng)
.unwrap()
.unwrap()
.create_proof(&pk, &mut self.rng)
.unwrap()
.prepare(&mut self.rng, [0; 32])
@ -834,7 +935,7 @@ pub mod testing {
fn arb_bundle_inputs(sk: SpendingKey)
(
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),
n_notes
),
recipient_amounts in vec(
output_amounts in vec(
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))
}),
n_recipients as usize
n_outputs as usize
),
rng_seed in prop::array::uniform32(prop::num::u8::ANY)
) -> ArbitraryBundleInputs<StdRng> {
@ -873,7 +974,7 @@ pub mod testing {
sk,
anchor: frontier.root().into(),
notes: notes_and_auth_paths,
recipient_amounts
output_amounts
}
}
}
@ -899,6 +1000,7 @@ mod tests {
use super::Builder;
use crate::{
builder::BundleType,
bundle::{Authorized, Bundle, Flags},
circuit::ProvingKey,
constants::MERKLE_DEPTH_ORCHARD,
@ -916,13 +1018,13 @@ mod tests {
let fvk = FullViewingKey::from(&sk);
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),
EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(),
);
));
builder
.add_recipient(None, recipient, NoteValue::from_raw(5000), None)
.add_output(None, recipient, NoteValue::from_raw(5000), None)
.unwrap();
let balance: i64 = builder.value_balance().unwrap();
assert_eq!(balance, -5000);
@ -930,6 +1032,7 @@ mod tests {
let bundle: Bundle<Authorized, i64> = builder
.build(&mut rng)
.unwrap()
.unwrap()
.create_proof(&pk, &mut rng)
.unwrap()
.prepare(rng, [0; 32])

View File

@ -42,7 +42,7 @@ impl<T> Action<T> {
}
/// Orchard-specific flags.
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Flags {
/// 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 {
/// 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 {
spends_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.
///
/// 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> {
// https://p.z.cash/TCR:bad-txns-v5-reserved-bits-nonzero
if value & FLAGS_EXPECTED_UNSET == 0 {
Some(Self::from_parts(
value & FLAG_SPENDS_ENABLED != 0,
value & FLAG_OUTPUTS_ENABLED != 0,
))
Some(Self {
spends_enabled: value & FLAG_SPENDS_ENABLED != 0,
outputs_enabled: value & FLAG_OUTPUTS_ENABLED != 0,
})
} else {
None
}

View File

@ -58,12 +58,14 @@ impl From<MerkleHashOrchard> for 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 {
self.0
}
}
impl Anchor {
/// Parses an Orchard anchor from a byte encoding.
pub fn from_bytes(bytes: [u8; 32]) -> CtOption<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
//! be checked in its `TryFrom<i64>` implementation.
//! - 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.
//!
@ -34,7 +34,7 @@
//! [`Bundle`]: crate::bundle::Bundle
//! [`Bundle::value_balance`]: crate::bundle::Bundle::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
use core::fmt::{self, Debug};
@ -83,7 +83,7 @@ impl fmt::Display for OverflowError {
impl std::error::Error for OverflowError {}
/// 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);
impl NoteValue {

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},
@ -42,12 +42,12 @@ fn bundle_chain() {
// Use the empty tree.
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!(
builder.add_recipient(None, recipient, NoteValue::from_raw(5000), None),
builder.add_output(None, recipient, NoteValue::from_raw(5000), None),
Ok(())
);
let unauthorized = builder.build(&mut rng).unwrap();
let unauthorized = builder.build(&mut rng).unwrap().unwrap();
let sighash = unauthorized.commitment().into();
let proven = unauthorized.create_proof(&pk, &mut rng).unwrap();
proven.apply_signatures(rng, sighash, &[]).unwrap()
@ -83,13 +83,13 @@ fn bundle_chain() {
let anchor = root.into();
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_recipient(None, recipient, NoteValue::from_raw(5000), None),
builder.add_output(None, recipient, NoteValue::from_raw(5000), None),
Ok(())
);
let unauthorized = builder.build(&mut rng).unwrap();
let unauthorized = builder.build(&mut rng).unwrap().unwrap();
let sighash = unauthorized.commitment().into();
let proven = unauthorized.create_proof(&pk, &mut rng).unwrap();
proven