diff --git a/benches/circuit.rs b/benches/circuit.rs index 029f80b55..e5cf933a7 100644 --- a/benches/circuit.rs +++ b/benches/circuit.rs @@ -8,6 +8,7 @@ use pprof::criterion::{Output, PProfProfiler}; use orchard::{ builder::{Builder, BundleType}, + bundle::Authorization, circuit::{ProvingKey, VerifyingKey}, keys::{FullViewingKey, Scope, SpendingKey}, note::AssetBase, @@ -86,7 +87,13 @@ fn criterion_benchmark(c: &mut Criterion) { .unwrap(); assert!(bundle.verify_proof(&vk).is_ok()); group.bench_function(BenchmarkId::new("bundle", num_recipients), |b| { - b.iter(|| bundle.authorization().proof().verify(&vk, &instances)); + b.iter(|| { + bundle + .authorization() + .proof() + .unwrap() + .verify(&vk, &instances) + }); }); } } diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 000000000..fa091418c --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +reorder_imports = false diff --git a/src/builder.rs b/src/builder.rs index 53d2971f2..6565724fa 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -26,8 +26,8 @@ use crate::{ }, note::{AssetBase, Note, Rho, TransmittedNoteCiphertext}, orchard_flavor::{Flavor, OrchardFlavor, OrchardVanilla, OrchardZSA}, - primitives::redpallas::{self, Binding, SpendAuth}, - swap_bundle::{ActionGroup, ActionGroupAuthorized}, + primitives::redpallas::{self, Binding, SpendAuth, SigningKey}, + swap_bundle::ActionGroupAuthorized, tree::{Anchor, MerklePath}, value::{self, NoteValue, OverflowError, ValueCommitTrapdoor, ValueCommitment, ValueSum}, }; @@ -65,10 +65,16 @@ impl BundleType { bundle_required: false, }; + /// The default bundle with all flags enabled, including Asset Swaps. + pub const DEFAULT_SWAP: BundleType = BundleType::Transactional { + flags: Flags::ENABLED_WITH_SWAPS, + 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, false), + flags: Flags::from_parts(false, false, false, false), bundle_required: false, }; @@ -513,12 +519,6 @@ impl BundleMetadata { /// A tuple containing an in-progress bundle with no proofs or signatures, and its associated metadata. pub type UnauthorizedBundleWithMetadata = (UnauthorizedBundle, BundleMetadata); -/// A tuple containing an in-progress action group with no proofs or signatures, and its associated metadata. -pub type UnauthorizedActionGroupWithMetadata = ( - ActionGroup, V>, - BundleMetadata, -); - /// A builder that constructs a [`Bundle`] from a set of notes to be spent, and outputs /// to receive funds. #[derive(Debug)] @@ -544,6 +544,14 @@ impl Builder { } } + /// Returns true if the builder is empty. + pub fn is_empty(&self) -> bool { + self.spends.is_empty() + && self.outputs.is_empty() + && self.burn.is_empty() + && self.reference_notes.is_empty() + } + /// Adds a note to be spent in this transaction. /// /// - `note` is a spendable note, obtained by trial-decrypting an [`Action`] using the @@ -690,6 +698,7 @@ impl Builder { self.bundle_type, self.spends, self.outputs, + 0, SpecificBuilderParams::BundleParams(self.burn), ) } @@ -697,12 +706,12 @@ impl Builder { /// Builds an action group containing the given spent and output notes. /// /// The returned action group will have no proof or signatures; these can be applied with - /// [`ActionGroup::create_proof`] and [`ActionGroup::apply_signatures`] respectively. + /// [`Bundle::create_proof`] and [`Bundle::apply_signatures`] respectively. pub fn build_action_group>( self, rng: impl RngCore, - timelimit: u32, - ) -> Result, BuildError> { + expiry_height: u32, + ) -> Result, BuildError> { if !self.burn.is_empty() { return Err(BuildError::BurnNotEmptyInActionGroup); } @@ -712,14 +721,9 @@ impl Builder { self.bundle_type, self.spends, self.outputs, + expiry_height, SpecificBuilderParams::ActionGroupParams(self.reference_notes), ) - .map(|(action_group, metadata)| { - ( - ActionGroup::from_parts(action_group, timelimit, None), - metadata, - ) - }) } } @@ -813,6 +817,7 @@ pub fn bundle, FL: OrchardFlavor>( bundle_type: BundleType, spends: Vec, outputs: Vec, + expiry_height: u32, specific_params: SpecificBuilderParams, ) -> Result, BuildError> { let flags = bundle_type.flags(); @@ -979,6 +984,7 @@ pub fn bundle, FL: OrchardFlavor>( result_value_balance, burn, anchor, + expiry_height, InProgress { proof: Unproven { witnesses, @@ -1019,6 +1025,10 @@ impl InProgress { impl Authorization for InProgress { type SpendAuth = S::SpendAuth; + + fn proof(&self) -> Option<&Proof> { + None + } } /// Marker for a bundle without a proof. @@ -1119,8 +1129,8 @@ 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. + /// These keys are used automatically in [`Bundle::prepare`] or + /// [`Bundle::apply_signatures`] to sign dummy spends. dummy_ask: Option, parts: SigningParts, } @@ -1256,23 +1266,15 @@ impl Bundle, V, D> { }) .finalize() } -} -impl Bundle, V, D> { - /// Applies signatures to this action group, in order to authorize it. + /// Applies signatures to this bundle as an action group, in order to authorize it. #[allow(clippy::type_complexity)] pub fn apply_signatures_for_action_group( self, mut rng: R, action_group_digest: [u8; 32], signing_keys: &[SpendAuthorizingKey], - ) -> Result< - ( - redpallas::SigningKey, - Bundle, - ), - BuildError, - > { + ) -> Result<(Bundle, SigningKey), BuildError> { signing_keys .iter() .fold( @@ -1395,20 +1397,14 @@ impl Bundle Result< - ( - redpallas::SigningKey, - Bundle, - ), - BuildError, - > { + ) -> Result<(Bundle, SigningKey), BuildError> { let bsk = self.authorization().sigs.bsk; self.try_map_authorization( &mut (), |_, _, maybe| maybe.finalize(), |_, partial| Ok(ActionGroupAuthorized::from_parts(partial.proof)), ) - .map(|bundle| (bsk, bundle)) + .map(|bundle| (bundle, bsk)) } } diff --git a/src/bundle.rs b/src/bundle.rs index 4f67591a8..4334809ce 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -16,7 +16,7 @@ use zcash_note_encryption_zsa::{try_note_decryption, try_output_recovery_with_ov use crate::{ action::Action, address::Address, - bundle::commitments::{hash_bundle_auth_data, hash_bundle_txid_data}, + bundle::commitments::{hash_bundle_auth_data, hash_bundle_txid_data, hash_action_group}, circuit::{Instance, Proof, VerifyingKey}, domain::{OrchardDomain, OrchardDomainCommon}, keys::{IncomingViewingKey, OutgoingViewingKey, PreparedIncomingViewingKey}, @@ -26,6 +26,7 @@ use crate::{ tree::Anchor, value::{NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum}, }; +use crate::orchard_flavor::OrchardZSA; impl Action { /// Prepares the public instance for this action, for creating and verifying the @@ -64,12 +65,18 @@ pub struct Flags { /// If `false`, all notes within [`Action`]s in the transaction's [`Bundle`] are /// guaranteed to be notes with native asset. zsa_enabled: bool, + /// Flag denoting whether Asset Swaps are enabled. + /// + /// If `false`, [`Bundle`] is guaranteed to contain only one ['ActionGroup']. + swaps_enabled: bool, } const FLAG_SPENDS_ENABLED: u8 = 0b0000_0001; const FLAG_OUTPUTS_ENABLED: u8 = 0b0000_0010; const FLAG_ZSA_ENABLED: u8 = 0b0000_0100; -const FLAGS_EXPECTED_UNSET: u8 = !(FLAG_SPENDS_ENABLED | FLAG_OUTPUTS_ENABLED | FLAG_ZSA_ENABLED); +const FLAG_SWAPS_ENABLED: u8 = 0b0000_1000; +const FLAGS_EXPECTED_UNSET: u8 = + !(FLAG_SPENDS_ENABLED | FLAG_OUTPUTS_ENABLED | FLAG_ZSA_ENABLED | FLAG_SWAPS_ENABLED); impl Flags { /// Construct a set of flags from its constituent parts @@ -77,40 +84,54 @@ impl Flags { spends_enabled: bool, outputs_enabled: bool, zsa_enabled: bool, + swaps_enabled: bool, ) -> Self { Flags { spends_enabled, outputs_enabled, zsa_enabled, + swaps_enabled, } } - /// The flag set with both spends and outputs enabled and ZSA disabled. + /// The flag set with both spends and outputs enabled. ZSA and swaps are disabled. pub const ENABLED_WITHOUT_ZSA: Flags = Flags { spends_enabled: true, outputs_enabled: true, zsa_enabled: false, + swaps_enabled: false, }; - /// The flags set with spends, outputs and ZSA enabled. + /// The flags set with spends, outputs and ZSA enabled. Swaps are disabled. pub const ENABLED_WITH_ZSA: Flags = Flags { spends_enabled: true, outputs_enabled: true, zsa_enabled: true, + swaps_enabled: false, + }; + + /// The flags set with spends, outputs, ZSA and swaps enabled. + pub const ENABLED_WITH_SWAPS: Flags = Flags { + spends_enabled: true, + outputs_enabled: true, + zsa_enabled: true, + swaps_enabled: true, }; - /// The flag set with spends and ZSA disabled. + /// The flag set with spends, ZSA and swaps disabled. pub const SPENDS_DISABLED_WITHOUT_ZSA: Flags = Flags { spends_enabled: false, outputs_enabled: true, zsa_enabled: false, + swaps_enabled: false, }; - /// The flag set with spends disabled and ZSA enabled. + /// The flag set with spends disabled and ZSA enabled. Swaps are disabled. pub const SPENDS_DISABLED_WITH_ZSA: Flags = Flags { spends_enabled: false, outputs_enabled: true, zsa_enabled: true, + swaps_enabled: false, }; /// The flag set with outputs disabled. @@ -118,6 +139,7 @@ impl Flags { spends_enabled: true, outputs_enabled: false, zsa_enabled: false, + swaps_enabled: false, }; /// Flag denoting whether Orchard spends are enabled in the transaction. @@ -177,6 +199,7 @@ impl Flags { spends_enabled: value & FLAG_SPENDS_ENABLED != 0, outputs_enabled: value & FLAG_OUTPUTS_ENABLED != 0, zsa_enabled: value & FLAG_ZSA_ENABLED != 0, + swaps_enabled: value & FLAG_SWAPS_ENABLED != 0, }) } else { None @@ -188,6 +211,9 @@ impl Flags { pub trait Authorization: fmt::Debug { /// The authorization type of an Orchard action. type SpendAuth: fmt::Debug + Clone; + + /// Return the proof component of the authorizing data. + fn proof(&self) -> Option<&Proof>; } /// A bundle of actions to be applied to the ledger. @@ -242,6 +268,7 @@ impl Bundle { value_balance: V, burn: Vec<(AssetBase, NoteValue)>, anchor: Anchor, + expiry_height: u32, authorization: A, ) -> Self { Bundle { @@ -250,7 +277,7 @@ impl Bundle { value_balance, burn, anchor, - expiry_height: 0, + expiry_height, authorization, } } @@ -490,6 +517,14 @@ impl, FL: OrchardFlavor> Bundle } } +impl> Bundle { + /// Computes a commitment to the effects of this bundle, + /// assuming that the bundle represents an action group inside a swap bundle. + pub fn action_group_commitment(&self) -> BundleCommitment { + BundleCommitment(hash_action_group(self)) + } +} + /// Authorizing data for a bundle of actions, ready to be committed to the ledger. #[derive(Debug, Clone)] pub struct Authorized { @@ -499,6 +534,11 @@ pub struct Authorized { impl Authorization for Authorized { type SpendAuth = redpallas::Signature; + + /// Return the proof component of the authorizing data. + fn proof(&self) -> Option<&Proof> { + Some(&self.proof) + } } impl Authorized { @@ -510,11 +550,6 @@ impl Authorized { } } - /// 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 @@ -533,6 +568,7 @@ impl Bundle { pub fn verify_proof(&self, vk: &VerifyingKey) -> Result<(), halo2_proofs::plonk::Error> { self.authorization() .proof() + .unwrap() .verify(vk, &self.to_instances()) } } @@ -614,6 +650,11 @@ pub mod testing { impl Authorization for Unauthorized { type SpendAuth = (); + + /// Return the proof component of the authorizing data. + fn proof(&self) -> Option<&Proof> { + None + } } /// `BundleArb` adapts `arb_...` functions for both Vanilla and ZSA Orchard protocol variations @@ -687,8 +728,8 @@ pub mod testing { prop_compose! { /// Create an arbitrary set of flags. - pub fn arb_flags()(spends_enabled in prop::bool::ANY, outputs_enabled in prop::bool::ANY, zsa_enabled in prop::bool::ANY) -> Flags { - Flags::from_parts(spends_enabled, outputs_enabled, zsa_enabled) + pub fn arb_flags()(spends_enabled in prop::bool::ANY, outputs_enabled in prop::bool::ANY, zsa_enabled in prop::bool::ANY, swaps_enabled in prop::bool::ANY) -> Flags { + Flags::from_parts(spends_enabled, outputs_enabled, zsa_enabled, swaps_enabled) } } @@ -723,6 +764,7 @@ pub mod testing { balances.into_iter().sum::>().unwrap(), burn, anchor, + 0, Unauthorized, ) } @@ -755,6 +797,7 @@ pub mod testing { balances.into_iter().sum::>().unwrap(), burn, anchor, + 0, Authorized { proof: Proof::new(fake_proof), binding_signature: sk.sign(rng, &fake_sighash), diff --git a/src/bundle/batch.rs b/src/bundle/batch.rs index cc9811c18..34fc3ba0b 100644 --- a/src/bundle/batch.rs +++ b/src/bundle/batch.rs @@ -58,7 +58,7 @@ impl BatchValidator { bundle .authorization() - .proof() + .proof .add_to_batch(&mut self.proofs, bundle.to_instances()); } diff --git a/src/bundle/commitments.rs b/src/bundle/commitments.rs index 1d5a3053e..bfaf11188 100644 --- a/src/bundle/commitments.rs +++ b/src/bundle/commitments.rs @@ -7,7 +7,6 @@ use crate::{ domain::OrchardDomainCommon, issuance::{IssueAuth, IssueBundle, Signed}, orchard_flavor::OrchardZSA, - swap_bundle::ActionGroup, }; // TODO remove @@ -64,40 +63,15 @@ pub fn hash_bundle_txid_empty() -> Blake2bHash { /// /// [zip228]: https://zips.z.cash/zip-0228 pub(crate) fn hash_action_group>( - action_group: &ActionGroup, + action_group: &Bundle, ) -> Blake2bHash { let mut agh = hasher(ZCASH_ORCHARD_ACTION_GROUPS_HASH_PERSONALIZATION); - let mut ch = hasher(ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION); - let mut mh = hasher(ZCASH_ORCHARD_ACTIONS_MEMOS_HASH_PERSONALIZATION); - let mut nh = hasher(ZCASH_ORCHARD_ACTIONS_NONCOMPACT_HASH_PERSONALIZATION); - for action in action_group.action_group().actions().iter() { - ch.update(&action.nullifier().to_bytes()); - ch.update(&action.cmx().to_bytes()); - ch.update(&action.encrypted_note().epk_bytes); - ch.update( - &action.encrypted_note().enc_ciphertext.as_ref()[..OrchardZSA::COMPACT_NOTE_SIZE], - ); - - mh.update( - &action.encrypted_note().enc_ciphertext.as_ref() - [OrchardZSA::COMPACT_NOTE_SIZE..OrchardZSA::COMPACT_NOTE_SIZE + MEMO_SIZE], - ); - nh.update(&action.cv_net().to_bytes()); - nh.update(&<[u8; 32]>::from(action.rk())); - nh.update( - &action.encrypted_note().enc_ciphertext.as_ref() - [OrchardZSA::COMPACT_NOTE_SIZE + MEMO_SIZE..], - ); - nh.update(&action.encrypted_note().out_ciphertext); - } + OrchardZSA::update_hash_with_actions(&mut agh, action_group); - agh.update(ch.finalize().as_bytes()); - agh.update(mh.finalize().as_bytes()); - agh.update(nh.finalize().as_bytes()); - agh.update(&[action_group.action_group().flags().to_byte()]); - agh.update(&action_group.action_group().anchor().to_bytes()); - agh.update(&action_group.timelimit().to_le_bytes()); + agh.update(&[action_group.flags().to_byte()]); + agh.update(&action_group.anchor().to_bytes()); + agh.update(&action_group.expiry_height().to_le_bytes()); agh.finalize() } @@ -106,7 +80,7 @@ pub(crate) fn hash_action_group>( /// /// [zip228]: https://zips.z.cash/zip-0228 pub(crate) fn hash_swap_bundle>( - action_groups: Vec<&ActionGroup>, + action_groups: Vec<&Bundle>, value_balance: V, ) -> Blake2bHash { let mut h = hasher(ZCASH_ORCHARD_HASH_PERSONALIZATION); diff --git a/src/circuit/circuit_vanilla.rs b/src/circuit/circuit_vanilla.rs index 4fcb95ce2..a19e03470 100644 --- a/src/circuit/circuit_vanilla.rs +++ b/src/circuit/circuit_vanilla.rs @@ -806,13 +806,14 @@ mod tests { let enable_spend = read_bool(&mut r); let enable_output = read_bool(&mut r); let enable_zsa = false; + let enable_swaps = false; let instance = Instance::from_parts( anchor, cv_net, nf_old, rk, cmx, - Flags::from_parts(enable_spend, enable_output, enable_zsa), + Flags::from_parts(enable_spend, enable_output, enable_zsa, enable_swaps), ); let mut proof_bytes = vec![]; diff --git a/src/circuit/circuit_zsa.rs b/src/circuit/circuit_zsa.rs index d87180a60..e6d3bb471 100644 --- a/src/circuit/circuit_zsa.rs +++ b/src/circuit/circuit_zsa.rs @@ -1050,13 +1050,14 @@ mod tests { let enable_spend = read_bool(&mut r); let enable_output = read_bool(&mut r); let enable_zsa = read_bool(&mut r); + let enable_swaps = false; let instance = Instance::from_parts( anchor, cv_net, nf_old, rk, cmx, - Flags::from_parts(enable_spend, enable_output, enable_zsa), + Flags::from_parts(enable_spend, enable_output, enable_zsa, enable_swaps), ); let mut proof_bytes = vec![]; diff --git a/src/domain/orchard_domain_vanilla.rs b/src/domain/orchard_domain_vanilla.rs index 69cd9a54c..f35963729 100644 --- a/src/domain/orchard_domain_vanilla.rs +++ b/src/domain/orchard_domain_vanilla.rs @@ -4,6 +4,13 @@ use blake2b_simd::Hash as Blake2bHash; use zcash_note_encryption_zsa::note_bytes::NoteBytesData; +use super::{ + orchard_domain::OrchardDomainCommon, + zcash_note_encryption_domain::{ + build_base_note_plaintext_bytes, Memo, COMPACT_NOTE_SIZE_VANILLA, NOTE_VERSION_BYTE_V2, + }, +}; + use crate::{ bundle::{ commitments::{ @@ -16,13 +23,6 @@ use crate::{ Bundle, }; -use super::{ - orchard_domain::OrchardDomainCommon, - zcash_note_encryption_domain::{ - build_base_note_plaintext_bytes, Memo, COMPACT_NOTE_SIZE_VANILLA, NOTE_VERSION_BYTE_V2, - }, -}; - impl OrchardDomainCommon for OrchardVanilla { const COMPACT_NOTE_SIZE: usize = COMPACT_NOTE_SIZE_VANILLA; @@ -66,7 +66,7 @@ impl OrchardDomainCommon for OrchardVanilla { /// [zip244]: https://zips.z.cash/zip-0244 fn hash_bundle_auth_data(bundle: &Bundle) -> Blake2bHash { let mut h = hasher(ZCASH_ORCHARD_SIGS_HASH_PERSONALIZATION); - h.update(bundle.authorization().proof().as_ref()); + h.update(bundle.authorization().proof().unwrap().as_ref()); for action in bundle.actions().iter() { h.update(&<[u8; 64]>::from(action.authorization())); } diff --git a/src/domain/orchard_domain_zsa.rs b/src/domain/orchard_domain_zsa.rs index 0c003de6c..46b8bfabb 100644 --- a/src/domain/orchard_domain_zsa.rs +++ b/src/domain/orchard_domain_zsa.rs @@ -5,14 +5,14 @@ use blake2b_simd::Hash as Blake2bHash; use zcash_note_encryption_zsa::note_bytes::NoteBytesData; use crate::bundle::commitments::{ - ZCASH_ORCHARD_ACTION_GROUPS_SIGS_HASH_PERSONALIZATION, ZCASH_ORCHARD_SIGS_HASH_PERSONALIZATION, + hash_action_group, ZCASH_ORCHARD_ACTION_GROUPS_SIGS_HASH_PERSONALIZATION, + ZCASH_ORCHARD_SIGS_HASH_PERSONALIZATION, }; use crate::bundle::Authorized; use crate::{ bundle::{ commitments::{ - hasher, ZCASH_ORCHARD_ACTION_GROUPS_HASH_PERSONALIZATION, - ZCASH_ORCHARD_HASH_PERSONALIZATION, ZCASH_ORCHARD_ZSA_BURN_HASH_PERSONALIZATION, + hasher, ZCASH_ORCHARD_HASH_PERSONALIZATION, ZCASH_ORCHARD_ZSA_BURN_HASH_PERSONALIZATION, }, Authorization, }, @@ -63,15 +63,8 @@ impl OrchardDomainCommon for OrchardZSA { bundle: &Bundle, ) -> Blake2bHash { let mut h = hasher(ZCASH_ORCHARD_HASH_PERSONALIZATION); - let mut agh = hasher(ZCASH_ORCHARD_ACTION_GROUPS_HASH_PERSONALIZATION); - - Self::update_hash_with_actions(&mut agh, bundle); - - agh.update(&[bundle.flags().to_byte()]); - agh.update(&bundle.anchor().to_bytes()); - agh.update(&bundle.expiry_height().to_le_bytes()); - - h.update(agh.finalize().as_bytes()); + let action_group_hash = hash_action_group(bundle); + h.update(action_group_hash.as_bytes()); let mut burn_hasher = hasher(ZCASH_ORCHARD_ZSA_BURN_HASH_PERSONALIZATION); for burn_item in bundle.burn() { @@ -91,7 +84,7 @@ impl OrchardDomainCommon for OrchardZSA { fn hash_bundle_auth_data(bundle: &Bundle) -> Blake2bHash { let mut h = hasher(ZCASH_ORCHARD_SIGS_HASH_PERSONALIZATION); let mut agh = hasher(ZCASH_ORCHARD_ACTION_GROUPS_SIGS_HASH_PERSONALIZATION); - agh.update(bundle.authorization().proof().as_ref()); + agh.update(bundle.authorization().proof().unwrap().as_ref()); for action in bundle.actions().iter() { agh.update(&<[u8; 64]>::from(action.authorization())); } diff --git a/src/swap_bundle.rs b/src/swap_bundle.rs index 0f188873b..1cbe1dbc8 100644 --- a/src/swap_bundle.rs +++ b/src/swap_bundle.rs @@ -1,12 +1,10 @@ //! Structs related to swap bundles. use crate::{ - builder::{BuildError, InProgress, InProgressSignatures, Unauthorized, Unproven}, - bundle::commitments::{hash_action_group, hash_swap_bundle}, + bundle::commitments::hash_swap_bundle, bundle::{derive_bvk, Authorization, Bundle, BundleCommitment}, - circuit::{ProvingKey, VerifyingKey}, + circuit::VerifyingKey, domain::OrchardDomainCommon, - keys::SpendAuthorizingKey, note::AssetBase, orchard_flavor::OrchardZSA, primitives::redpallas::{self, Binding, SpendAuth}, @@ -16,112 +14,11 @@ use crate::{ use k256::elliptic_curve::rand_core::{CryptoRng, RngCore}; -/// An action group. -#[derive(Debug)] -pub struct ActionGroup { - /// The action group main content. - action_group: Bundle, - /// The action group timelimit. - timelimit: u32, - /// The binding signature key for the action group. - /// - /// During the building of the action group, this key is not set. - /// Once the action group is finalized (it contains a spend authorizing signature for each - /// action and a proof), the key is set. - bsk: Option>, -} - -impl ActionGroup { - /// Constructs an `ActionGroup` from its constituent parts. - pub fn from_parts( - action_group: Bundle, - timelimit: u32, - bsk: Option>, - ) -> Self { - ActionGroup { - action_group, - timelimit, - bsk, - } - } - - /// Returns the action group's main content. - pub fn action_group(&self) -> &Bundle { - &self.action_group - } - - /// Returns the action group's timelimit. - pub fn timelimit(&self) -> u32 { - self.timelimit - } - - /// Returns the action group's binding signature key. - pub fn bsk(&self) -> Option<&redpallas::SigningKey> { - self.bsk.as_ref() - } - - /// Remove bsk from this action group - /// - /// When creating a SwapBundle from a list of action groups, we evaluate the binding signature - /// by signing the sighash with the sum of the bsk of each action group. - /// Then, we remove the bsk of each action group as it is no longer needed. - fn remove_bsk(&mut self) { - self.bsk = None; - } -} - -impl ActionGroup, V> { - /// Creates the proof for this action group. - pub fn create_proof( - self, - pk: &ProvingKey, - mut rng: impl RngCore, - ) -> Result, V>, BuildError> { - let new_action_group = self.action_group.create_proof(pk, &mut rng)?; - Ok(ActionGroup { - action_group: new_action_group, - timelimit: self.timelimit, - bsk: self.bsk, - }) - } -} - -impl ActionGroup, V> { - /// Applies signatures to this action group, in order to authorize it. - pub fn apply_signatures( - self, - mut rng: R, - action_group_digest: [u8; 32], - signing_keys: &[SpendAuthorizingKey], - ) -> Result, BuildError> { - let (bsk, action_group) = signing_keys - .iter() - .fold( - self.action_group - .prepare_for_action_group(&mut rng, action_group_digest), - |partial, ask| partial.sign(&mut rng, ask), - ) - .finalize()?; - Ok(ActionGroup { - action_group, - timelimit: self.timelimit, - bsk: Some(bsk), - }) - } -} - -impl> ActionGroup { - /// Computes a commitment to the content of this action group. - pub fn commitment(&self) -> BundleCommitment { - BundleCommitment(hash_action_group(self)) - } -} - /// A swap bundle to be applied to the ledger. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct SwapBundle { /// The list of action groups that make up this swap bundle. - action_groups: Vec>, + action_groups: Vec>, /// The net value moved out of this swap. /// /// This is the sum of Orchard spends minus the sum of Orchard outputs. @@ -130,26 +27,36 @@ pub struct SwapBundle { binding_signature: redpallas::Signature, } +impl SwapBundle { + /// Constructs a `SwapBundle` from its constituent parts. + pub fn from_parts( + action_groups: Vec>, + value_balance: V, + binding_signature: redpallas::Signature, + ) -> Self { + SwapBundle { + action_groups, + value_balance, + binding_signature, + } + } +} + impl + std::iter::Sum> SwapBundle { - /// Constructs a `SwapBundle` from its action groups. + /// Constructs a `SwapBundle` from its action groups and respective binding signature keys. + /// Keys should go in the same order as the action groups. pub fn new( rng: R, - mut action_groups: Vec>, + action_groups: Vec>, + bsks: Vec>, ) -> Self { + assert_eq!(action_groups.len(), bsks.len()); // Evaluate the swap value balance by summing the value balance of each action group. - let value_balance = action_groups - .iter() - .map(|a| *a.action_group().value_balance()) - .sum(); + let value_balance = action_groups.iter().map(|a| *a.value_balance()).sum(); // Evaluate the swap bsk by summing the bsk of each action group. - let bsk = action_groups - .iter_mut() - .map(|ag| { - let bsk = ValueCommitTrapdoor::from_bsk(ag.bsk.unwrap()); - // Remove the bsk of each action group as it is no longer needed. - ag.remove_bsk(); - bsk - }) + let bsk = bsks + .into_iter() + .map(ValueCommitTrapdoor::from_bsk) .sum::() .into_bsk(); // Evaluate the swap sighash @@ -178,6 +85,11 @@ pub struct ActionGroupAuthorized { impl Authorization for ActionGroupAuthorized { type SpendAuth = redpallas::Signature; + + /// Return the proof component of the authorizing data. + fn proof(&self) -> Option<&Proof> { + Some(&self.proof) + } } impl ActionGroupAuthorized { @@ -185,11 +97,6 @@ impl ActionGroupAuthorized { pub fn from_parts(proof: Proof) -> Self { ActionGroupAuthorized { proof } } - - /// Return the proof component of the authorizing data. - pub fn proof(&self) -> &Proof { - &self.proof - } } impl Bundle { @@ -197,13 +104,14 @@ impl Bundle { pub fn verify_proof(&self, vk: &VerifyingKey) -> Result<(), halo2_proofs::plonk::Error> { self.authorization() .proof() + .unwrap() .verify(vk, &self.to_instances()) } } impl SwapBundle { /// Returns the list of action groups that make up this swap bundle. - pub fn action_groups(&self) -> &Vec> { + pub fn action_groups(&self) -> &Vec> { &self.action_groups } @@ -211,6 +119,13 @@ impl SwapBundle { pub fn binding_signature(&self) -> &redpallas::Signature { &self.binding_signature } + + /// The net value moved out of this swap. + /// + /// This is the sum of Orchard spends minus the sum of Orchard outputs. + pub fn value_balance(&self) -> &V { + &self.value_balance + } } impl> SwapBundle { @@ -228,7 +143,7 @@ impl> SwapBundle { let actions = self .action_groups .iter() - .flat_map(|ag| ag.action_group().actions()) + .flat_map(|ag| ag.actions()) .collect::>(); derive_bvk( actions, diff --git a/tests/builder.rs b/tests/builder.rs index 2361041ac..905a8d9aa 100644 --- a/tests/builder.rs +++ b/tests/builder.rs @@ -8,7 +8,7 @@ use orchard::{ keys::{FullViewingKey, PreparedIncomingViewingKey, Scope, SpendAuthorizingKey, SpendingKey}, note::{AssetBase, ExtractedNoteCommitment}, orchard_flavor::{OrchardFlavor, OrchardVanilla, OrchardZSA}, - swap_bundle::{ActionGroup, ActionGroupAuthorized, SwapBundle}, + swap_bundle::{ActionGroupAuthorized, SwapBundle}, tree::{MerkleHashOrchard, MerklePath}, value::NoteValue, Anchor, Bundle, Note, @@ -43,8 +43,6 @@ pub fn verify_swap_bundle(swap_bundle: &SwapBundle, vks: Vec<&VerifyingKey> assert_eq!(vks.len(), swap_bundle.action_groups().len()); for (action_group, vk) in swap_bundle.action_groups().iter().zip(vks.iter()) { verify_action_group(action_group, vk); - // Verify that bsk is None - assert!(action_group.bsk().is_none()); } let sighash: [u8; 32] = swap_bundle.commitment().into(); @@ -59,13 +57,12 @@ pub fn verify_swap_bundle(swap_bundle: &SwapBundle, vks: Vec<&VerifyingKey> // - verify the proof // - verify the signature on each action pub fn verify_action_group( - action_group: &ActionGroup, + action_group_bundle: &Bundle, vk: &VerifyingKey, ) { - let action_group_bundle = action_group.action_group(); assert!(matches!(action_group_bundle.verify_proof(vk), Ok(()))); - let action_group_digest: [u8; 32] = action_group.commitment().into(); + let action_group_digest: [u8; 32] = action_group_bundle.action_group_commitment().into(); for action in action_group_bundle.actions() { assert_eq!( action diff --git a/tests/zsa.rs b/tests/zsa.rs index 6ce84409e..1f2b4e079 100644 --- a/tests/zsa.rs +++ b/tests/zsa.rs @@ -13,7 +13,8 @@ use orchard::{ keys::{IssuanceAuthorizingKey, IssuanceValidatingKey}, note::{AssetBase, ExtractedNoteCommitment, Nullifier}, orchard_flavor::OrchardZSA, - swap_bundle::{ActionGroup, ActionGroupAuthorized, SwapBundle}, + primitives::redpallas::{Binding, SigningKey}, + swap_bundle::{ActionGroupAuthorized, SwapBundle}, tree::{MerkleHashOrchard, MerklePath}, value::NoteValue, Address, Anchor, Bundle, Note, ReferenceKeys, @@ -103,13 +104,20 @@ fn build_and_sign_action_group( mut rng: OsRng, pk: &ProvingKey, sk: &SpendingKey, -) -> ActionGroup { +) -> ( + Bundle, + SigningKey, +) { let unauthorized = builder.build_action_group(&mut rng, timelimit).unwrap().0; - let action_group_digest = unauthorized.commitment().into(); + let action_group_digest = unauthorized.action_group_commitment().into(); let proven = unauthorized.create_proof(pk, &mut rng).unwrap(); proven - .apply_signatures(rng, action_group_digest, &[SpendAuthorizingKey::from(sk)]) + .apply_signatures_for_action_group( + rng, + action_group_digest, + &[SpendAuthorizingKey::from(sk)], + ) .unwrap() } @@ -287,6 +295,7 @@ fn build_and_verify_bundle( Ok(()) } +#[allow(clippy::type_complexity)] fn build_and_verify_action_group( spends: Vec<&TestSpendInfo>, outputs: Vec, @@ -295,9 +304,15 @@ fn build_and_verify_action_group( timelimit: u32, expected_num_actions: usize, keys: &Keychain, -) -> Result, String> { +) -> Result< + ( + Bundle, + SigningKey, + ), + String, +> { let rng = OsRng; - let shielded_action_group: ActionGroup<_, i64> = { + let (shielded_action_group, bsk) = { let mut builder = Builder::new(BundleType::DEFAULT_ZSA, anchor); spends @@ -327,14 +342,9 @@ fn build_and_verify_action_group( }; verify_action_group(&shielded_action_group, &keys.vk); - assert_eq!( - shielded_action_group.action_group().actions().len(), - expected_num_actions - ); - assert!(verify_unique_spent_nullifiers( - shielded_action_group.action_group() - )); - Ok(shielded_action_group) + assert_eq!(shielded_action_group.actions().len(), expected_num_actions); + assert!(verify_unique_spent_nullifiers(&shielded_action_group)); + Ok((shielded_action_group, bsk)) } fn verify_unique_spent_nullifiers(bundle: &Bundle) -> bool { @@ -792,7 +802,7 @@ fn action_group_and_swap_bundle() { { // 1. Create and verify ActionGroup for user1 - let action_group1 = build_and_verify_action_group( + let (action_group1, bsk1) = build_and_verify_action_group( vec![ &asset1_spend1, // 40 asset1 &asset1_spend2, // 2 asset1 @@ -832,7 +842,7 @@ fn action_group_and_swap_bundle() { .unwrap(); // 2. Create and verify ActionGroup for user2 - let action_group2 = build_and_verify_action_group( + let (action_group2, bsk2) = build_and_verify_action_group( vec![ &asset2_spend1, // 40 asset2 &asset2_spend2, // 2 asset2 @@ -871,7 +881,7 @@ fn action_group_and_swap_bundle() { .unwrap(); // 3. Matcher fees action group - let action_group_matcher = build_and_verify_action_group( + let (action_group_matcher, bsk_matcher) = build_and_verify_action_group( // The matcher spends nothing. vec![], // The matcher receives 5 ZEC as a fee from user1 and user2. @@ -894,6 +904,7 @@ fn action_group_and_swap_bundle() { let swap_bundle = SwapBundle::new( OsRng, vec![action_group1, action_group2, action_group_matcher], + vec![bsk1, bsk2, bsk_matcher], ); verify_swap_bundle(&swap_bundle, vec![&keys1.vk, &keys2.vk, &matcher_keys.vk]); } @@ -912,7 +923,7 @@ fn action_group_and_swap_bundle() { { // 1. Create and verify ActionGroup for user1 - let action_group1 = build_and_verify_action_group( + let (action_group1, bsk1) = build_and_verify_action_group( vec![ &asset1_spend1, // 40 asset1 &asset1_spend2, // 2 asset1 @@ -942,7 +953,7 @@ fn action_group_and_swap_bundle() { .unwrap(); // 2. Create and verify ActionGroup for user2 - let action_group2 = build_and_verify_action_group( + let (action_group2, bsk2) = build_and_verify_action_group( vec![ &user2_native_note1_spend, // 100 ZEC &user2_native_note2_spend, // 100 ZEC @@ -973,7 +984,7 @@ fn action_group_and_swap_bundle() { .unwrap(); // 3. Matcher fees action group - let action_group_matcher = build_and_verify_action_group( + let (action_group_matcher, bsk_matcher) = build_and_verify_action_group( // The matcher spends nothing. vec![], // The matcher receives 10 ZEC as a fee from user2. @@ -996,6 +1007,7 @@ fn action_group_and_swap_bundle() { let swap_bundle = SwapBundle::new( OsRng, vec![action_group1, action_group2, action_group_matcher], + vec![bsk1, bsk2, bsk_matcher], ); verify_swap_bundle(&swap_bundle, vec![&keys1.vk, &keys2.vk, &matcher_keys.vk]); } @@ -1004,7 +1016,7 @@ fn action_group_and_swap_bundle() { // User1 would like to send 30 asset1 to User2 { // 1. Create and verify ActionGroup - let action_group = build_and_verify_action_group( + let (action_group, bsk1) = build_and_verify_action_group( vec![ &asset1_spend1, // 40 asset1 &asset1_spend2, // 2 asset1 @@ -1031,7 +1043,7 @@ fn action_group_and_swap_bundle() { .unwrap(); // 2. Create a SwapBundle from the previous ActionGroup - let swap_bundle = SwapBundle::new(OsRng, vec![action_group]); + let swap_bundle = SwapBundle::new(OsRng, vec![action_group], vec![bsk1]); verify_swap_bundle(&swap_bundle, vec![&keys1.vk]); } }