//! Logic for building Orchard components of transactions. use core::fmt; use core::iter; use std::fmt::Display; use ff::Field; use nonempty::NonEmpty; use pasta_curves::pallas; use rand::{prelude::SliceRandom, CryptoRng, RngCore}; use crate::{ action::Action, address::Address, bundle::{Authorization, Authorized, Bundle, Flags}, circuit::{Circuit, Instance, Proof, ProvingKey}, keys::{ FullViewingKey, OutgoingViewingKey, Scope, SpendAuthorizingKey, SpendValidatingKey, SpendingKey, }, note::{Note, TransmittedNoteCiphertext}, note_encryption::OrchardNoteEncryption, primitives::redpallas::{self, Binding, SpendAuth}, tree::{Anchor, MerklePath}, value::{self, NoteValue, OverflowError, ValueCommitTrapdoor, ValueCommitment, ValueSum}, }; const MIN_ACTIONS: usize = 2; /// An error type for the kinds of errors that can occur during bundle construction. #[derive(Debug)] pub enum BuildError { /// A bundle could not be built because required signatures were missing. MissingSignatures, /// An error occurred in the process of producing a proof for a bundle. Proof(halo2_proofs::plonk::Error), /// An overflow error occurred while attempting to construct the value /// for a bundle. ValueSum(value::OverflowError), /// 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, } 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"), Proof(e) => f.write_str(&format!("Could not create proof: {}", e)), ValueSum(_) => f.write_str("Overflow occurred during value construction"), InvalidExternalSignature => f.write_str("External signature was invalid"), DuplicateSignature => f.write_str("Signature valid for more than one input"), } } } impl std::error::Error for BuildError {} /// An error type for adding a spend to the builder. #[derive(Debug, PartialEq, Eq)] 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, } 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 {} /// The only error that can occur here is if outputs are disabled for this builder. #[derive(Debug, PartialEq, Eq)] 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 {} impl From for BuildError { fn from(e: halo2_proofs::plonk::Error) -> Self { BuildError::Proof(e) } } impl From for BuildError { fn from(e: value::OverflowError) -> Self { BuildError::ValueSum(e) } } /// Information about a specific note to be spent in an [`Action`]. #[derive(Debug)] pub struct SpendInfo { pub(crate) dummy_sk: Option, pub(crate) fvk: FullViewingKey, pub(crate) scope: Scope, pub(crate) note: Note, pub(crate) merkle_path: MerklePath, } impl SpendInfo { /// 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 { let scope = fvk.scope_for_address(¬e.recipient())?; Some(SpendInfo { dummy_sk: None, fvk, scope, note, merkle_path, }) } /// 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, // 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, note, merkle_path, } } } /// Information about a specific recipient to receive funds in an [`Action`]. #[derive(Debug)] struct RecipientInfo { ovk: Option, recipient: Address, value: NoteValue, memo: Option<[u8; 512]>, } impl RecipientInfo { /// 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 fvk: FullViewingKey = (&SpendingKey::random(rng)).into(); let recipient = fvk.address_at(0u32, Scope::External); RecipientInfo { ovk: None, recipient, value: NoteValue::zero(), memo: None, } } } /// Information about a specific [`Action`] we plan to build. #[derive(Debug)] struct ActionInfo { spend: SpendInfo, output: RecipientInfo, rcv: ValueCommitTrapdoor, } impl ActionInfo { fn new(spend: SpendInfo, output: RecipientInfo, rng: impl RngCore) -> Self { ActionInfo { spend, output, rcv: ValueCommitTrapdoor::random(rng), } } /// Returns the value sum for this action. fn value_sum(&self) -> ValueSum { 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 fn build(self, mut rng: impl RngCore) -> (Action, Circuit) { let v_net = self.value_sum(); let cv_net = ValueCommitment::derive(v_net, self.rcv.clone()); let nf_old = self.spend.note.nullifier(&self.spend.fvk); let ak: SpendValidatingKey = self.spend.fvk.clone().into(); let alpha = pallas::Scalar::random(&mut rng); let rk = ak.randomize(&alpha); let note = Note::new(self.output.recipient, self.output.value, nf_old, &mut rng); let cm_new = note.commitment(); let cmx = cm_new.into(); let encryptor = OrchardNoteEncryption::new( self.output.ovk, note, self.output.memo.unwrap_or_else(|| { let mut memo = [0; 512]; memo[0] = 0xf6; memo }), ); let encrypted_note = TransmittedNoteCiphertext { epk_bytes: encryptor.epk().to_bytes().0, enc_ciphertext: encryptor.encrypt_note_plaintext(), out_ciphertext: encryptor.encrypt_outgoing_plaintext(&cv_net, &cmx, &mut rng), }; ( Action::from_parts( nf_old, rk, cmx, encrypted_note, cv_net, SigningMetadata { dummy_ask: self.spend.dummy_sk.as_ref().map(SpendAuthorizingKey::from), parts: SigningParts { ak, alpha }, }, ), Circuit::from_action_context_unchecked(self.spend, note, alpha, self.rcv), ) } } /// A builder that constructs a [`Bundle`] from a set of notes to be spent, and recipients /// to receive funds. #[derive(Debug)] pub struct Builder { spends: Vec, recipients: Vec, flags: Flags, anchor: Anchor, } impl Builder { /// Constructs a new empty builder for an Orchard bundle. pub fn new(flags: Flags, anchor: Anchor) -> Self { Builder { spends: vec![], recipients: vec![], flags, anchor, } } /// Adds a note to be spent in this transaction. /// /// - `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`]. /// /// Returns an error if the given Merkle path does not have the required anchor for /// the given note. /// /// [`OrchardDomain`]: crate::note_encryption::OrchardDomain /// [`MerkleHashOrchard`]: crate::tree::MerkleHashOrchard pub fn add_spend( &mut self, fvk: FullViewingKey, note: Note, merkle_path: MerklePath, ) -> Result<(), SpendError> { if !self.flags.spends_enabled() { return Err(SpendError::SpendsDisabled); } // Consistency check: all anchors must be equal. let cm = note.commitment(); let path_root = merkle_path.root(cm.into()); if path_root != self.anchor { return Err(SpendError::AnchorMismatch); } // Check if note is internal or external. 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(()) } /// Adds an address which will receive funds in this transaction. pub fn add_recipient( &mut self, ovk: Option, recipient: Address, value: NoteValue, memo: Option<[u8; 512]>, ) -> Result<(), OutputError> { if !self.flags.outputs_enabled() { return Err(OutputError); } self.recipients.push(RecipientInfo { ovk, recipient, value, memo, }); Ok(()) } /// Returns the action spend components that will be produced by the /// transaction being constructed pub fn spends(&self) -> &Vec> { &self.spends } /// Returns the action output components that will be produced by the /// transaction being constructed pub fn outputs(&self) -> &Vec { &self.recipients } /// 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 /// 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 pub fn value_balance>(&self) -> Result { let value_balance = self .spends .iter() .map(|spend| spend.note.value() - NoteValue::zero()) .chain( self.recipients .iter() .map(|recipient| NoteValue::zero() - recipient.value), ) .fold(Some(ValueSum::zero()), |acc, note_value| acc? + note_value) .ok_or(OverflowError)?; i64::try_from(value_balance).and_then(|i| V::try_from(i).map_err(|_| value::OverflowError)) } /// Builds a bundle containing the given spent notes and recipients. /// /// The returned bundle will have no proof or signatures; these can be applied with /// [`Bundle::create_proof`] and [`Bundle::apply_signatures`] respectively. pub fn build>( mut self, mut rng: impl RngCore, ) -> Result, V>, BuildError> { // Pair up the spends and recipients, extending with dummy values as necessary. let pre_actions: Vec<_> = { let num_spends = self.spends.len(); let num_recipients = self.recipients.len(); let num_actions = [num_spends, num_recipients, MIN_ACTIONS] .iter() .max() .cloned() .unwrap(); self.spends.extend( iter::repeat_with(|| SpendInfo::dummy(&mut rng)).take(num_actions - num_spends), ); self.recipients.extend( iter::repeat_with(|| RecipientInfo::dummy(&mut rng)) .take(num_actions - num_recipients), ); // Shuffle the spends and recipients, so that learning the position of a // specific spent note or output note doesn't reveal anything on its own about // the meaning of that note in the transaction context. self.spends.shuffle(&mut rng); self.recipients.shuffle(&mut rng); self.spends .into_iter() .zip(self.recipients.into_iter()) .map(|(spend, recipient)| ActionInfo::new(spend, recipient, &mut rng)) .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. 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::() .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::derive(value_balance, ValueCommitTrapdoor::zero())) .into_bvk(); assert_eq!(redpallas::VerificationKey::from(&bsk), bvk); Ok(Bundle::from_parts( NonEmpty::from_vec(actions).unwrap(), flags, result_value_balance, anchor, InProgress { proof: Unproven { circuits }, sigs: Unauthorized { bsk }, }, )) } } /// Marker trait representing bundle signatures in the process of being created. pub trait InProgressSignatures: fmt::Debug { /// The authorization type of an Orchard action in the process of being authorized. type SpendAuth: fmt::Debug; } /// Marker for a bundle in the process of being built. #[derive(Clone, Debug)] pub struct InProgress { proof: P, sigs: S, } impl Authorization for InProgress { type SpendAuth = S::SpendAuth; } /// Marker for a bundle without a proof. /// /// This struct contains the private data needed to create a [`Proof`] for a [`Bundle`]. #[derive(Clone, Debug)] pub struct Unproven { circuits: Vec, } impl InProgress { /// Creates the proof for this bundle. pub fn create_proof( &self, pk: &ProvingKey, instances: &[Instance], rng: impl RngCore, ) -> Result { Proof::create(pk, &self.proof.circuits, instances, rng) } } impl Bundle, V> { /// Creates the proof for this bundle. pub fn create_proof( self, pk: &ProvingKey, mut rng: impl RngCore, ) -> Result, V>, BuildError> { let instances: Vec<_> = self .actions() .iter() .map(|a| a.to_instance(*self.flags(), *self.anchor())) .collect(); self.try_map_authorization( &mut (), |_, _, a| Ok(a), |_, auth| { let proof = auth.create_proof(pk, &instances, &mut rng)?; Ok(InProgress { proof, sigs: auth.sigs, }) }, ) } } /// The parts needed to sign an [`Action`]. #[derive(Clone, Debug)] 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, } /// Marker for an unauthorized bundle with no signatures. #[derive(Clone, Debug)] pub struct Unauthorized { bsk: redpallas::SigningKey, } impl InProgressSignatures for Unauthorized { type SpendAuth = SigningMetadata; } /// Container for metadata needed to sign an [`Action`]. #[derive(Clone, Debug)] 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::prepare`] or /// [`Bundle::apply_signatures`] to sign dummy spends. dummy_ask: Option, parts: SigningParts, } /// Marker for a partially-authorized bundle, in the process of being signed. #[derive(Debug)] pub struct PartiallyAuthorized { binding_signature: redpallas::Signature, sighash: [u8; 32], } impl InProgressSignatures for PartiallyAuthorized { 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), } impl MaybeSigned { fn finalize(self) -> Result, BuildError> { match self { Self::Signature(sig) => Ok(sig), _ => Err(BuildError::MissingSignatures), } } } impl Bundle, V> { /// Loads the sighash into this bundle, preparing it for signing. /// /// This API ensures that all signatures are created over the same sighash. pub fn prepare( self, mut rng: R, sighash: [u8; 32], ) -> Bundle, V> { self.map_authorization( &mut rng, |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)) }, |rng, auth| InProgress { proof: auth.proof, sigs: PartiallyAuthorized { binding_signature: auth.sigs.bsk.sign(rng, &sighash), sighash, }, }, ) } } impl Bundle, V> { /// Applies signatures to this bundle, in order to authorize it. /// /// This is a helper method that wraps [`Bundle::prepare`], [`Bundle::sign`], and /// [`Bundle::finalize`]. pub fn apply_signatures( self, mut rng: R, sighash: [u8; 32], signing_keys: &[SpendAuthorizingKey], ) -> Result, BuildError> { signing_keys .iter() .fold(self.prepare(&mut rng, sighash), |partial, ask| { partial.sign(&mut rng, ask) }) .finalize() } } impl Bundle, V> { /// Signs this bundle with the given [`SpendAuthorizingKey`]. /// /// This will apply signatures for all notes controlled by this spending key. pub fn sign(self, mut rng: R, ask: &SpendAuthorizingKey) -> Self { let expected_ak = ask.into(); self.map_authorization( &mut rng, |rng, partial, maybe| match maybe { MaybeSigned::SigningMetadata(parts) if parts.ak == expected_ak => { MaybeSigned::Signature( ask.randomize(&parts.alpha).sign(rng, &partial.sigs.sighash), ) } s => s, }, |_, partial| partial, ) } /// 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], ) -> Result { signatures.iter().try_fold(self, Self::append_signature) } fn append_signature( self, signature: &redpallas::Signature, ) -> Result { 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 { 0 => Err(BuildError::InvalidExternalSignature), 1 => Ok(bundle), _ => Err(BuildError::DuplicateSignature), } } } impl Bundle, V> { /// Finalizes this bundle, enabling it to be included in a transaction. /// /// Returns an error if any signatures are missing. pub fn finalize(self) -> Result, BuildError> { self.try_map_authorization( &mut (), |_, _, maybe| maybe.finalize(), |_, partial| { Ok(Authorized::from_parts( partial.proof, partial.sigs.binding_signature, )) }, ) } } /// A trait that provides a minimized view of an Orchard input suitable for use in /// fee and change calculation. pub trait InputView { /// An identifier for the input being spent. fn note_id(&self) -> &NoteRef; /// The value of the input being spent. fn value>(&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>(&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>(&self) -> V; } impl OutputView for RecipientInfo { fn value>(&self) -> V { V::from(self.value.inner()) } } /// Generators for property testing. #[cfg(any(test, feature = "test-dependencies"))] #[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))] pub mod testing { use bridgetree::BridgeTree; use core::fmt::Debug; use rand::{rngs::StdRng, CryptoRng, SeedableRng}; use proptest::collection::vec; use proptest::prelude::*; use crate::{ address::testing::arb_address, bundle::{Authorized, Bundle, Flags}, circuit::ProvingKey, keys::{testing::arb_spending_key, FullViewingKey, SpendAuthorizingKey, SpendingKey}, note::testing::arb_note, tree::{Anchor, MerkleHashOrchard, MerklePath}, value::{testing::arb_positive_note_value, NoteValue, MAX_NOTE_VALUE}, Address, Note, }; use super::Builder; /// 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)] struct ArbitraryBundleInputs { rng: R, sk: SpendingKey, anchor: Anchor, notes: Vec<(Note, MerklePath)>, recipient_amounts: Vec<(Address, NoteValue)>, } impl ArbitraryBundleInputs { /// Create a bundle from the set of arbitrary bundle inputs. fn into_bundle>(mut self) -> Bundle { let fvk = FullViewingKey::from(&self.sk); let flags = Flags::from_parts(true, true); let mut builder = Builder::new(flags, self.anchor); for (note, path) in self.notes.into_iter() { builder.add_spend(fvk.clone(), note, path).unwrap(); } for (addr, value) in self.recipient_amounts.into_iter() { let scope = fvk.scope_for_address(&addr).unwrap(); let ovk = fvk.to_ovk(scope); builder .add_recipient(Some(ovk.clone()), addr, value, None) .unwrap(); } let pk = ProvingKey::build(); builder .build(&mut self.rng) .unwrap() .create_proof(&pk, &mut self.rng) .unwrap() .prepare(&mut self.rng, [0; 32]) .sign(&mut self.rng, &SpendAuthorizingKey::from(&self.sk)) .finalize() .unwrap() } } prop_compose! { /// Produce a random valid Orchard bundle. fn arb_bundle_inputs(sk: SpendingKey) ( n_notes in 1usize..30, n_recipients in 1..30, ) ( // 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 ), recipient_amounts in vec( arb_address().prop_flat_map(move |a| { arb_positive_note_value(MAX_NOTE_VALUE / n_recipients as u64) .prop_map(move |v| (a, v)) }), n_recipients as usize ), rng_seed in prop::array::uniform32(prop::num::u8::ANY) ) -> ArbitraryBundleInputs { const MERKLE_DEPTH_ORCHARD: u8 = crate::constants::MERKLE_DEPTH_ORCHARD as u8; let mut tree = BridgeTree::::new(100, 0); let mut notes_and_auth_paths: Vec<(Note, MerklePath)> = Vec::new(); for note in notes.iter() { let leaf = MerkleHashOrchard::from_cmx(¬e.commitment().into()); tree.append(leaf); let position = tree.mark().expect("tree is not empty"); let path = MerklePath::from((position, tree.witness(position, 0).expect("we just witnessed the path"))); notes_and_auth_paths.push((*note, path)); } ArbitraryBundleInputs { rng: StdRng::from_seed(rng_seed), sk, anchor: tree.root(0).unwrap().into(), notes: notes_and_auth_paths, recipient_amounts } } } /// Produce an arbitrary valid Orchard bundle using a random spending key. pub fn arb_bundle + Debug>() -> impl Strategy> { arb_spending_key() .prop_flat_map(arb_bundle_inputs) .prop_map(|inputs| inputs.into_bundle::()) } /// Produce an arbitrary valid Orchard bundle using a specified spending key. pub fn arb_bundle_with_key + Debug>( k: SpendingKey, ) -> impl Strategy> { arb_bundle_inputs(k).prop_map(|inputs| inputs.into_bundle::()) } } #[cfg(test)] mod tests { use rand::rngs::OsRng; use super::Builder; use crate::{ bundle::{Authorized, Bundle, Flags}, circuit::ProvingKey, constants::MERKLE_DEPTH_ORCHARD, keys::{FullViewingKey, Scope, SpendingKey}, tree::EMPTY_ROOTS, value::NoteValue, }; #[test] fn shielding_bundle() { let pk = ProvingKey::build(); let mut rng = OsRng; let sk = SpendingKey::random(&mut rng); let fvk = FullViewingKey::from(&sk); let recipient = fvk.address_at(0u32, Scope::External); let mut builder = Builder::new( Flags::from_parts(true, true), EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(), ); builder .add_recipient(None, recipient, NoteValue::from_raw(5000), None) .unwrap(); let balance: i64 = builder.value_balance().unwrap(); assert_eq!(balance, -5000); let bundle: Bundle = builder .build(&mut rng) .unwrap() .create_proof(&pk, &mut rng) .unwrap() .prepare(rng, [0; 32]) .finalize() .unwrap(); assert_eq!(bundle.value_balance(), &(-5000)) } }