2021-04-14 21:14:34 -07:00
|
|
|
//! Logic for building Orchard components of transactions.
|
|
|
|
|
2022-04-28 13:20:23 -07:00
|
|
|
use core::fmt;
|
|
|
|
use core::iter;
|
2022-12-08 12:58:30 -08:00
|
|
|
use std::fmt::Display;
|
2021-04-14 21:14:34 -07:00
|
|
|
|
|
|
|
use ff::Field;
|
|
|
|
use nonempty::NonEmpty;
|
|
|
|
use pasta_curves::pallas;
|
2022-02-07 14:33:57 -08:00
|
|
|
use rand::{prelude::SliceRandom, CryptoRng, RngCore};
|
2021-04-14 21:14:34 -07:00
|
|
|
|
|
|
|
use crate::{
|
2022-04-28 14:46:24 -07:00
|
|
|
action::Action,
|
2021-05-05 10:46:24 -07:00
|
|
|
address::Address,
|
2022-04-28 14:46:24 -07:00
|
|
|
bundle::{Authorization, Authorized, Bundle, Flags},
|
2021-07-28 05:49:35 -07:00
|
|
|
circuit::{Circuit, Instance, Proof, ProvingKey},
|
2021-04-14 21:14:34 -07:00
|
|
|
keys::{
|
2022-03-30 04:53:46 -07:00
|
|
|
FullViewingKey, OutgoingViewingKey, Scope, SpendAuthorizingKey, SpendValidatingKey,
|
|
|
|
SpendingKey,
|
2021-04-14 21:14:34 -07:00
|
|
|
},
|
2021-05-05 10:46:24 -07:00
|
|
|
note::{Note, TransmittedNoteCiphertext},
|
2021-06-02 16:25:49 -07:00
|
|
|
note_encryption::OrchardNoteEncryption,
|
2021-04-14 21:14:34 -07:00
|
|
|
primitives::redpallas::{self, Binding, SpendAuth},
|
|
|
|
tree::{Anchor, MerklePath},
|
2021-04-27 12:56:36 -07:00
|
|
|
value::{self, NoteValue, OverflowError, ValueCommitTrapdoor, ValueCommitment, ValueSum},
|
2021-04-14 21:14:34 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
const MIN_ACTIONS: usize = 2;
|
|
|
|
|
2023-12-19 11:18:31 -08:00
|
|
|
/// An enumeration of rules for Orchard bundle construction.
|
2023-12-08 12:38:52 -08:00
|
|
|
#[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.
|
2023-12-20 18:00:47 -08:00
|
|
|
Transactional {
|
|
|
|
/// The flags that control whether spends and/or outputs are enabled for the bundle.
|
|
|
|
flags: Flags,
|
|
|
|
/// A flag that, when set to `true`, indicates that a bundle should be produced even if no
|
|
|
|
/// spends or outputs have been added to the bundle; in such a circumstance, all of the
|
|
|
|
/// actions in the resulting bundle will be dummies.
|
|
|
|
bundle_required: bool,
|
|
|
|
},
|
2023-12-08 12:38:52 -08:00
|
|
|
/// A coinbase bundle is required to have no non-dummy spends. No padding is performed.
|
|
|
|
Coinbase,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl BundleType {
|
2023-12-20 18:00:47 -08:00
|
|
|
/// The default bundle type has all flags enabled, and does not require a bundle to be produced
|
|
|
|
/// if no spends or outputs have been added to the bundle.
|
|
|
|
pub const DEFAULT: BundleType = BundleType::Transactional {
|
|
|
|
flags: Flags::ENABLED,
|
|
|
|
bundle_required: false,
|
|
|
|
};
|
|
|
|
|
|
|
|
/// The DISABLED bundle type does not permit any bundle to be produced, and when used in the
|
|
|
|
/// builder will prevent any spends or outputs from being added.
|
|
|
|
pub const DISABLED: BundleType = BundleType::Transactional {
|
|
|
|
flags: Flags::from_parts(false, false),
|
|
|
|
bundle_required: false,
|
|
|
|
};
|
|
|
|
|
2023-12-08 12:38:52 -08:00
|
|
|
/// 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> {
|
2023-12-15 15:19:24 -08:00
|
|
|
let num_requested_actions = core::cmp::max(num_spends, num_outputs);
|
2023-12-08 12:38:52 -08:00
|
|
|
|
|
|
|
match self {
|
2023-12-20 18:00:47 -08:00
|
|
|
BundleType::Transactional {
|
|
|
|
flags,
|
|
|
|
bundle_required,
|
|
|
|
} => {
|
2023-12-15 15:19:24 -08:00
|
|
|
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")
|
2023-12-08 12:38:52 -08:00
|
|
|
} else {
|
2023-12-20 18:00:47 -08:00
|
|
|
Ok(if *bundle_required || num_requested_actions > 0 {
|
|
|
|
core::cmp::max(num_requested_actions, MIN_ACTIONS)
|
|
|
|
} else {
|
|
|
|
0
|
|
|
|
})
|
2023-12-08 12:38:52 -08:00
|
|
|
}
|
|
|
|
}
|
2023-12-19 11:18:31 -08:00
|
|
|
BundleType::Coinbase => {
|
|
|
|
if num_spends > 0 {
|
|
|
|
Err("Coinbase bundles have spends disabled, so num_spends must be zero")
|
|
|
|
} else {
|
|
|
|
Ok(num_outputs)
|
|
|
|
}
|
|
|
|
}
|
2023-12-15 15:19:24 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the set of flags and the anchor that will be used for bundle construction.
|
2023-12-20 18:00:47 -08:00
|
|
|
pub fn flags(&self) -> Flags {
|
2023-12-15 15:19:24 -08:00
|
|
|
match self {
|
2023-12-20 18:00:47 -08:00
|
|
|
BundleType::Transactional { flags, .. } => *flags,
|
|
|
|
BundleType::Coinbase => Flags::SPENDS_DISABLED,
|
2023-12-08 12:38:52 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-27 12:56:36 -07:00
|
|
|
/// An error type for the kinds of errors that can occur during bundle construction.
|
2021-04-14 21:14:34 -07:00
|
|
|
#[derive(Debug)]
|
2022-12-01 12:58:26 -08:00
|
|
|
pub enum BuildError {
|
2023-12-15 15:19:24 -08:00
|
|
|
/// Spends are disabled for the provided bundle type.
|
|
|
|
SpendsDisabled,
|
|
|
|
/// Spends are disabled for the provided bundle type.
|
|
|
|
OutputsDisabled,
|
2023-12-19 11:18:31 -08:00
|
|
|
/// The anchor provided to this builder doesn't match the Merkle path used to add a spend.
|
2023-12-15 15:19:24 -08:00
|
|
|
AnchorMismatch,
|
2021-04-27 12:56:36 -07:00
|
|
|
/// A bundle could not be built because required signatures were missing.
|
2021-04-14 21:14:34 -07:00
|
|
|
MissingSignatures,
|
2021-04-27 12:56:36 -07:00
|
|
|
/// An error occurred in the process of producing a proof for a bundle.
|
2022-01-28 07:53:03 -08:00
|
|
|
Proof(halo2_proofs::plonk::Error),
|
2021-04-27 12:56:36 -07:00
|
|
|
/// An overflow error occurred while attempting to construct the value
|
|
|
|
/// for a bundle.
|
2021-04-14 21:14:34 -07:00
|
|
|
ValueSum(value::OverflowError),
|
2022-03-16 08:17:47 -07:00
|
|
|
/// External signature is not valid.
|
|
|
|
InvalidExternalSignature,
|
|
|
|
/// 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,
|
2023-12-08 12:38:52 -08:00
|
|
|
/// The bundle being constructed violated the construction rules for the requested bundle type.
|
|
|
|
BundleTypeNotSatisfiable,
|
2021-04-14 21:14:34 -07:00
|
|
|
}
|
|
|
|
|
2022-12-08 12:58:30 -08:00
|
|
|
impl Display for BuildError {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
use BuildError::*;
|
|
|
|
match self {
|
|
|
|
MissingSignatures => f.write_str("Required signatures were missing during build"),
|
2023-02-28 14:20:39 -08:00
|
|
|
Proof(e) => f.write_str(&format!("Could not create proof: {}", e)),
|
2022-12-31 03:19:43 -08:00
|
|
|
ValueSum(_) => f.write_str("Overflow occurred during value construction"),
|
2022-12-08 12:58:30 -08:00
|
|
|
InvalidExternalSignature => f.write_str("External signature was invalid"),
|
|
|
|
DuplicateSignature => f.write_str("Signature valid for more than one input"),
|
2023-12-08 12:38:52 -08:00
|
|
|
BundleTypeNotSatisfiable => {
|
|
|
|
f.write_str("Bundle structure did not conform to requested bundle type.")
|
|
|
|
}
|
2023-12-15 15:19:24 -08:00
|
|
|
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.")
|
|
|
|
}
|
2022-12-08 12:58:30 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl std::error::Error for BuildError {}
|
|
|
|
|
2023-12-15 15:19:24 -08:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-01 12:51:04 -08:00
|
|
|
/// An error type for adding a spend to the builder.
|
2022-12-01 13:01:10 -08:00
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
2022-12-01 12:51:04 -08:00
|
|
|
pub enum SpendError {
|
|
|
|
/// Spends aren't enabled for this builder.
|
|
|
|
SpendsDisabled,
|
|
|
|
/// The anchor provided to this builder doesn't match the merkle path used to add a spend.
|
|
|
|
AnchorMismatch,
|
|
|
|
/// The full viewing key provided didn't match the note provided
|
|
|
|
FvkMismatch,
|
|
|
|
}
|
|
|
|
|
2022-12-08 12:58:30 -08:00
|
|
|
impl Display for SpendError {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
use SpendError::*;
|
|
|
|
f.write_str(match self {
|
|
|
|
SpendsDisabled => "Spends are not enabled for this builder",
|
|
|
|
AnchorMismatch => "All anchors must be equal.",
|
|
|
|
FvkMismatch => "FullViewingKey does not correspond to the given note",
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl std::error::Error for SpendError {}
|
|
|
|
|
2022-12-01 12:51:04 -08:00
|
|
|
/// The only error that can occur here is if outputs are disabled for this builder.
|
2022-12-08 12:35:49 -08:00
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
2022-12-08 12:58:30 -08:00
|
|
|
pub struct OutputError;
|
|
|
|
|
|
|
|
impl Display for OutputError {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
f.write_str("Outputs are not enabled for this builder")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl std::error::Error for OutputError {}
|
2022-12-01 12:51:04 -08:00
|
|
|
|
2021-04-14 21:14:34 -07:00
|
|
|
/// Information about a specific note to be spent in an [`Action`].
|
|
|
|
#[derive(Debug)]
|
2022-09-20 08:08:54 -07:00
|
|
|
pub struct SpendInfo {
|
|
|
|
pub(crate) dummy_sk: Option<SpendingKey>,
|
|
|
|
pub(crate) fvk: FullViewingKey,
|
|
|
|
pub(crate) scope: Scope,
|
|
|
|
pub(crate) note: Note,
|
|
|
|
pub(crate) merkle_path: MerklePath,
|
2021-04-14 21:14:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl SpendInfo {
|
2022-09-20 08:08:54 -07:00
|
|
|
/// This constructor is public to enable creation of custom builders.
|
|
|
|
/// If you are not creating a custom builder, use [`Builder::add_spend`] instead.
|
|
|
|
///
|
|
|
|
/// Creates a `SpendInfo` from note, full viewing key owning the note,
|
|
|
|
/// and merkle path witness of the note.
|
|
|
|
///
|
|
|
|
/// Returns `None` if the `fvk` does not own the `note`.
|
|
|
|
///
|
|
|
|
/// [`Builder::add_spend`]: Builder::add_spend
|
|
|
|
pub fn new(fvk: FullViewingKey, note: Note, merkle_path: MerklePath) -> Option<Self> {
|
|
|
|
let scope = fvk.scope_for_address(¬e.recipient())?;
|
|
|
|
Some(SpendInfo {
|
|
|
|
dummy_sk: None,
|
|
|
|
fvk,
|
|
|
|
scope,
|
|
|
|
note,
|
|
|
|
merkle_path,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-04-14 21:14:34 -07:00
|
|
|
/// 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 {
|
|
|
|
let (sk, fvk, note) = Note::dummy(rng, None);
|
|
|
|
let merkle_path = MerklePath::dummy(rng);
|
|
|
|
|
|
|
|
SpendInfo {
|
|
|
|
dummy_sk: Some(sk),
|
|
|
|
fvk,
|
2022-03-30 04:53:46 -07:00
|
|
|
// We use external scope to avoid unnecessary derivations, because the dummy
|
|
|
|
// note's spending key is random and thus scoping is irrelevant.
|
|
|
|
scope: Scope::External,
|
2021-04-14 21:14:34 -07:00
|
|
|
note,
|
|
|
|
merkle_path,
|
|
|
|
}
|
|
|
|
}
|
2023-12-19 11:18:31 -08:00
|
|
|
|
2023-12-20 18:00:47 -08:00
|
|
|
fn has_matching_anchor(&self, anchor: &Anchor) -> bool {
|
2023-12-19 11:18:31 -08:00
|
|
|
if self.note.value() == NoteValue::zero() {
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
let cm = self.note.commitment();
|
|
|
|
let path_root = self.merkle_path.root(cm.into());
|
2023-12-20 18:00:47 -08:00
|
|
|
&path_root == anchor
|
2023-12-19 11:18:31 -08:00
|
|
|
}
|
|
|
|
}
|
2021-04-14 21:14:34 -07:00
|
|
|
}
|
|
|
|
|
2023-12-12 10:12:10 -08:00
|
|
|
/// Information about a specific output to receive funds in an [`Action`].
|
2021-04-14 21:14:34 -07:00
|
|
|
#[derive(Debug)]
|
2023-12-15 15:19:24 -08:00
|
|
|
pub struct OutputInfo {
|
2021-04-14 21:14:34 -07:00
|
|
|
ovk: Option<OutgoingViewingKey>,
|
|
|
|
recipient: Address,
|
|
|
|
value: NoteValue,
|
2023-12-15 15:19:24 -08:00
|
|
|
memo: [u8; 512],
|
2021-04-14 21:14:34 -07:00
|
|
|
}
|
|
|
|
|
2023-12-12 10:12:10 -08:00
|
|
|
impl OutputInfo {
|
2023-12-15 15:19:24 -08:00
|
|
|
/// 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
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-14 21:14:34 -07:00
|
|
|
/// Defined in [Zcash Protocol Spec § 4.8.3: Dummy Notes (Orchard)][orcharddummynotes].
|
|
|
|
///
|
|
|
|
/// [orcharddummynotes]: https://zips.z.cash/protocol/nu5.pdf#orcharddummynotes
|
2023-12-15 15:19:24 -08:00
|
|
|
pub fn dummy(rng: &mut impl RngCore) -> Self {
|
2021-04-14 21:14:34 -07:00
|
|
|
let fvk: FullViewingKey = (&SpendingKey::random(rng)).into();
|
2022-03-30 04:53:46 -07:00
|
|
|
let recipient = fvk.address_at(0u32, Scope::External);
|
2021-04-14 21:14:34 -07:00
|
|
|
|
2023-12-15 15:19:24 -08:00
|
|
|
Self::new(None, recipient, NoteValue::zero(), None)
|
2021-04-14 21:14:34 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Information about a specific [`Action`] we plan to build.
|
|
|
|
#[derive(Debug)]
|
|
|
|
struct ActionInfo {
|
|
|
|
spend: SpendInfo,
|
2023-12-12 10:12:10 -08:00
|
|
|
output: OutputInfo,
|
2021-04-14 21:14:34 -07:00
|
|
|
rcv: ValueCommitTrapdoor,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ActionInfo {
|
2023-12-12 10:12:10 -08:00
|
|
|
fn new(spend: SpendInfo, output: OutputInfo, rng: impl RngCore) -> Self {
|
2021-04-14 21:14:34 -07:00
|
|
|
ActionInfo {
|
|
|
|
spend,
|
|
|
|
output,
|
|
|
|
rcv: ValueCommitTrapdoor::random(rng),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the value sum for this action.
|
2022-04-29 06:54:42 -07:00
|
|
|
fn value_sum(&self) -> ValueSum {
|
2021-04-14 21:14:34 -07:00
|
|
|
self.spend.note.value() - self.output.value
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Builds the action.
|
|
|
|
///
|
|
|
|
/// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend].
|
|
|
|
///
|
|
|
|
/// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend
|
2021-04-27 12:56:36 -07:00
|
|
|
fn build(self, mut rng: impl RngCore) -> (Action<SigningMetadata>, Circuit) {
|
2022-04-29 06:54:42 -07:00
|
|
|
let v_net = self.value_sum();
|
2021-07-28 14:51:43 -07:00
|
|
|
let cv_net = ValueCommitment::derive(v_net, self.rcv.clone());
|
2021-04-14 21:14:34 -07:00
|
|
|
|
|
|
|
let nf_old = self.spend.note.nullifier(&self.spend.fvk);
|
2021-06-10 22:02:05 -07:00
|
|
|
let ak: SpendValidatingKey = self.spend.fvk.clone().into();
|
2021-04-14 21:14:34 -07:00
|
|
|
let alpha = pallas::Scalar::random(&mut rng);
|
|
|
|
let rk = ak.randomize(&alpha);
|
|
|
|
|
2021-06-02 16:25:49 -07:00
|
|
|
let note = Note::new(self.output.recipient, self.output.value, nf_old, &mut rng);
|
2021-04-14 21:14:34 -07:00
|
|
|
let cm_new = note.commitment();
|
2021-06-02 16:25:49 -07:00
|
|
|
let cmx = cm_new.into();
|
|
|
|
|
2023-12-15 15:19:24 -08:00
|
|
|
let encryptor = OrchardNoteEncryption::new(self.output.ovk, note, self.output.memo);
|
2021-04-14 21:14:34 -07:00
|
|
|
|
2021-04-21 08:57:48 -07:00
|
|
|
let encrypted_note = TransmittedNoteCiphertext {
|
2021-06-02 16:25:49 -07:00
|
|
|
epk_bytes: encryptor.epk().to_bytes().0,
|
|
|
|
enc_ciphertext: encryptor.encrypt_note_plaintext(),
|
|
|
|
out_ciphertext: encryptor.encrypt_outgoing_plaintext(&cv_net, &cmx, &mut rng),
|
2021-04-21 08:57:48 -07:00
|
|
|
};
|
2021-04-14 21:14:34 -07:00
|
|
|
|
|
|
|
(
|
|
|
|
Action::from_parts(
|
|
|
|
nf_old,
|
|
|
|
rk,
|
2021-06-02 16:25:49 -07:00
|
|
|
cmx,
|
2021-04-14 21:14:34 -07:00
|
|
|
encrypted_note,
|
|
|
|
cv_net,
|
2021-04-28 15:38:08 -07:00
|
|
|
SigningMetadata {
|
|
|
|
dummy_ask: self.spend.dummy_sk.as_ref().map(SpendAuthorizingKey::from),
|
2022-09-20 08:08:54 -07:00
|
|
|
parts: SigningParts { ak, alpha },
|
2021-04-28 15:38:08 -07:00
|
|
|
},
|
2021-04-14 21:14:34 -07:00
|
|
|
),
|
2022-09-20 08:08:54 -07:00
|
|
|
Circuit::from_action_context_unchecked(self.spend, note, alpha, self.rcv),
|
2021-04-14 21:14:34 -07:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-19 11:18:31 -08:00
|
|
|
/// 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>;
|
|
|
|
|
2024-01-09 14:14:12 -08:00
|
|
|
/// Metadata about a bundle created by [`bundle`] or [`Builder::build`] that is not
|
|
|
|
/// necessarily recoverable from the bundle itself.
|
|
|
|
///
|
|
|
|
/// This includes information about how [`Action`]s within the bundle are ordered (after
|
|
|
|
/// padding and randomization) relative to the order in which spends and outputs were
|
|
|
|
/// provided (to [`bundle`]), or the order in which [`Builder`] mutations were performed.
|
2024-01-09 10:48:15 -08:00
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
|
|
pub struct BundleMetadata {
|
|
|
|
spend_indices: Vec<usize>,
|
|
|
|
output_indices: Vec<usize>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl BundleMetadata {
|
|
|
|
fn new(num_requested_spends: usize, num_requested_outputs: usize) -> Self {
|
|
|
|
BundleMetadata {
|
|
|
|
spend_indices: vec![0; num_requested_spends],
|
|
|
|
output_indices: vec![0; num_requested_outputs],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-09 14:14:12 -08:00
|
|
|
/// Returns the metadata for a [`Bundle`] that contains only dummy actions, if any.
|
2024-01-09 10:48:15 -08:00
|
|
|
pub fn empty() -> Self {
|
|
|
|
Self::new(0, 0)
|
|
|
|
}
|
|
|
|
|
2024-01-09 14:14:12 -08:00
|
|
|
/// Returns the index within the bundle of the [`Action`] corresponding to the `n`-th
|
|
|
|
/// spend specified in bundle construction. If a [`Builder`] was used, this refers to
|
|
|
|
/// the spend added by the `n`-th call to [`Builder::add_spend`].
|
2024-01-09 10:48:15 -08:00
|
|
|
///
|
2024-01-09 14:14:12 -08:00
|
|
|
/// For the purpose of improving indistinguishability, actions are padded and note
|
|
|
|
/// positions are randomized when building bundles. This means that the bundle
|
|
|
|
/// consumer cannot assume that e.g. the first spend they added corresponds to the
|
|
|
|
/// first action in the bundle.
|
2024-01-09 10:48:15 -08:00
|
|
|
pub fn spend_action_index(&self, n: usize) -> Option<usize> {
|
|
|
|
self.spend_indices.get(n).copied()
|
|
|
|
}
|
|
|
|
|
2024-01-09 14:14:12 -08:00
|
|
|
/// Returns the index within the bundle of the [`Action`] corresponding to the `n`-th
|
|
|
|
/// output specified in bundle construction. If a [`Builder`] was used, this refers to
|
|
|
|
/// the output added by the `n`-th call to [`Builder::add_output`].
|
2024-01-09 10:48:15 -08:00
|
|
|
///
|
2024-01-09 14:14:12 -08:00
|
|
|
/// For the purpose of improving indistinguishability, actions are padded and note
|
|
|
|
/// positions are randomized when building bundles. This means that the bundle
|
|
|
|
/// consumer cannot assume that e.g. the first output they added corresponds to the
|
|
|
|
/// first action in the bundle.
|
2024-01-09 10:48:15 -08:00
|
|
|
pub fn output_action_index(&self, n: usize) -> Option<usize> {
|
|
|
|
self.output_indices.get(n).copied()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-12 10:12:10 -08:00
|
|
|
/// A builder that constructs a [`Bundle`] from a set of notes to be spent, and outputs
|
2021-04-14 21:14:34 -07:00
|
|
|
/// to receive funds.
|
2021-04-27 12:56:36 -07:00
|
|
|
#[derive(Debug)]
|
2021-04-14 21:14:34 -07:00
|
|
|
pub struct Builder {
|
|
|
|
spends: Vec<SpendInfo>,
|
2023-12-12 10:12:10 -08:00
|
|
|
outputs: Vec<OutputInfo>,
|
2023-12-15 15:19:24 -08:00
|
|
|
bundle_type: BundleType,
|
2023-12-20 18:00:47 -08:00
|
|
|
anchor: Anchor,
|
2021-04-14 21:14:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Builder {
|
2021-05-05 10:10:52 -07:00
|
|
|
/// Constructs a new empty builder for an Orchard bundle.
|
2023-12-20 18:00:47 -08:00
|
|
|
pub fn new(bundle_type: BundleType, anchor: Anchor) -> Self {
|
2021-04-14 21:14:34 -07:00
|
|
|
Builder {
|
|
|
|
spends: vec![],
|
2023-12-12 10:12:10 -08:00
|
|
|
outputs: vec![],
|
2023-12-15 15:19:24 -08:00
|
|
|
bundle_type,
|
2023-12-20 18:00:47 -08:00
|
|
|
anchor,
|
2021-04-14 21:14:34 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Adds a note to be spent in this transaction.
|
|
|
|
///
|
2022-06-23 15:51:05 -07:00
|
|
|
/// - `note` is a spendable note, obtained by trial-decrypting an [`Action`] using the
|
|
|
|
/// [`zcash_note_encryption`] crate instantiated with [`OrchardDomain`].
|
|
|
|
/// - `merkle_path` can be obtained using the [`incrementalmerkletree`] crate
|
|
|
|
/// instantiated with [`MerkleHashOrchard`].
|
|
|
|
///
|
2021-04-14 21:14:34 -07:00
|
|
|
/// Returns an error if the given Merkle path does not have the required anchor for
|
|
|
|
/// the given note.
|
2022-06-23 15:51:05 -07:00
|
|
|
///
|
|
|
|
/// [`OrchardDomain`]: crate::note_encryption::OrchardDomain
|
|
|
|
/// [`MerkleHashOrchard`]: crate::tree::MerkleHashOrchard
|
2021-04-14 21:14:34 -07:00
|
|
|
pub fn add_spend(
|
|
|
|
&mut self,
|
|
|
|
fvk: FullViewingKey,
|
|
|
|
note: Note,
|
|
|
|
merkle_path: MerklePath,
|
2022-12-01 12:51:04 -08:00
|
|
|
) -> Result<(), SpendError> {
|
2023-12-20 18:00:47 -08:00
|
|
|
let flags = self.bundle_type.flags();
|
2023-12-15 15:19:24 -08:00
|
|
|
if !flags.spends_enabled() {
|
2022-12-01 12:51:04 -08:00
|
|
|
return Err(SpendError::SpendsDisabled);
|
2021-04-14 21:14:34 -07:00
|
|
|
}
|
|
|
|
|
2023-12-19 11:18:31 -08:00
|
|
|
let spend = SpendInfo::new(fvk, note, merkle_path).ok_or(SpendError::FvkMismatch)?;
|
|
|
|
|
2021-04-14 21:14:34 -07:00
|
|
|
// Consistency check: all anchors must be equal.
|
2023-12-20 18:00:47 -08:00
|
|
|
if !spend.has_matching_anchor(&self.anchor) {
|
2022-12-01 12:51:04 -08:00
|
|
|
return Err(SpendError::AnchorMismatch);
|
2021-06-07 22:18:16 -07:00
|
|
|
}
|
2021-04-14 21:14:34 -07:00
|
|
|
|
2023-12-19 11:18:31 -08:00
|
|
|
self.spends.push(spend);
|
2021-04-14 21:14:34 -07:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Adds an address which will receive funds in this transaction.
|
2023-12-12 10:12:10 -08:00
|
|
|
pub fn add_output(
|
2021-04-14 21:14:34 -07:00
|
|
|
&mut self,
|
|
|
|
ovk: Option<OutgoingViewingKey>,
|
|
|
|
recipient: Address,
|
|
|
|
value: NoteValue,
|
2021-06-02 16:25:49 -07:00
|
|
|
memo: Option<[u8; 512]>,
|
2022-12-08 12:58:30 -08:00
|
|
|
) -> Result<(), OutputError> {
|
2023-12-20 18:00:47 -08:00
|
|
|
let flags = self.bundle_type.flags();
|
2023-12-15 15:19:24 -08:00
|
|
|
if !flags.outputs_enabled() {
|
2022-12-08 12:58:30 -08:00
|
|
|
return Err(OutputError);
|
2021-04-14 21:14:34 -07:00
|
|
|
}
|
|
|
|
|
2023-12-15 15:19:24 -08:00
|
|
|
self.outputs
|
|
|
|
.push(OutputInfo::new(ovk, recipient, value, memo));
|
2021-04-14 21:14:34 -07:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-01-03 13:34:28 -08:00
|
|
|
/// Returns the action spend components that will be produced by the
|
2022-12-08 13:23:35 -08:00
|
|
|
/// transaction being constructed
|
2022-12-01 12:15:10 -08:00
|
|
|
pub fn spends(&self) -> &Vec<impl InputView<()>> {
|
|
|
|
&self.spends
|
|
|
|
}
|
|
|
|
|
2022-12-08 13:23:35 -08:00
|
|
|
/// Returns the action output components that will be produced by the
|
|
|
|
/// transaction being constructed
|
2022-12-01 12:15:10 -08:00
|
|
|
pub fn outputs(&self) -> &Vec<impl OutputView> {
|
2023-12-12 10:12:10 -08:00
|
|
|
&self.outputs
|
2022-12-01 12:15:10 -08:00
|
|
|
}
|
|
|
|
|
2022-08-23 12:23:15 -07:00
|
|
|
/// The net value of the bundle to be built. The value of all spends,
|
|
|
|
/// minus the value of all outputs.
|
|
|
|
///
|
|
|
|
/// Useful for balancing a transaction, as the value balance of an individual bundle
|
2022-09-19 05:27:16 -07:00
|
|
|
/// can be non-zero. Each bundle's value balance is [added] to the transparent
|
|
|
|
/// transaction value pool, which [must not have a negative value]. (If it were
|
|
|
|
/// negative, the transaction would output more value than it receives in inputs.)
|
|
|
|
///
|
|
|
|
/// [added]: https://zips.z.cash/protocol/protocol.pdf#orchardbalance
|
|
|
|
/// [must not have a negative value]: https://zips.z.cash/protocol/protocol.pdf#transactions
|
2022-08-23 12:23:15 -07:00
|
|
|
pub fn value_balance<V: TryFrom<i64>>(&self) -> Result<V, value::OverflowError> {
|
|
|
|
let value_balance = self
|
|
|
|
.spends
|
|
|
|
.iter()
|
|
|
|
.map(|spend| spend.note.value() - NoteValue::zero())
|
|
|
|
.chain(
|
2023-12-12 10:12:10 -08:00
|
|
|
self.outputs
|
2022-08-23 12:23:15 -07:00
|
|
|
.iter()
|
2023-12-12 10:12:10 -08:00
|
|
|
.map(|output| NoteValue::zero() - output.value),
|
2022-08-23 12:23:15 -07:00
|
|
|
)
|
|
|
|
.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))
|
|
|
|
}
|
|
|
|
|
2023-12-12 10:12:10 -08:00
|
|
|
/// Builds a bundle containing the given spent notes and outputs.
|
2021-04-14 21:14:34 -07:00
|
|
|
///
|
2022-05-04 07:57:08 -07:00
|
|
|
/// The returned bundle will have no proof or signatures; these can be applied with
|
|
|
|
/// [`Bundle::create_proof`] and [`Bundle::apply_signatures`] respectively.
|
2021-07-28 06:19:43 -07:00
|
|
|
pub fn build<V: TryFrom<i64>>(
|
2023-12-15 15:19:24 -08:00
|
|
|
self,
|
|
|
|
rng: impl RngCore,
|
2024-01-09 10:48:15 -08:00
|
|
|
) -> Result<Option<(UnauthorizedBundle<V>, BundleMetadata)>, BuildError> {
|
2023-12-20 18:00:47 -08:00
|
|
|
bundle(
|
|
|
|
rng,
|
|
|
|
self.anchor,
|
|
|
|
self.bundle_type,
|
|
|
|
self.spends,
|
|
|
|
self.outputs,
|
|
|
|
)
|
2023-12-15 15:19:24 -08:00
|
|
|
}
|
|
|
|
}
|
2021-04-14 21:14:34 -07:00
|
|
|
|
2023-12-15 15:19:24 -08:00
|
|
|
/// 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,
|
2023-12-20 18:00:47 -08:00
|
|
|
anchor: Anchor,
|
|
|
|
bundle_type: BundleType,
|
2024-01-09 10:48:15 -08:00
|
|
|
spends: Vec<SpendInfo>,
|
|
|
|
outputs: Vec<OutputInfo>,
|
|
|
|
) -> Result<Option<(UnauthorizedBundle<V>, BundleMetadata)>, BuildError> {
|
2023-12-20 18:00:47 -08:00
|
|
|
let flags = bundle_type.flags();
|
2023-12-15 15:19:24 -08:00
|
|
|
|
|
|
|
let num_requested_spends = spends.len();
|
|
|
|
if !flags.spends_enabled() && num_requested_spends > 0 {
|
|
|
|
return Err(BuildError::SpendsDisabled);
|
|
|
|
}
|
2021-04-14 21:14:34 -07:00
|
|
|
|
2023-12-15 15:19:24 -08:00
|
|
|
for spend in &spends {
|
2023-12-20 18:00:47 -08:00
|
|
|
if !spend.has_matching_anchor(&anchor) {
|
2023-12-19 11:18:31 -08:00
|
|
|
return Err(BuildError::AnchorMismatch);
|
2023-12-15 15:19:24 -08:00
|
|
|
}
|
|
|
|
}
|
2021-04-14 21:14:34 -07:00
|
|
|
|
2023-12-15 15:19:24 -08:00
|
|
|
let num_requested_outputs = outputs.len();
|
|
|
|
if !flags.outputs_enabled() && num_requested_outputs > 0 {
|
|
|
|
return Err(BuildError::OutputsDisabled);
|
|
|
|
}
|
2021-05-05 10:10:52 -07:00
|
|
|
|
2023-12-15 15:19:24 -08:00
|
|
|
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.
|
2024-01-09 10:48:15 -08:00
|
|
|
let (pre_actions, bundle_meta) = {
|
|
|
|
let mut indexed_spends = spends
|
|
|
|
.into_iter()
|
|
|
|
.chain(iter::repeat_with(|| SpendInfo::dummy(&mut rng)))
|
|
|
|
.enumerate()
|
|
|
|
.take(num_actions)
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
let mut indexed_outputs = outputs
|
|
|
|
.into_iter()
|
|
|
|
.chain(iter::repeat_with(|| OutputInfo::dummy(&mut rng)))
|
|
|
|
.enumerate()
|
|
|
|
.take(num_actions)
|
|
|
|
.collect::<Vec<_>>();
|
2023-12-15 15:19:24 -08:00
|
|
|
|
|
|
|
// 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.
|
2024-01-09 10:48:15 -08:00
|
|
|
indexed_spends.shuffle(&mut rng);
|
|
|
|
indexed_outputs.shuffle(&mut rng);
|
2023-12-15 15:19:24 -08:00
|
|
|
|
2024-01-09 10:48:15 -08:00
|
|
|
let mut bundle_meta = BundleMetadata::new(num_requested_spends, num_requested_outputs);
|
|
|
|
let pre_actions = indexed_spends
|
2023-12-15 15:19:24 -08:00
|
|
|
.into_iter()
|
2024-01-09 10:48:15 -08:00
|
|
|
.zip(indexed_outputs.into_iter())
|
|
|
|
.enumerate()
|
|
|
|
.map(|(action_idx, ((spend_idx, spend), (out_idx, output)))| {
|
|
|
|
// Record the post-randomization spend location
|
|
|
|
if spend_idx < num_requested_spends {
|
|
|
|
bundle_meta.spend_indices[spend_idx] = action_idx;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Record the post-randomization output location
|
|
|
|
if out_idx < num_requested_outputs {
|
|
|
|
bundle_meta.output_indices[out_idx] = action_idx;
|
|
|
|
}
|
|
|
|
|
|
|
|
ActionInfo::new(spend, output, &mut rng)
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
(pre_actions, bundle_meta)
|
2023-12-15 15:19:24 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
// 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 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| {
|
2024-01-09 10:48:15 -08:00
|
|
|
(
|
|
|
|
Bundle::from_parts(
|
|
|
|
actions,
|
|
|
|
flags,
|
|
|
|
result_value_balance,
|
|
|
|
anchor,
|
|
|
|
InProgress {
|
|
|
|
proof: Unproven { circuits },
|
|
|
|
sigs: Unauthorized { bsk },
|
|
|
|
},
|
|
|
|
),
|
|
|
|
bundle_meta,
|
2023-12-15 15:19:24 -08:00
|
|
|
)
|
|
|
|
}))
|
2021-04-14 21:14:34 -07:00
|
|
|
}
|
|
|
|
|
2021-07-28 05:45:24 -07:00
|
|
|
/// Marker trait representing bundle signatures in the process of being created.
|
2022-02-28 12:01:21 -08:00
|
|
|
pub trait InProgressSignatures: fmt::Debug {
|
2021-07-28 05:45:24 -07:00
|
|
|
/// The authorization type of an Orchard action in the process of being authorized.
|
2022-02-28 12:01:21 -08:00
|
|
|
type SpendAuth: fmt::Debug;
|
2021-07-28 05:45:24 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Marker for a bundle in the process of being built.
|
2022-02-11 18:04:52 -08:00
|
|
|
#[derive(Clone, Debug)]
|
2021-07-28 05:45:24 -07:00
|
|
|
pub struct InProgress<P, S: InProgressSignatures> {
|
|
|
|
proof: P,
|
|
|
|
sigs: S,
|
|
|
|
}
|
|
|
|
|
2022-02-28 12:01:21 -08:00
|
|
|
impl<P: fmt::Debug, S: InProgressSignatures> Authorization for InProgress<P, S> {
|
2021-07-28 05:45:24 -07:00
|
|
|
type SpendAuth = S::SpendAuth;
|
|
|
|
}
|
|
|
|
|
2021-07-28 05:49:35 -07:00
|
|
|
/// Marker for a bundle without a proof.
|
|
|
|
///
|
|
|
|
/// This struct contains the private data needed to create a [`Proof`] for a [`Bundle`].
|
2022-02-11 18:04:52 -08:00
|
|
|
#[derive(Clone, Debug)]
|
2021-07-28 05:49:35 -07:00
|
|
|
pub struct Unproven {
|
|
|
|
circuits: Vec<Circuit>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<S: InProgressSignatures> InProgress<Unproven, S> {
|
|
|
|
/// Creates the proof for this bundle.
|
|
|
|
pub fn create_proof(
|
|
|
|
&self,
|
|
|
|
pk: &ProvingKey,
|
|
|
|
instances: &[Instance],
|
2022-01-04 21:58:59 -08:00
|
|
|
rng: impl RngCore,
|
2022-01-28 07:53:03 -08:00
|
|
|
) -> Result<Proof, halo2_proofs::plonk::Error> {
|
2022-01-04 21:58:59 -08:00
|
|
|
Proof::create(pk, &self.proof.circuits, instances, rng)
|
2021-07-28 05:49:35 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<S: InProgressSignatures, V> Bundle<InProgress<Unproven, S>, V> {
|
|
|
|
/// Creates the proof for this bundle.
|
2022-01-04 21:58:59 -08:00
|
|
|
pub fn create_proof(
|
|
|
|
self,
|
|
|
|
pk: &ProvingKey,
|
|
|
|
mut rng: impl RngCore,
|
2022-12-01 12:58:26 -08:00
|
|
|
) -> Result<Bundle<InProgress<Proof, S>, V>, BuildError> {
|
2021-07-28 05:49:35 -07:00
|
|
|
let instances: Vec<_> = self
|
|
|
|
.actions()
|
|
|
|
.iter()
|
|
|
|
.map(|a| a.to_instance(*self.flags(), *self.anchor()))
|
|
|
|
.collect();
|
2022-04-28 17:16:16 -07:00
|
|
|
self.try_map_authorization(
|
2021-07-28 05:49:35 -07:00
|
|
|
&mut (),
|
|
|
|
|_, _, a| Ok(a),
|
|
|
|
|_, auth| {
|
2022-01-04 21:58:59 -08:00
|
|
|
let proof = auth.create_proof(pk, &instances, &mut rng)?;
|
2021-07-28 05:49:35 -07:00
|
|
|
Ok(InProgress {
|
|
|
|
proof,
|
|
|
|
sigs: auth.sigs,
|
|
|
|
})
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-05 14:35:52 -07:00
|
|
|
/// The parts needed to sign an [`Action`].
|
2022-02-11 18:04:52 -08:00
|
|
|
#[derive(Clone, Debug)]
|
2021-06-05 14:35:52 -07:00
|
|
|
pub struct SigningParts {
|
|
|
|
/// The spend validating key for this action. Used to match spend authorizing keys to
|
|
|
|
/// actions they can create signatures for.
|
|
|
|
ak: SpendValidatingKey,
|
|
|
|
/// The randomization needed to derive the actual signing key for this note.
|
|
|
|
alpha: pallas::Scalar,
|
|
|
|
}
|
|
|
|
|
2021-07-28 05:45:24 -07:00
|
|
|
/// Marker for an unauthorized bundle with no signatures.
|
2022-02-11 18:04:52 -08:00
|
|
|
#[derive(Clone, Debug)]
|
2021-04-14 21:14:34 -07:00
|
|
|
pub struct Unauthorized {
|
|
|
|
bsk: redpallas::SigningKey<Binding>,
|
|
|
|
}
|
|
|
|
|
2021-07-28 05:45:24 -07:00
|
|
|
impl InProgressSignatures for Unauthorized {
|
2021-04-28 15:38:08 -07:00
|
|
|
type SpendAuth = SigningMetadata;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Container for metadata needed to sign an [`Action`].
|
2022-02-11 18:04:52 -08:00
|
|
|
#[derive(Clone, Debug)]
|
2021-04-28 15:38:08 -07:00
|
|
|
pub struct SigningMetadata {
|
|
|
|
/// If this action is spending a dummy note, this field holds that note's spend
|
|
|
|
/// authorizing key.
|
|
|
|
///
|
|
|
|
/// These keys are used automatically in [`Bundle<Unauthorized>::prepare`] or
|
|
|
|
/// [`Bundle<Unauthorized>::apply_signatures`] to sign dummy spends.
|
|
|
|
dummy_ask: Option<SpendAuthorizingKey>,
|
2021-06-05 14:35:52 -07:00
|
|
|
parts: SigningParts,
|
2021-04-14 21:14:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Marker for a partially-authorized bundle, in the process of being signed.
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct PartiallyAuthorized {
|
|
|
|
binding_signature: redpallas::Signature<Binding>,
|
|
|
|
sighash: [u8; 32],
|
|
|
|
}
|
|
|
|
|
2021-07-28 05:45:24 -07:00
|
|
|
impl InProgressSignatures for PartiallyAuthorized {
|
2021-06-05 14:35:52 -07:00
|
|
|
type SpendAuth = MaybeSigned;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A heisen[`Signature`] for a particular [`Action`].
|
|
|
|
///
|
|
|
|
/// [`Signature`]: redpallas::Signature
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum MaybeSigned {
|
|
|
|
/// The information needed to sign this [`Action`].
|
|
|
|
SigningMetadata(SigningParts),
|
|
|
|
/// The signature for this [`Action`].
|
|
|
|
Signature(redpallas::Signature<SpendAuth>),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl MaybeSigned {
|
2022-12-01 12:58:26 -08:00
|
|
|
fn finalize(self) -> Result<redpallas::Signature<SpendAuth>, BuildError> {
|
2021-06-05 14:35:52 -07:00
|
|
|
match self {
|
|
|
|
Self::Signature(sig) => Ok(sig),
|
2022-12-01 12:58:26 -08:00
|
|
|
_ => Err(BuildError::MissingSignatures),
|
2021-06-05 14:35:52 -07:00
|
|
|
}
|
|
|
|
}
|
2021-04-14 21:14:34 -07:00
|
|
|
}
|
|
|
|
|
2022-02-28 12:01:21 -08:00
|
|
|
impl<P: fmt::Debug, V> Bundle<InProgress<P, Unauthorized>, V> {
|
2021-04-14 21:14:34 -07:00
|
|
|
/// Loads the sighash into this bundle, preparing it for signing.
|
|
|
|
///
|
|
|
|
/// This API ensures that all signatures are created over the same sighash.
|
2021-05-08 12:51:55 -07:00
|
|
|
pub fn prepare<R: RngCore + CryptoRng>(
|
2021-04-14 21:14:34 -07:00
|
|
|
self,
|
|
|
|
mut rng: R,
|
|
|
|
sighash: [u8; 32],
|
2021-07-28 05:45:24 -07:00
|
|
|
) -> Bundle<InProgress<P, PartiallyAuthorized>, V> {
|
2022-04-28 17:16:16 -07:00
|
|
|
self.map_authorization(
|
2021-04-14 21:14:34 -07:00
|
|
|
&mut rng,
|
2021-06-05 14:35:52 -07:00
|
|
|
|rng, _, SigningMetadata { dummy_ask, parts }| {
|
|
|
|
// We can create signatures for dummy spends immediately.
|
|
|
|
dummy_ask
|
|
|
|
.map(|ask| ask.randomize(&parts.alpha).sign(rng, &sighash))
|
|
|
|
.map(MaybeSigned::Signature)
|
|
|
|
.unwrap_or(MaybeSigned::SigningMetadata(parts))
|
2021-04-14 21:14:34 -07:00
|
|
|
},
|
2021-07-28 05:45:24 -07:00
|
|
|
|rng, auth| InProgress {
|
|
|
|
proof: auth.proof,
|
|
|
|
sigs: PartiallyAuthorized {
|
|
|
|
binding_signature: auth.sigs.bsk.sign(rng, &sighash),
|
|
|
|
sighash,
|
|
|
|
},
|
2021-04-14 21:14:34 -07:00
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
2021-07-28 05:45:24 -07:00
|
|
|
}
|
2021-04-14 21:14:34 -07:00
|
|
|
|
2021-07-28 05:45:24 -07:00
|
|
|
impl<V> Bundle<InProgress<Proof, Unauthorized>, V> {
|
2021-04-14 21:14:34 -07:00
|
|
|
/// Applies signatures to this bundle, in order to authorize it.
|
2022-05-04 07:57:08 -07:00
|
|
|
///
|
|
|
|
/// This is a helper method that wraps [`Bundle::prepare`], [`Bundle::sign`], and
|
|
|
|
/// [`Bundle::finalize`].
|
2021-05-08 12:51:55 -07:00
|
|
|
pub fn apply_signatures<R: RngCore + CryptoRng>(
|
2021-04-14 21:14:34 -07:00
|
|
|
self,
|
|
|
|
mut rng: R,
|
|
|
|
sighash: [u8; 32],
|
|
|
|
signing_keys: &[SpendAuthorizingKey],
|
2022-12-01 12:58:26 -08:00
|
|
|
) -> Result<Bundle<Authorized, V>, BuildError> {
|
2021-04-14 21:14:34 -07:00
|
|
|
signing_keys
|
|
|
|
.iter()
|
|
|
|
.fold(self.prepare(&mut rng, sighash), |partial, ask| {
|
|
|
|
partial.sign(&mut rng, ask)
|
|
|
|
})
|
|
|
|
.finalize()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-28 12:01:21 -08:00
|
|
|
impl<P: fmt::Debug, V> Bundle<InProgress<P, PartiallyAuthorized>, V> {
|
2021-04-14 21:14:34 -07:00
|
|
|
/// Signs this bundle with the given [`SpendAuthorizingKey`].
|
|
|
|
///
|
|
|
|
/// This will apply signatures for all notes controlled by this spending key.
|
2021-05-08 12:51:55 -07:00
|
|
|
pub fn sign<R: RngCore + CryptoRng>(self, mut rng: R, ask: &SpendAuthorizingKey) -> Self {
|
2021-04-14 21:14:34 -07:00
|
|
|
let expected_ak = ask.into();
|
2022-04-28 17:16:16 -07:00
|
|
|
self.map_authorization(
|
2021-04-14 21:14:34 -07:00
|
|
|
&mut rng,
|
2021-06-05 14:35:52 -07:00
|
|
|
|rng, partial, maybe| match maybe {
|
|
|
|
MaybeSigned::SigningMetadata(parts) if parts.ak == expected_ak => {
|
2021-07-28 05:45:24 -07:00
|
|
|
MaybeSigned::Signature(
|
|
|
|
ask.randomize(&parts.alpha).sign(rng, &partial.sigs.sighash),
|
|
|
|
)
|
2021-06-05 14:35:52 -07:00
|
|
|
}
|
|
|
|
s => s,
|
2021-04-14 21:14:34 -07:00
|
|
|
},
|
|
|
|
|_, partial| partial,
|
|
|
|
)
|
|
|
|
}
|
2022-03-16 08:17:47 -07:00
|
|
|
/// Appends externally computed [`Signature`]s.
|
|
|
|
///
|
|
|
|
/// Each signature will be applied to the one input for which it is valid. An error
|
|
|
|
/// will be returned if the signature is not valid for any inputs, or if it is valid
|
|
|
|
/// for more than one input.
|
|
|
|
///
|
|
|
|
/// [`Signature`]: redpallas::Signature
|
|
|
|
pub fn append_signatures(
|
|
|
|
self,
|
|
|
|
signatures: &[redpallas::Signature<SpendAuth>],
|
2022-12-01 12:58:26 -08:00
|
|
|
) -> Result<Self, BuildError> {
|
2022-05-06 12:05:24 -07:00
|
|
|
signatures.iter().try_fold(self, Self::append_signature)
|
2022-03-16 08:17:47 -07:00
|
|
|
}
|
|
|
|
|
2022-12-01 12:58:26 -08:00
|
|
|
fn append_signature(
|
|
|
|
self,
|
|
|
|
signature: &redpallas::Signature<SpendAuth>,
|
|
|
|
) -> Result<Self, BuildError> {
|
2022-03-16 08:17:47 -07:00
|
|
|
let mut signature_valid_for = 0usize;
|
|
|
|
let bundle = self.map_authorization(
|
|
|
|
&mut signature_valid_for,
|
|
|
|
|valid_for, partial, maybe| match maybe {
|
|
|
|
MaybeSigned::SigningMetadata(parts) => {
|
|
|
|
let rk = parts.ak.randomize(&parts.alpha);
|
|
|
|
if rk.verify(&partial.sigs.sighash[..], signature).is_ok() {
|
|
|
|
*valid_for += 1;
|
|
|
|
MaybeSigned::Signature(signature.clone())
|
|
|
|
} else {
|
|
|
|
// Signature isn't for this input.
|
|
|
|
MaybeSigned::SigningMetadata(parts)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
s => s,
|
|
|
|
},
|
|
|
|
|_, partial| partial,
|
|
|
|
);
|
|
|
|
match signature_valid_for {
|
2022-12-01 12:58:26 -08:00
|
|
|
0 => Err(BuildError::InvalidExternalSignature),
|
2022-03-16 08:17:47 -07:00
|
|
|
1 => Ok(bundle),
|
2022-12-01 12:58:26 -08:00
|
|
|
_ => Err(BuildError::DuplicateSignature),
|
2022-03-16 08:17:47 -07:00
|
|
|
}
|
|
|
|
}
|
2021-07-28 05:45:24 -07:00
|
|
|
}
|
2021-04-14 21:14:34 -07:00
|
|
|
|
2021-07-28 05:45:24 -07:00
|
|
|
impl<V> Bundle<InProgress<Proof, PartiallyAuthorized>, V> {
|
2021-04-14 21:14:34 -07:00
|
|
|
/// Finalizes this bundle, enabling it to be included in a transaction.
|
|
|
|
///
|
|
|
|
/// Returns an error if any signatures are missing.
|
2022-12-01 12:58:26 -08:00
|
|
|
pub fn finalize(self) -> Result<Bundle<Authorized, V>, BuildError> {
|
2022-04-28 17:16:16 -07:00
|
|
|
self.try_map_authorization(
|
2021-04-14 21:14:34 -07:00
|
|
|
&mut (),
|
2021-06-05 14:35:52 -07:00
|
|
|
|_, _, maybe| maybe.finalize(),
|
2021-04-14 21:14:34 -07:00
|
|
|
|_, partial| {
|
|
|
|
Ok(Authorized::from_parts(
|
|
|
|
partial.proof,
|
2021-07-28 05:45:24 -07:00
|
|
|
partial.sigs.binding_signature,
|
2021-04-14 21:14:34 -07:00
|
|
|
))
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-01 12:15:10 -08:00
|
|
|
/// A trait that provides a minimized view of an Orchard input suitable for use in
|
|
|
|
/// fee and change calculation.
|
|
|
|
pub trait InputView<NoteRef> {
|
|
|
|
/// An identifier for the input being spent.
|
|
|
|
fn note_id(&self) -> &NoteRef;
|
|
|
|
/// The value of the input being spent.
|
|
|
|
fn value<V: From<u64>>(&self) -> V;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl InputView<()> for SpendInfo {
|
|
|
|
fn note_id(&self) -> &() {
|
|
|
|
// The builder does not make use of note identifiers, so we can just return the unit value.
|
|
|
|
&()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn value<V: From<u64>>(&self) -> V {
|
|
|
|
V::from(self.note.value().inner())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A trait that provides a minimized view of an Orchard output suitable for use in
|
|
|
|
/// fee and change calculation.
|
|
|
|
pub trait OutputView {
|
|
|
|
/// The value of the output being produced.
|
|
|
|
fn value<V: From<u64>>(&self) -> V;
|
|
|
|
}
|
|
|
|
|
2023-12-12 10:12:10 -08:00
|
|
|
impl OutputView for OutputInfo {
|
2022-12-01 12:15:10 -08:00
|
|
|
fn value<V: From<u64>>(&self) -> V {
|
|
|
|
V::from(self.value.inner())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-27 06:49:49 -07:00
|
|
|
/// Generators for property testing.
|
|
|
|
#[cfg(any(test, feature = "test-dependencies"))]
|
2021-12-17 14:08:58 -08:00
|
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))]
|
2021-04-27 06:49:49 -07:00
|
|
|
pub mod testing {
|
2022-04-28 13:20:23 -07:00
|
|
|
use core::fmt::Debug;
|
2023-05-18 15:52:17 -07:00
|
|
|
use incrementalmerkletree::{frontier::Frontier, Hashable};
|
2021-04-29 08:55:11 -07:00
|
|
|
use rand::{rngs::StdRng, CryptoRng, SeedableRng};
|
2021-04-27 06:49:49 -07:00
|
|
|
|
|
|
|
use proptest::collection::vec;
|
|
|
|
use proptest::prelude::*;
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
address::testing::arb_address,
|
2023-12-20 18:00:47 -08:00
|
|
|
bundle::{Authorized, Bundle},
|
2021-04-27 06:49:49 -07:00
|
|
|
circuit::ProvingKey,
|
2022-03-30 04:53:46 -07:00
|
|
|
keys::{testing::arb_spending_key, FullViewingKey, SpendAuthorizingKey, SpendingKey},
|
2021-09-16 11:44:42 -07:00
|
|
|
note::testing::arb_note,
|
|
|
|
tree::{Anchor, MerkleHashOrchard, MerklePath},
|
2021-05-05 16:19:42 -07:00
|
|
|
value::{testing::arb_positive_note_value, NoteValue, MAX_NOTE_VALUE},
|
2021-04-27 12:56:36 -07:00
|
|
|
Address, Note,
|
2021-04-27 06:49:49 -07:00
|
|
|
};
|
|
|
|
|
2023-12-08 12:38:52 -08:00
|
|
|
use super::{Builder, BundleType};
|
2021-04-27 06:49:49 -07:00
|
|
|
|
2021-04-27 12:56:36 -07:00
|
|
|
/// An intermediate type used for construction of arbitrary
|
|
|
|
/// bundle values. This type is required because of a limitation
|
|
|
|
/// of the proptest prop_compose! macro which does not correctly
|
|
|
|
/// handle polymorphic generator functions. Instead of generating
|
|
|
|
/// a bundle directly, we generate the bundle inputs, and then
|
|
|
|
/// are able to use the `build` function to construct the bundle
|
|
|
|
/// from these inputs, but using a `ValueBalance` implementation that
|
|
|
|
/// is defined by the end user.
|
|
|
|
#[derive(Debug)]
|
2021-05-08 12:51:55 -07:00
|
|
|
struct ArbitraryBundleInputs<R> {
|
2021-04-29 08:55:11 -07:00
|
|
|
rng: R,
|
2021-04-27 12:56:36 -07:00
|
|
|
sk: SpendingKey,
|
|
|
|
anchor: Anchor,
|
2021-06-07 22:18:16 -07:00
|
|
|
notes: Vec<(Note, MerklePath)>,
|
2023-12-12 10:12:10 -08:00
|
|
|
output_amounts: Vec<(Address, NoteValue)>,
|
2021-04-27 12:56:36 -07:00
|
|
|
}
|
|
|
|
|
2021-05-08 12:51:55 -07:00
|
|
|
impl<R: RngCore + CryptoRng> ArbitraryBundleInputs<R> {
|
2021-04-27 12:56:36 -07:00
|
|
|
/// Create a bundle from the set of arbitrary bundle inputs.
|
2021-04-29 08:55:11 -07:00
|
|
|
fn into_bundle<V: TryFrom<i64>>(mut self) -> Bundle<Authorized, V> {
|
2021-04-27 12:56:36 -07:00
|
|
|
let fvk = FullViewingKey::from(&self.sk);
|
2023-12-20 18:00:47 -08:00
|
|
|
let mut builder = Builder::new(BundleType::DEFAULT, self.anchor);
|
2021-04-27 06:49:49 -07:00
|
|
|
|
2021-06-07 22:18:16 -07:00
|
|
|
for (note, path) in self.notes.into_iter() {
|
|
|
|
builder.add_spend(fvk.clone(), note, path).unwrap();
|
2021-04-27 06:49:49 -07:00
|
|
|
}
|
|
|
|
|
2023-12-12 10:12:10 -08:00
|
|
|
for (addr, value) in self.output_amounts.into_iter() {
|
2022-03-30 04:53:46 -07:00
|
|
|
let scope = fvk.scope_for_address(&addr).unwrap();
|
|
|
|
let ovk = fvk.to_ovk(scope);
|
|
|
|
|
2021-04-27 12:56:36 -07:00
|
|
|
builder
|
2023-12-12 10:12:10 -08:00
|
|
|
.add_output(Some(ovk.clone()), addr, value, None)
|
2021-04-27 12:56:36 -07:00
|
|
|
.unwrap();
|
2021-04-27 06:49:49 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
let pk = ProvingKey::build();
|
|
|
|
builder
|
2023-12-15 15:19:24 -08:00
|
|
|
.build(&mut self.rng)
|
|
|
|
.unwrap()
|
2021-07-28 05:49:35 -07:00
|
|
|
.unwrap()
|
2024-01-09 10:48:15 -08:00
|
|
|
.0
|
2022-01-04 21:58:59 -08:00
|
|
|
.create_proof(&pk, &mut self.rng)
|
2021-04-27 06:49:49 -07:00
|
|
|
.unwrap()
|
2021-05-08 12:51:55 -07:00
|
|
|
.prepare(&mut self.rng, [0; 32])
|
|
|
|
.sign(&mut self.rng, &SpendAuthorizingKey::from(&self.sk))
|
2021-04-27 06:49:49 -07:00
|
|
|
.finalize()
|
|
|
|
.unwrap()
|
|
|
|
}
|
|
|
|
}
|
2021-04-27 12:56:36 -07:00
|
|
|
|
|
|
|
prop_compose! {
|
|
|
|
/// Produce a random valid Orchard bundle.
|
2021-05-05 10:10:52 -07:00
|
|
|
fn arb_bundle_inputs(sk: SpendingKey)
|
|
|
|
(
|
2021-06-09 22:54:42 -07:00
|
|
|
n_notes in 1usize..30,
|
2023-12-12 10:12:10 -08:00
|
|
|
n_outputs in 1..30,
|
2022-04-26 12:31:46 -07:00
|
|
|
|
2021-05-05 10:10:52 -07:00
|
|
|
)
|
|
|
|
(
|
2021-09-16 11:44:42 -07:00
|
|
|
// generate note values that we're certain won't exceed MAX_NOTE_VALUE in total
|
|
|
|
notes in vec(
|
|
|
|
arb_positive_note_value(MAX_NOTE_VALUE / n_notes as u64).prop_flat_map(arb_note),
|
|
|
|
n_notes
|
|
|
|
),
|
2023-12-12 10:12:10 -08:00
|
|
|
output_amounts in vec(
|
2021-05-05 10:10:52 -07:00
|
|
|
arb_address().prop_flat_map(move |a| {
|
2023-12-12 10:12:10 -08:00
|
|
|
arb_positive_note_value(MAX_NOTE_VALUE / n_outputs as u64)
|
2021-05-28 04:06:50 -07:00
|
|
|
.prop_map(move |v| (a, v))
|
2021-05-05 10:10:52 -07:00
|
|
|
}),
|
2023-12-12 10:12:10 -08:00
|
|
|
n_outputs as usize
|
2021-04-27 12:56:36 -07:00
|
|
|
),
|
2021-04-29 08:55:11 -07:00
|
|
|
rng_seed in prop::array::uniform32(prop::num::u8::ANY)
|
2021-05-08 12:51:55 -07:00
|
|
|
) -> ArbitraryBundleInputs<StdRng> {
|
2023-05-18 15:52:17 -07:00
|
|
|
use crate::constants::MERKLE_DEPTH_ORCHARD;
|
|
|
|
let mut frontier = Frontier::<MerkleHashOrchard, { MERKLE_DEPTH_ORCHARD as u8 }>::empty();
|
2021-09-16 11:44:42 -07:00
|
|
|
let mut notes_and_auth_paths: Vec<(Note, MerklePath)> = Vec::new();
|
|
|
|
|
|
|
|
for note in notes.iter() {
|
|
|
|
let leaf = MerkleHashOrchard::from_cmx(¬e.commitment().into());
|
2023-05-18 15:52:17 -07:00
|
|
|
frontier.append(leaf);
|
|
|
|
|
|
|
|
let path = frontier
|
|
|
|
.witness(|addr| Some(<MerkleHashOrchard as Hashable>::empty_root(addr.level())))
|
|
|
|
.ok()
|
|
|
|
.flatten()
|
|
|
|
.expect("we can always construct a correct Merkle path");
|
|
|
|
notes_and_auth_paths.push((*note, path.into()));
|
2021-09-16 11:44:42 -07:00
|
|
|
}
|
|
|
|
|
2021-04-27 12:56:36 -07:00
|
|
|
ArbitraryBundleInputs {
|
2021-04-29 08:55:11 -07:00
|
|
|
rng: StdRng::from_seed(rng_seed),
|
2021-07-15 00:18:01 -07:00
|
|
|
sk,
|
2023-05-18 15:52:17 -07:00
|
|
|
anchor: frontier.root().into(),
|
2021-06-08 00:27:08 -07:00
|
|
|
notes: notes_and_auth_paths,
|
2023-12-12 10:12:10 -08:00
|
|
|
output_amounts
|
2021-04-27 12:56:36 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Produce an arbitrary valid Orchard bundle using a random spending key.
|
|
|
|
pub fn arb_bundle<V: TryFrom<i64> + Debug>() -> impl Strategy<Value = Bundle<Authorized, V>> {
|
|
|
|
arb_spending_key()
|
|
|
|
.prop_flat_map(arb_bundle_inputs)
|
|
|
|
.prop_map(|inputs| inputs.into_bundle::<V>())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Produce an arbitrary valid Orchard bundle using a specified spending key.
|
|
|
|
pub fn arb_bundle_with_key<V: TryFrom<i64> + Debug>(
|
|
|
|
k: SpendingKey,
|
|
|
|
) -> impl Strategy<Value = Bundle<Authorized, V>> {
|
|
|
|
arb_bundle_inputs(k).prop_map(|inputs| inputs.into_bundle::<V>())
|
|
|
|
}
|
2021-04-27 06:49:49 -07:00
|
|
|
}
|
|
|
|
|
2021-04-14 21:14:34 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use rand::rngs::OsRng;
|
|
|
|
|
|
|
|
use super::Builder;
|
|
|
|
use crate::{
|
2023-12-08 12:38:52 -08:00
|
|
|
builder::BundleType,
|
2023-12-20 18:00:47 -08:00
|
|
|
bundle::{Authorized, Bundle},
|
2021-04-14 21:14:34 -07:00
|
|
|
circuit::ProvingKey,
|
2021-06-10 22:02:05 -07:00
|
|
|
constants::MERKLE_DEPTH_ORCHARD,
|
2022-03-30 04:53:46 -07:00
|
|
|
keys::{FullViewingKey, Scope, SpendingKey},
|
2021-06-10 22:02:05 -07:00
|
|
|
tree::EMPTY_ROOTS,
|
2021-04-27 06:49:49 -07:00
|
|
|
value::NoteValue,
|
2021-04-14 21:14:34 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn shielding_bundle() {
|
|
|
|
let pk = ProvingKey::build();
|
|
|
|
let mut rng = OsRng;
|
|
|
|
|
|
|
|
let sk = SpendingKey::random(&mut rng);
|
|
|
|
let fvk = FullViewingKey::from(&sk);
|
2022-03-30 04:53:46 -07:00
|
|
|
let recipient = fvk.address_at(0u32, Scope::External);
|
2021-04-14 21:14:34 -07:00
|
|
|
|
2023-12-20 18:00:47 -08:00
|
|
|
let mut builder = Builder::new(
|
|
|
|
BundleType::DEFAULT,
|
2021-06-10 22:02:05 -07:00
|
|
|
EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(),
|
2023-12-20 18:00:47 -08:00
|
|
|
);
|
2021-06-10 22:02:05 -07:00
|
|
|
|
2021-04-14 21:14:34 -07:00
|
|
|
builder
|
2023-12-12 10:12:10 -08:00
|
|
|
.add_output(None, recipient, NoteValue::from_raw(5000), None)
|
2021-04-14 21:14:34 -07:00
|
|
|
.unwrap();
|
2022-09-19 05:41:41 -07:00
|
|
|
let balance: i64 = builder.value_balance().unwrap();
|
|
|
|
assert_eq!(balance, -5000);
|
|
|
|
|
2021-06-05 14:39:57 -07:00
|
|
|
let bundle: Bundle<Authorized, i64> = builder
|
2023-12-15 15:19:24 -08:00
|
|
|
.build(&mut rng)
|
|
|
|
.unwrap()
|
2021-07-28 05:49:35 -07:00
|
|
|
.unwrap()
|
2024-01-09 10:48:15 -08:00
|
|
|
.0
|
2022-01-04 21:58:59 -08:00
|
|
|
.create_proof(&pk, &mut rng)
|
2021-04-14 21:14:34 -07:00
|
|
|
.unwrap()
|
2023-03-10 13:23:06 -08:00
|
|
|
.prepare(rng, [0; 32])
|
2021-06-05 14:39:57 -07:00
|
|
|
.finalize()
|
|
|
|
.unwrap();
|
2021-04-27 06:49:49 -07:00
|
|
|
assert_eq!(bundle.value_balance(), &(-5000))
|
2021-04-14 21:14:34 -07:00
|
|
|
}
|
|
|
|
}
|