mirror of https://github.com/zcash/orchard.git
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:
commit
78f598616a
25
CHANGELOG.md
25
CHANGELOG.md
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
329
src/builder.rs
329
src/builder.rs
|
@ -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>,
|
||||||
|
recipient: Address,
|
||||||
|
value: NoteValue,
|
||||||
|
memo: [u8; 512],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OutputInfo {
|
||||||
|
/// Constructs a new OutputInfo from its constituent parts.
|
||||||
|
pub fn new(
|
||||||
ovk: Option<OutgoingViewingKey>,
|
ovk: Option<OutgoingViewingKey>,
|
||||||
recipient: Address,
|
recipient: Address,
|
||||||
value: NoteValue,
|
value: NoteValue,
|
||||||
memo: Option<[u8; 512]>,
|
memo: Option<[u8; 512]>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
ovk,
|
||||||
|
recipient,
|
||||||
|
value,
|
||||||
|
memo: memo.unwrap_or_else(|| {
|
||||||
|
let mut memo = [0; 512];
|
||||||
|
memo[0] = 0xf6;
|
||||||
|
memo
|
||||||
|
}),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RecipientInfo {
|
|
||||||
/// 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(¬e.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,58 +446,83 @@ 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,
|
||||||
|
rng: impl RngCore,
|
||||||
|
) -> Result<Option<UnauthorizedBundle<V>>, BuildError> {
|
||||||
|
bundle(rng, self.spends, self.outputs, self.bundle_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 rng: impl RngCore,
|
||||||
) -> Result<Bundle<InProgress<Unproven, Unauthorized>, V>, BuildError> {
|
mut spends: Vec<SpendInfo>,
|
||||||
// Pair up the spends and recipients, extending with dummy values as necessary.
|
mut outputs: Vec<OutputInfo>,
|
||||||
|
bundle_type: BundleType,
|
||||||
|
) -> Result<Option<UnauthorizedBundle<V>>, BuildError> {
|
||||||
|
let (flags, anchor) = bundle_type.bundle_config();
|
||||||
|
|
||||||
|
let num_requested_spends = spends.len();
|
||||||
|
if !flags.spends_enabled() && num_requested_spends > 0 {
|
||||||
|
return Err(BuildError::SpendsDisabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
for spend in &spends {
|
||||||
|
if !spend.has_matching_anchor(anchor) {
|
||||||
|
return Err(BuildError::AnchorMismatch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let num_requested_outputs = outputs.len();
|
||||||
|
if !flags.outputs_enabled() && num_requested_outputs > 0 {
|
||||||
|
return Err(BuildError::OutputsDisabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
let num_actions = bundle_type
|
||||||
|
.num_actions(num_requested_spends, num_requested_outputs)
|
||||||
|
.map_err(|_| BuildError::BundleTypeNotSatisfiable)?;
|
||||||
|
|
||||||
|
// 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();
|
spends.extend(
|
||||||
let num_recipients = self.recipients.len();
|
iter::repeat_with(|| SpendInfo::dummy(&mut rng))
|
||||||
let num_actions = [num_spends, num_recipients, MIN_ACTIONS]
|
.take(num_actions - num_requested_spends),
|
||||||
.iter()
|
|
||||||
.max()
|
|
||||||
.cloned()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
self.spends.extend(
|
|
||||||
iter::repeat_with(|| SpendInfo::dummy(&mut rng)).take(num_actions - num_spends),
|
|
||||||
);
|
);
|
||||||
self.recipients.extend(
|
outputs.extend(
|
||||||
iter::repeat_with(|| RecipientInfo::dummy(&mut rng))
|
iter::repeat_with(|| OutputInfo::dummy(&mut rng))
|
||||||
.take(num_actions - num_recipients),
|
.take(num_actions - num_requested_outputs),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Shuffle the spends and recipients, so that learning the position of a
|
// 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
|
// specific spent note or output note doesn't reveal anything on its own about
|
||||||
// the meaning of that note in the transaction context.
|
// the meaning of that note in the transaction context.
|
||||||
self.spends.shuffle(&mut rng);
|
spends.shuffle(&mut rng);
|
||||||
self.recipients.shuffle(&mut rng);
|
outputs.shuffle(&mut rng);
|
||||||
|
|
||||||
self.spends
|
spends
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.zip(self.recipients.into_iter())
|
.zip(outputs.into_iter())
|
||||||
.map(|(spend, recipient)| ActionInfo::new(spend, recipient, &mut rng))
|
.map(|(spend, output)| ActionInfo::new(spend, output, &mut rng))
|
||||||
.collect()
|
.collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Move some things out of self that we will need.
|
|
||||||
let flags = self.flags;
|
|
||||||
let anchor = self.anchor;
|
|
||||||
|
|
||||||
// Determine the value balance for this bundle, ensuring it is valid.
|
// Determine the value balance for this bundle, ensuring it is valid.
|
||||||
let value_balance = pre_actions
|
let value_balance = pre_actions
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -453,8 +552,9 @@ impl Builder {
|
||||||
.into_bvk();
|
.into_bvk();
|
||||||
assert_eq!(redpallas::VerificationKey::from(&bsk), bvk);
|
assert_eq!(redpallas::VerificationKey::from(&bsk), bvk);
|
||||||
|
|
||||||
Ok(Bundle::from_parts(
|
Ok(NonEmpty::from_vec(actions).map(|actions| {
|
||||||
NonEmpty::from_vec(actions).unwrap(),
|
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])
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue