Skip to content

Commit 8238744

Browse files
committed
Zsa Swap Utils (#141)
This PR contains a set of additions/changes that allow ZSA Swaps to be implemented in librustzcash: - Adds Swap BundleType and Flag - Adds AuthorizedWithProof trait that defines proof() method for both Authorized and ActionGroupAuthorized - Adds utility methods e.g. from_parts() for SwapBundle, is_empty() in Builder - Changes visibility of some methods e.g. dummy() for testing in librustzcash - Removes ActionGroup structure
1 parent 9552ad1 commit 8238744

13 files changed

Lines changed: 196 additions & 129 deletions

benches/circuit.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use pprof::criterion::{Output, PProfProfiler};
88

99
use orchard::{
1010
builder::{Builder, BundleType},
11+
bundle::Authorization,
1112
circuit::{ProvingKey, VerifyingKey},
1213
keys::{FullViewingKey, Scope, SpendingKey},
1314
note::AssetBase,
@@ -86,7 +87,13 @@ fn criterion_benchmark<FL: OrchardFlavorBench>(c: &mut Criterion) {
8687
.unwrap();
8788
assert!(bundle.verify_proof(&vk).is_ok());
8889
group.bench_function(BenchmarkId::new("bundle", num_recipients), |b| {
89-
b.iter(|| bundle.authorization().proof().verify(&vk, &instances));
90+
b.iter(|| {
91+
bundle
92+
.authorization()
93+
.proof()
94+
.unwrap()
95+
.verify(&vk, &instances)
96+
});
9097
});
9198
}
9299
}

rustfmt.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
reorder_imports = false

src/builder.rs

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,16 @@ impl BundleType {
7575
bundle_required: false,
7676
};
7777

78+
/// The default bundle with all flags enabled, including Asset Swaps.
79+
pub const DEFAULT_SWAP: BundleType = BundleType::Transactional {
80+
flags: Flags::ENABLED_WITH_SWAPS,
81+
bundle_required: false,
82+
};
83+
7884
/// The DISABLED bundle type does not permit any bundle to be produced, and when used in the
7985
/// builder will prevent any spends or outputs from being added.
8086
pub const DISABLED: BundleType = BundleType::Transactional {
81-
flags: Flags::from_parts(false, false, false),
87+
flags: Flags::from_parts(false, false, false, false),
8288
bundle_required: false,
8389
};
8490

@@ -654,6 +660,14 @@ impl Builder {
654660
}
655661
}
656662

663+
/// Returns true if the builder is empty.
664+
pub fn is_empty(&self) -> bool {
665+
self.spends.is_empty()
666+
&& self.outputs.is_empty()
667+
&& self.burn.is_empty()
668+
&& self.reference_notes.is_empty()
669+
}
670+
657671
/// Adds a note to be spent in this transaction.
658672
///
659673
/// - `note` is a spendable note, obtained by trial-decrypting an [`Action`] using the
@@ -803,36 +817,32 @@ impl Builder {
803817
self.bundle_type,
804818
self.spends,
805819
self.outputs,
820+
0,
806821
SpecificBuilderParams::BundleParams(self.burn),
807822
)
808823
}
809824

810825
/// Builds an action group containing the given spent and output notes.
811826
///
812827
/// The returned action group will have no proof or signatures; these can be applied with
813-
/// [`ActionGroup::create_proof`] and [`ActionGroup::apply_signatures`] respectively.
828+
/// [`Bundle::create_proof`] and [`Bundle::apply_signatures`] respectively.
814829
pub fn build_action_group<V: TryFrom<i64>>(
815830
self,
816831
rng: impl RngCore,
817-
timelimit: u32,
818-
) -> Result<Option<UnauthorizedActionGroupWithMetadata<V>>, BuildError> {
832+
expiry_height: u32,
833+
) -> Result<UnauthorizedBundleWithMetadata<V, OrchardZSA>, BuildError> {
819834
if !self.burn.is_empty() {
820835
return Err(BuildError::BurnNotEmptyInActionGroup);
821836
}
822-
Ok(bundle(
837+
bundle(
823838
rng,
824839
self.anchor,
825840
self.bundle_type,
826841
self.spends,
827842
self.outputs,
843+
expiry_height,
828844
SpecificBuilderParams::ActionGroupParams(self.reference_notes),
829-
)?
830-
.map(|(action_group, metadata)| {
831-
(
832-
ActionGroup::from_parts(action_group, timelimit, None),
833-
metadata,
834-
)
835-
}))
845+
)
836846
}
837847

838848
/// Builds a bundle containing the given spent notes and outputs along with their
@@ -1051,6 +1061,7 @@ fn build_bundle<B, R: RngCore>(
10511061
bundle_type: BundleType,
10521062
spends: Vec<SpendInfo>,
10531063
outputs: Vec<OutputInfo>,
1064+
expiry_height: u32,
10541065
burn: BTreeMap<AssetBase, NoteValue>,
10551066
reference_notes: BTreeMap<AssetBase, SpendInfo>,
10561067
specific_params: SpecificBuilderParams,
@@ -1213,6 +1224,10 @@ pub struct InProgress<P, S: InProgressSignatures> {
12131224

12141225
impl<P: fmt::Debug, S: InProgressSignatures> Authorization for InProgress<P, S> {
12151226
type SpendAuth = S::SpendAuth;
1227+
1228+
fn proof(&self) -> Option<&Proof> {
1229+
None
1230+
}
12161231
}
12171232

12181233
/// Marker for a bundle without a proof.
@@ -1302,8 +1317,8 @@ pub struct SigningMetadata {
13021317
/// If this action is spending a dummy note, this field holds that note's spend
13031318
/// authorizing key.
13041319
///
1305-
/// These keys are used automatically in [`Bundle<Unauthorized>::prepare`] or
1306-
/// [`Bundle<Unauthorized>::apply_signatures`] to sign dummy spends.
1320+
/// These keys are used automatically in [`Bundle<Unauthorized, _, _>::prepare`] or
1321+
/// [`Bundle<Unauthorized, _, _>::apply_signatures`] to sign dummy spends.
13071322
dummy_ask: Option<SpendAuthorizingKey>,
13081323
parts: SigningParts,
13091324
}
@@ -1448,23 +1463,15 @@ impl<V, P: OrchardPrimitives> Bundle<InProgress<Proof, Unauthorized>, V, P> {
14481463
})
14491464
.finalize()
14501465
}
1451-
}
14521466

1453-
impl<V, D: OrchardDomainCommon> Bundle<InProgress<Proof, Unauthorized>, V, D> {
1454-
/// Applies signatures to this action group, in order to authorize it.
1467+
/// Applies signatures to this bundle as an action group, in order to authorize it.
14551468
#[allow(clippy::type_complexity)]
14561469
pub fn apply_signatures_for_action_group<R: RngCore + CryptoRng>(
14571470
self,
14581471
mut rng: R,
14591472
action_group_digest: [u8; 32],
14601473
signing_keys: &[SpendAuthorizingKey],
1461-
) -> Result<
1462-
(
1463-
redpallas::SigningKey<Binding>,
1464-
Bundle<ActionGroupAuthorized, V, D>,
1465-
),
1466-
BuildError,
1467-
> {
1474+
) -> Result<(Bundle<ActionGroupAuthorized, V, D>, SigningKey<Binding>), BuildError> {
14681475
signing_keys
14691476
.iter()
14701477
.fold(
@@ -1587,20 +1594,14 @@ impl<V, D: OrchardDomainCommon> Bundle<InProgress<Proof, ActionGroupPartiallyAut
15871594
#[allow(clippy::type_complexity)]
15881595
pub fn finalize(
15891596
self,
1590-
) -> Result<
1591-
(
1592-
redpallas::SigningKey<Binding>,
1593-
Bundle<ActionGroupAuthorized, V, D>,
1594-
),
1595-
BuildError,
1596-
> {
1597+
) -> Result<(Bundle<ActionGroupAuthorized, V, D>, SigningKey<Binding>), BuildError> {
15971598
let bsk = self.authorization().sigs.bsk;
15981599
self.try_map_authorization(
15991600
&mut (),
16001601
|_, _, maybe| maybe.finalize(),
16011602
|_, partial| Ok(ActionGroupAuthorized::from_parts(partial.proof)),
16021603
)
1603-
.map(|bundle| (bsk, bundle))
1604+
.map(|bundle| (bundle, bsk))
16041605
}
16051606
}
16061607

src/bundle.rs

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use memuse::DynamicUsage;
2424
use crate::{
2525
action::Action,
2626
address::Address,
27-
bundle::commitments::{hash_bundle_auth_data, hash_bundle_txid_data},
27+
bundle::commitments::{hash_bundle_auth_data, hash_bundle_txid_data, hash_action_group},
2828
circuit::{Instance, Proof, VerifyingKey},
2929
keys::{IncomingViewingKey, OutgoingViewingKey, PreparedIncomingViewingKey},
3030
note::{AssetBase, Note},
@@ -35,6 +35,7 @@ use crate::{
3535
value::{NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum},
3636
Proof,
3737
};
38+
use crate::orchard_flavor::OrchardZSA;
3839

3940
#[cfg(feature = "circuit")]
4041
use crate::circuit::{Instance, VerifyingKey};
@@ -77,60 +78,81 @@ pub struct Flags {
7778
/// If `false`, all notes within [`Action`]s in the transaction's [`Bundle`] are
7879
/// guaranteed to be notes with native asset.
7980
zsa_enabled: bool,
81+
/// Flag denoting whether Asset Swaps are enabled.
82+
///
83+
/// If `false`, [`Bundle`] is guaranteed to contain only one ['ActionGroup'].
84+
swaps_enabled: bool,
8085
}
8186

8287
const FLAG_SPENDS_ENABLED: u8 = 0b0000_0001;
8388
const FLAG_OUTPUTS_ENABLED: u8 = 0b0000_0010;
8489
const FLAG_ZSA_ENABLED: u8 = 0b0000_0100;
85-
const FLAGS_EXPECTED_UNSET: u8 = !(FLAG_SPENDS_ENABLED | FLAG_OUTPUTS_ENABLED | FLAG_ZSA_ENABLED);
90+
const FLAG_SWAPS_ENABLED: u8 = 0b0000_1000;
91+
const FLAGS_EXPECTED_UNSET: u8 =
92+
!(FLAG_SPENDS_ENABLED | FLAG_OUTPUTS_ENABLED | FLAG_ZSA_ENABLED | FLAG_SWAPS_ENABLED);
8693

8794
impl Flags {
8895
/// Construct a set of flags from its constituent parts
8996
pub(crate) const fn from_parts(
9097
spends_enabled: bool,
9198
outputs_enabled: bool,
9299
zsa_enabled: bool,
100+
swaps_enabled: bool,
93101
) -> Self {
94102
Flags {
95103
spends_enabled,
96104
outputs_enabled,
97105
zsa_enabled,
106+
swaps_enabled,
98107
}
99108
}
100109

101-
/// The flag set with both spends and outputs enabled and ZSA disabled.
110+
/// The flag set with both spends and outputs enabled. ZSA and swaps are disabled.
102111
pub const ENABLED_WITHOUT_ZSA: Flags = Flags {
103112
spends_enabled: true,
104113
outputs_enabled: true,
105114
zsa_enabled: false,
115+
swaps_enabled: false,
106116
};
107117

108-
/// The flags set with spends, outputs and ZSA enabled.
118+
/// The flags set with spends, outputs and ZSA enabled. Swaps are disabled.
109119
pub const ENABLED_WITH_ZSA: Flags = Flags {
110120
spends_enabled: true,
111121
outputs_enabled: true,
112122
zsa_enabled: true,
123+
swaps_enabled: false,
124+
};
125+
126+
/// The flags set with spends, outputs, ZSA and swaps enabled.
127+
pub const ENABLED_WITH_SWAPS: Flags = Flags {
128+
spends_enabled: true,
129+
outputs_enabled: true,
130+
zsa_enabled: true,
131+
swaps_enabled: true,
113132
};
114133

115-
/// The flag set with spends and ZSA disabled.
134+
/// The flag set with spends, ZSA and swaps disabled.
116135
pub const SPENDS_DISABLED_WITHOUT_ZSA: Flags = Flags {
117136
spends_enabled: false,
118137
outputs_enabled: true,
119138
zsa_enabled: false,
139+
swaps_enabled: false,
120140
};
121141

122-
/// The flag set with spends disabled and ZSA enabled.
142+
/// The flag set with spends disabled and ZSA enabled. Swaps are disabled.
123143
pub const SPENDS_DISABLED_WITH_ZSA: Flags = Flags {
124144
spends_enabled: false,
125145
outputs_enabled: true,
126146
zsa_enabled: true,
147+
swaps_enabled: false,
127148
};
128149

129150
/// The flag set with outputs disabled and ZSA disabled.
130151
pub const OUTPUTS_DISABLED: Flags = Flags {
131152
spends_enabled: true,
132153
outputs_enabled: false,
133154
zsa_enabled: false,
155+
swaps_enabled: false,
134156
};
135157

136158
/// Flag denoting whether Orchard spends are enabled in the transaction.
@@ -190,6 +212,7 @@ impl Flags {
190212
spends_enabled: value & FLAG_SPENDS_ENABLED != 0,
191213
outputs_enabled: value & FLAG_OUTPUTS_ENABLED != 0,
192214
zsa_enabled: value & FLAG_ZSA_ENABLED != 0,
215+
swaps_enabled: value & FLAG_SWAPS_ENABLED != 0,
193216
})
194217
} else {
195218
None
@@ -201,6 +224,9 @@ impl Flags {
201224
pub trait Authorization: fmt::Debug {
202225
/// The authorization type of an Orchard action.
203226
type SpendAuth: fmt::Debug + Clone;
227+
228+
/// Return the proof component of the authorizing data.
229+
fn proof(&self) -> Option<&Proof>;
204230
}
205231

206232
/// A bundle of actions to be applied to the ledger.
@@ -535,6 +561,14 @@ impl Authorization for EffectsOnly {
535561
type SpendAuth = ();
536562
}
537563

564+
impl<A: Authorization, V: Copy + Into<i64>> Bundle<A, V, OrchardZSA> {
565+
/// Computes a commitment to the effects of this bundle,
566+
/// assuming that the bundle represents an action group inside a swap bundle.
567+
pub fn action_group_commitment(&self) -> BundleCommitment {
568+
BundleCommitment(hash_action_group(self))
569+
}
570+
}
571+
538572
/// Authorizing data for a bundle of actions, ready to be committed to the ledger.
539573
#[derive(Debug, Clone)]
540574
pub struct Authorized {
@@ -544,6 +578,11 @@ pub struct Authorized {
544578

545579
impl Authorization for Authorized {
546580
type SpendAuth = VerSpendAuthSig;
581+
582+
/// Return the proof component of the authorizing data.
583+
fn proof(&self) -> Option<&Proof> {
584+
Some(&self.proof)
585+
}
547586
}
548587

549588
impl Authorized {
@@ -555,11 +594,6 @@ impl Authorized {
555594
}
556595
}
557596

558-
/// Return the proof component of the authorizing data.
559-
pub fn proof(&self) -> &Proof {
560-
&self.proof
561-
}
562-
563597
/// Return the versioned binding signature.
564598
pub fn binding_signature(&self) -> &VerBindingSig {
565599
&self.binding_signature
@@ -584,6 +618,7 @@ impl<V, P: OrchardPrimitives> Bundle<Authorized, V, P> {
584618
pub fn verify_proof(&self, vk: &VerifyingKey) -> Result<(), halo2_proofs::plonk::Error> {
585619
self.authorization()
586620
.proof()
621+
.unwrap()
587622
.verify(vk, &self.to_instances())
588623
}
589624
}
@@ -767,8 +802,8 @@ pub mod testing {
767802

768803
prop_compose! {
769804
/// Create an arbitrary set of flags.
770-
pub fn arb_flags()(spends_enabled in prop::bool::ANY, outputs_enabled in prop::bool::ANY, zsa_enabled in prop::bool::ANY) -> Flags {
771-
Flags::from_parts(spends_enabled, outputs_enabled, zsa_enabled)
805+
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 {
806+
Flags::from_parts(spends_enabled, outputs_enabled, zsa_enabled, swaps_enabled)
772807
}
773808
}
774809

@@ -803,7 +838,7 @@ pub mod testing {
803838
balances.into_iter().sum::<Result<ValueSum, _>>().unwrap(),
804839
burn,
805840
anchor,
806-
None,
841+
0,
807842
super::EffectsOnly,
808843
)
809844
}
@@ -836,6 +871,7 @@ pub mod testing {
836871
balances.into_iter().sum::<Result<ValueSum, _>>().unwrap(),
837872
burn,
838873
anchor,
874+
0,
839875
Authorized {
840876
proof: Proof::new(fake_proof),
841877
binding_signature: VerBindingSig::new(P::default_sighash_version(), sk.sign(rng, &fake_sighash)),

src/bundle/batch.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ impl BatchValidator {
6262

6363
bundle
6464
.authorization()
65-
.proof()
65+
.proof
6666
.add_to_batch(&mut self.proofs, bundle.to_instances());
6767
}
6868

0 commit comments

Comments
 (0)