//! Structs related to bundles of Orchard actions. use nonempty::NonEmpty; use crate::{ circuit::{Instance, Proof}, note::{ExtractedNoteCommitment, Nullifier, TransmittedNoteCiphertext}, primitives::redpallas::{self, Binding, SpendAuth}, tree::Anchor, value::ValueCommitment, }; /// An action applied to the global ledger. /// /// Externally, this both creates a note (adding a commitment to the global ledger), /// and consumes some note created prior to this action (adding a nullifier to the /// global ledger). /// /// Internally, this may both consume a note and create a note, or it may do only one of /// the two. TODO: Determine which is more efficient (circuit size vs bundle size). #[derive(Debug)] pub struct Action { /// The nullifier of the note being spent. nf: Nullifier, /// The randomized verification key for the note being spent. rk: redpallas::VerificationKey, /// A commitment to the new note being created. cmx: ExtractedNoteCommitment, /// The transmitted note ciphertext encrypted_note: TransmittedNoteCiphertext, /// A commitment to the net value created or consumed by this action. cv_net: ValueCommitment, /// The authorization for this action. authorization: A, } impl Action { /// Constructs an `Action` from its constituent parts. pub fn from_parts( nf: Nullifier, rk: redpallas::VerificationKey, cmx: ExtractedNoteCommitment, encrypted_note: TransmittedNoteCiphertext, cv_net: ValueCommitment, authorization: T, ) -> Self { Action { nf, rk, cmx, encrypted_note, cv_net, authorization, } } /// Returns the nullifier of the note being spent. pub fn nullifier(&self) -> &Nullifier { &self.nf } /// Returns the randomized verification key for the note being spent. pub fn rk(&self) -> &redpallas::VerificationKey { &self.rk } /// Returns the commitment to the new note being created. pub fn cmx(&self) -> &ExtractedNoteCommitment { &self.cmx } /// Returns the encrypted note ciphertext. pub fn encrypted_note(&self) -> &TransmittedNoteCiphertext { &self.encrypted_note } /// Returns the commitment to the net value created or consumed by this action. pub fn cv_net(&self) -> &ValueCommitment { &self.cv_net } /// Returns the authorization for this action. pub fn authorization(&self) -> &T { &self.authorization } /// Transitions this action from one authorization state to another. pub fn map(self, step: impl FnOnce(T) -> U) -> Action { Action { nf: self.nf, rk: self.rk, cmx: self.cmx, encrypted_note: self.encrypted_note, cv_net: self.cv_net, authorization: step(self.authorization), } } /// Transitions this action from one authorization state to another. pub fn try_map(self, step: impl FnOnce(T) -> Result) -> Result, E> { Ok(Action { nf: self.nf, rk: self.rk, cmx: self.cmx, encrypted_note: self.encrypted_note, cv_net: self.cv_net, authorization: step(self.authorization)?, }) } pub(crate) fn to_instance(&self, flags: Flags, anchor: Anchor) -> Instance { Instance { anchor, cv_net: self.cv_net.clone(), nf_old: self.nf.clone(), rk: self.rk.clone(), cmx: self.cmx.clone(), enable_spend: flags.spends_enabled, enable_output: flags.outputs_enabled, } } } /// Orchard-specific flags. #[derive(Clone, Copy, Debug)] pub struct Flags { /// Flag denoting whether Orchard spends are enabled in the transaction. /// /// If `false`, spent notes within [`Action`]s in the transaction's [`Bundle`] are /// guaranteed to be dummy notes. If `true`, the spent notes may be either real or /// dummy notes. spends_enabled: bool, /// Flag denoting whether Orchard outputs are enabled in the transaction. /// /// If `false`, created notes within [`Action`]s in the transaction's [`Bundle`] are /// guaranteed to be dummy notes. If `true`, the created notes may be either real or /// dummy notes. outputs_enabled: bool, } impl Flags { /// Construct a set of flags from its constituent parts pub fn from_parts(spends_enabled: bool, outputs_enabled: bool) -> Self { Flags { spends_enabled, outputs_enabled, } } /// Flag denoting whether Orchard spends are enabled in the transaction. /// /// If `false`, spent notes within [`Action`]s in the transaction's [`Bundle`] are /// guaranteed to be dummy notes. If `true`, the spent notes may be either real or /// dummy notes. pub fn spends_enabled(&self) -> bool { self.spends_enabled } /// Flag denoting whether Orchard outputs are enabled in the transaction. /// /// If `false`, created notes within [`Action`]s in the transaction's [`Bundle`] are /// guaranteed to be dummy notes. If `true`, the created notes may be either real or /// dummy notes. pub fn outputs_enabled(&self) -> bool { self.outputs_enabled } } /// Defines the authorization type of an Orchard bundle. pub trait Authorization { /// The authorization type of an Orchard action. type SpendAuth; } /// Authorizing data for a bundle of actions, ready to be committed to the ledger. #[derive(Debug)] pub struct Authorized { proof: Proof, binding_signature: redpallas::Signature, } impl Authorized { /// Construct a new value with authorizing data. pub fn new(proof: Proof, binding_signature: redpallas::Signature) -> Self { Authorized { proof, binding_signature, } } /// Return the proof component of the authorizing data. pub fn proof(&self) -> &Proof { &self.proof } /// Return the binding signature. pub fn binding_signature(&self) -> &redpallas::Signature { &self.binding_signature } } impl Authorization for Authorized { type SpendAuth = redpallas::Signature; } /// A bundle of actions to be applied to the ledger. #[derive(Debug)] pub struct Bundle { /// The list of actions that make up this bundle. actions: NonEmpty>, /// Orchard-specific transaction-level flags for this bundle. flags: Flags, /// The net value moved out of the Orchard shielded pool. /// /// This is the sum of Orchard spends minus the sum of Orchard outputs. value_balance: V, /// The root of the Orchard commitment tree that this bundle commits to. anchor: Anchor, /// The authorization for this bundle. authorization: T, } impl Bundle { /// Constructs a `Bundle` from its constituent parts. pub fn from_parts( actions: NonEmpty>, flags: Flags, value_balance: V, anchor: Anchor, authorization: T, ) -> Self { Bundle { actions, flags, value_balance, anchor, authorization, } } /// Returns the list of actions that make up this bundle. pub fn actions(&self) -> &NonEmpty> { &self.actions } /// Returns the Orchard-specific transaction-level flags for this bundle. pub fn flags(&self) -> &Flags { &self.flags } /// Returns the net value moved into or out of the Orchard shielded pool. /// /// This is the sum of Orchard spends minus the sum Orchard outputs. pub fn value_balance(&self) -> &V { &self.value_balance } /// Returns the root of the Orchard commitment tree that this bundle commits to. pub fn anchor(&self) -> &Anchor { &self.anchor } /// Returns the authorization for this bundle. /// /// In the case of a `Bundle`, this is the proof and binding signature. pub fn authorization(&self) -> &T { &self.authorization } /// Computes a commitment to the effects of this bundle, suitable for inclusion within /// a transaction ID. pub fn commitment(&self) -> BundleCommitment { todo!() } /// Transitions this bundle from one authorization state to another. pub fn authorize( self, context: &mut R, mut spend_auth: impl FnMut(&mut R, &T, T::SpendAuth) -> U::SpendAuth, step: impl FnOnce(&mut R, T) -> U, ) -> Bundle { let authorization = self.authorization; Bundle { actions: self .actions .map(|a| a.map(|a_auth| spend_auth(context, &authorization, a_auth))), flags: self.flags, value_balance: self.value_balance, anchor: self.anchor, authorization: step(context, authorization), } } /// Transitions this bundle from one authorization state to another. pub fn try_authorize( self, context: &mut R, mut spend_auth: impl FnMut(&mut R, &T, T::SpendAuth) -> Result, step: impl FnOnce(&mut R, T) -> Result, ) -> Result, E> { let authorization = self.authorization; let new_actions = self .actions .into_iter() .map(|a| a.try_map(|a_auth| spend_auth(context, &authorization, a_auth))) .collect::, E>>()?; Ok(Bundle { actions: NonEmpty::from_vec(new_actions).unwrap(), flags: self.flags, value_balance: self.value_balance, anchor: self.anchor, authorization: step(context, authorization)?, }) } } /// Authorizing data for a bundle of actions, ready to be committed to the ledger. #[derive(Debug)] pub struct BundleAuth { /// The authorizing data for the actions in a bundle pub action_authorizations: NonEmpty<::SpendAuth>, /// The authorizing data that covers the bundle as a whole pub authorization: Authorized, } /// Errors that may be generated in the process of constructing bundle authorizing data. #[derive(Debug)] pub enum BundleAuthError { /// An error produced by the underlying computation of authorizing data for a bundle Wrapped(E), /// Authorizing data for the bundle could not be matched to bundle contents. AuthLengthMismatch(usize, usize), } //impl Bundle { // /// Compute the authorizing data for a bundle and apply it to the bundle, returning the // /// authorized result. // pub fn with_auth Result>( // self, // f: F, // ) -> Result, BundleAuthError> { // let auth = f(&self).map_err(BundleAuthError::Wrapped)?; // let actions_len = self.actions.len(); // // if actions_len != auth.action_authorizations.len() { // Err(BundleAuthError::AuthLengthMismatch( // actions_len, // auth.action_authorizations.len(), // )) // } else { // let actions = NonEmpty::from_vec( // self.actions // .into_iter() // .zip(auth.action_authorizations.into_iter()) // .map(|(act, a)| act.map(|_| a)) // .collect(), // ) // .ok_or(BundleAuthError::AuthLengthMismatch(actions_len, 0))?; // // Ok(Bundle { // actions, // flags: self.flags, // value_balance: self.value_balance, // anchor: self.anchor, // authorization: auth.authorization, // }) // } // } //} impl Authorized { /// Constructs the authorizing data for a bundle of actions from its constituent parts. pub fn from_parts(proof: Proof, binding_signature: redpallas::Signature) -> Self { Authorized { proof, binding_signature, } } } impl Bundle { /// Computes a commitment to the authorizing data within for this bundle. /// /// This together with `Bundle::commitment` bind the entire bundle. pub fn authorizing_commitment(&self) -> BundleAuthorizingCommitment { todo!() } } /// A commitment to a bundle of actions. /// /// This commitment is non-malleable, in the sense that a bundle's commitment will only /// change if the effects of the bundle are altered. #[derive(Debug)] pub struct BundleCommitment; /// A commitment to the authorizing data within a bundle of actions. #[derive(Debug)] pub struct BundleAuthorizingCommitment; /// Generators for property testing. #[cfg(any(test, feature = "test-dependencies"))] pub mod testing { use nonempty::NonEmpty; use proptest::collection::vec; use proptest::prelude::*; //use pasta_curves::{pallas}; use crate::{ note::{ commitment::ExtractedNoteCommitment, nullifier::testing::arb_nullifier, testing::arb_note, TransmittedNoteCiphertext, }, primitives::redpallas::testing::arb_spendauth_verification_key, value::{ testing::{arb_note_value}, NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum, }, Anchor, }; use super::{Action, Authorization, Bundle, Flags}; /// Marker for an unauthorized bundle with no proofs or signatures. #[derive(Debug)] pub struct Unauthorized; impl Authorization for Unauthorized { type SpendAuth = (); } prop_compose! { /// Generate an action without authorization data. pub fn arb_unauthorized_action(value: NoteValue)( nf in arb_nullifier(), rk in arb_spendauth_verification_key(), note in arb_note(value), ) -> Action<()> { let cmx = ExtractedNoteCommitment::from(note.commitment()); let cv_net = ValueCommitment::derive( (note.value() - NoteValue::zero()).unwrap(), ValueCommitTrapdoor::zero() ); // FIXME: make a real one from the note. let encrypted_note = TransmittedNoteCiphertext { epk_bytes: [0u8; 32], enc_ciphertext: [0u8; 580], out_ciphertext: [0u8; 80] }; Action { nf, rk, cmx, encrypted_note, cv_net, authorization: () } } } prop_compose! { /// Create an arbitrary set of flags. pub fn arb_flags()(spends_enabled in prop::bool::ANY, outputs_enabled in prop::bool::ANY) -> Flags { Flags::from_parts(spends_enabled, outputs_enabled) } } prop_compose! { /// Generate an arbitrary unauthorized bundle. This bundle does not /// necessarily respect consensus rules; for that use /// [`crate::builder::testing::arb_bundle`] pub fn arb_unauthorized_bundle()( acts in vec( arb_note_value().prop_flat_map(|v| arb_unauthorized_action(v).prop_map(move |a| (v, a)) ), 1..10 ), flags in arb_flags(), anchor in prop::array::uniform32(prop::num::u8::ANY).prop_map(Anchor) ) -> Bundle { let (values, actions): (Vec, Vec>) = acts.into_iter().unzip(); Bundle::from_parts( NonEmpty::from_vec(actions).unwrap(), flags, values.into_iter().fold( ValueSum::zero(), |acc, cv| (acc + (cv - NoteValue::zero()).unwrap()).unwrap() ), anchor, Unauthorized ) } } }