Skip to content

Commit

Permalink
Add SwapBundle functionalities
Browse files Browse the repository at this point in the history
  • Loading branch information
ConstanceBeguier committed Oct 14, 2024
1 parent d1f9b76 commit 5871966
Show file tree
Hide file tree
Showing 5 changed files with 269 additions and 23 deletions.
100 changes: 100 additions & 0 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use rand::{prelude::SliceRandom, CryptoRng, RngCore};
use zcash_note_encryption_zsa::NoteEncryption;

use crate::builder::BuildError::{BurnNative, BurnZero};
use crate::bundle::ActionGroupAuthorized;
use crate::{
action::Action,
address::Address,
Expand Down Expand Up @@ -1060,6 +1061,17 @@ impl InProgressSignatures for PartiallyAuthorized {
type SpendAuth = MaybeSigned;
}

/// Marker for a partially-authorized bundle, in the process of being signed.
#[derive(Debug)]
pub struct ActionGroupPartiallyAuthorized {
bsk: redpallas::SigningKey<Binding>,
sighash: [u8; 32],
}

impl InProgressSignatures for ActionGroupPartiallyAuthorized {
type SpendAuth = MaybeSigned;
}

/// A heisen[`Signature`] for a particular [`Action`].
///
/// [`Signature`]: redpallas::Signature
Expand Down Expand Up @@ -1109,6 +1121,35 @@ impl<P: fmt::Debug, V, D: OrchardDomainCommon> Bundle<InProgress<P, Unauthorized
}
}

impl<P: fmt::Debug, V, D: OrchardDomainCommon> Bundle<InProgress<P, Unauthorized>, V, D> {
/// 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_for_action_group<R: RngCore + CryptoRng>(
self,
mut rng: R,
sighash: [u8; 32],
) -> Bundle<InProgress<P, ActionGroupPartiallyAuthorized>, V, D> {
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: ActionGroupPartiallyAuthorized {
bsk: auth.sigs.bsk,
sighash,
},
},
)
}
}

impl<V, D: OrchardDomainCommon> Bundle<InProgress<Proof, Unauthorized>, V, D> {
/// Applies signatures to this bundle, in order to authorize it.
///
Expand All @@ -1129,6 +1170,47 @@ impl<V, D: OrchardDomainCommon> Bundle<InProgress<Proof, Unauthorized>, V, D> {
}
}

impl<V, D: OrchardDomainCommon> Bundle<InProgress<Proof, Unauthorized>, V, D> {
/// Applies signatures to this action group, in order to authorize it.
pub fn apply_signatures_for_action_group<R: RngCore + CryptoRng>(
self,
mut rng: R,
sighash: [u8; 32],
signing_keys: &[SpendAuthorizingKey],
) -> Result<Bundle<ActionGroupAuthorized, V, D>, BuildError> {
signing_keys
.iter()
.fold(
self.prepare_for_action_group(&mut rng, sighash),
|partial, ask| partial.sign(&mut rng, ask),
)
.finalize()
}
}

impl<P: fmt::Debug, V, D: OrchardDomainCommon>
Bundle<InProgress<P, ActionGroupPartiallyAuthorized>, V, D>
{
/// Signs this action group with the given [`SpendAuthorizingKey`].
///
/// This will apply signatures for all notes controlled by this spending key.
pub fn sign<R: RngCore + CryptoRng>(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,
)
}
}

impl<P: fmt::Debug, V, D: OrchardDomainCommon> Bundle<InProgress<P, PartiallyAuthorized>, V, D> {
/// Signs this bundle with the given [`SpendAuthorizingKey`].
///
Expand Down Expand Up @@ -1210,6 +1292,24 @@ impl<V, D: OrchardDomainCommon> Bundle<InProgress<Proof, PartiallyAuthorized>, V
}
}

impl<V, D: OrchardDomainCommon> Bundle<InProgress<Proof, ActionGroupPartiallyAuthorized>, V, D> {
/// Finalizes this bundle, enabling it to be included in a transaction.
///
/// Returns an error if any signatures are missing.
pub fn finalize(self) -> Result<Bundle<ActionGroupAuthorized, V, D>, BuildError> {
self.try_map_authorization(
&mut (),
|_, _, maybe| maybe.finalize(),
|_, partial| {
Ok(ActionGroupAuthorized::from_parts(
partial.proof,
partial.sigs.bsk,
))
},
)
}
}

/// A trait that provides a minimized view of an Orchard input suitable for use in
/// fee and change calculation.
pub trait InputView<NoteRef> {
Expand Down
124 changes: 109 additions & 15 deletions src/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub use batch::BatchValidator;
use core::fmt;

use blake2b_simd::Hash as Blake2bHash;
use k256::elliptic_curve::rand_core::{CryptoRng, RngCore};
use memuse::DynamicUsage;
use nonempty::NonEmpty;
use zcash_note_encryption_zsa::{try_note_decryption, try_output_recovery_with_ovk};
Expand Down Expand Up @@ -457,21 +458,6 @@ impl<A: Authorization, V, D: OrchardDomainCommon> Bundle<A, V, D> {
}
}

/// A swap bundle to be applied to the ledger.
#[derive(Clone, Debug)]
pub struct SwapBundle<V> {
/// The list of action groups that make up this swap bundle.
action_groups: Vec<Bundle<Authorized, V, OrchardZSA>>,
/// Orchard-specific transaction-level flags for this swap.
flags: Flags,
/// The net value moved out of this swap.
///
/// This is the sum of Orchard spends minus the sum of Orchard outputs.
value_balance: V,
/// The binding signature for this swap.
binding_signature: redpallas::Signature<Binding>,
}

pub(crate) fn derive_bvk<'a, A: 'a, V: Clone + Into<i64>, FL: 'a + OrchardFlavor>(
actions: impl IntoIterator<Item = &'a Action<A, FL>>,
value_balance: V,
Expand Down Expand Up @@ -516,6 +502,82 @@ impl<A: Authorization, V: Copy + Into<i64>, FL: OrchardFlavor> Bundle<A, V, FL>
}
}

/// A swap bundle to be applied to the ledger.
#[derive(Clone, Debug)]
pub struct SwapBundle<V> {
/// The list of action groups that make up this swap bundle.
action_groups: Vec<Bundle<ActionGroupAuthorized, V, OrchardZSA>>,
/// The net value moved out of this swap.
///
/// This is the sum of Orchard spends minus the sum of Orchard outputs.
value_balance: V,
/// The binding signature for this swap.
binding_signature: redpallas::Signature<Binding>,
}

impl<V: Copy + Into<i64> + std::iter::Sum> SwapBundle<V> {
/// Constructs a `Bundle` from its constituent parts.
pub fn new<R: RngCore + CryptoRng>(
rng: R,
action_groups: Vec<Bundle<ActionGroupAuthorized, V, OrchardZSA>>,
) -> Self {
let value_balance = action_groups.iter().map(|a| *a.value_balance()).sum();
let bsk = action_groups
.iter()
.map(|a| ValueCommitTrapdoor::from_bsk(a.authorization().bsk))
.sum::<ValueCommitTrapdoor>()
.into_bsk();
let sighash = BundleCommitment(hash_action_groups_txid_data(
action_groups.iter().collect(),
value_balance,
))
.into();
let binding_signature = bsk.sign(rng, &sighash);
SwapBundle {
action_groups,
value_balance,
binding_signature,
}
}
}

impl<V> SwapBundle<V> {
/// Returns the list of action groups that make up this swapbundle.
pub fn action_groups(&self) -> &Vec<Bundle<ActionGroupAuthorized, V, OrchardZSA>> {
&self.action_groups
}

/// Returns the binding signature of this swap bundle.
pub fn binding_signature(&self) -> &redpallas::Signature<Binding> {
&self.binding_signature
}
}

impl<V: Copy + Into<i64>> SwapBundle<V> {
/// Computes a commitment to the effects of this swap bundle, suitable for inclusion
/// within a transaction ID.
pub fn commitment(&self) -> BundleCommitment {
BundleCommitment(hash_action_groups_txid_data(
self.action_groups.iter().collect(),
self.value_balance,
))
}

/// Returns the transaction binding validating key for this swap bundle.
pub fn binding_validating_key(&self) -> redpallas::VerificationKey<Binding> {
let actions = self
.action_groups
.iter()
.flat_map(|ag| ag.actions())
.collect::<Vec<_>>();
derive_bvk(
actions,
self.value_balance,
std::iter::empty::<(AssetBase, NoteValue)>(),
)
}
}

/// Authorizing data for a bundle of actions, ready to be committed to the ledger.
#[derive(Debug, Clone)]
pub struct Authorized {
Expand Down Expand Up @@ -563,6 +625,38 @@ impl<V, D: OrchardDomainCommon> Bundle<Authorized, V, D> {
}
}

/// Authorizing data for an action group, ready to be sent to the matcher.
#[derive(Debug, Clone)]
pub struct ActionGroupAuthorized {
proof: Proof,
bsk: redpallas::SigningKey<Binding>,
}

impl Authorization for ActionGroupAuthorized {
type SpendAuth = redpallas::Signature<SpendAuth>;
}

impl ActionGroupAuthorized {
/// Constructs the authorizing data for a bundle of actions from its constituent parts.
pub fn from_parts(proof: Proof, bsk: redpallas::SigningKey<Binding>) -> Self {
ActionGroupAuthorized { proof, bsk }
}

/// Return the proof component of the authorizing data.
pub fn proof(&self) -> &Proof {
&self.proof
}
}

impl<V, D: OrchardDomainCommon> Bundle<ActionGroupAuthorized, V, D> {
/// Verifies the proof for this bundle.
pub fn verify_proof(&self, vk: &VerifyingKey) -> Result<(), halo2_proofs::plonk::Error> {
self.authorization()
.proof()
.verify(vk, &self.to_instances())
}
}

impl<V: DynamicUsage, D: OrchardDomainCommon> DynamicUsage for Bundle<Authorized, V, D> {
fn dynamic_usage(&self) -> usize {
self.actions.dynamic_usage()
Expand Down
10 changes: 10 additions & 0 deletions src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,12 @@ impl Add<&ValueCommitTrapdoor> for ValueCommitTrapdoor {
}
}

impl Sum for ValueCommitTrapdoor {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
iter.fold(ValueCommitTrapdoor::zero(), |acc, cv| acc + &cv)
}
}

impl<'a> Sum<&'a ValueCommitTrapdoor> for ValueCommitTrapdoor {
fn sum<I: Iterator<Item = &'a ValueCommitTrapdoor>>(iter: I) -> Self {
iter.fold(ValueCommitTrapdoor::zero(), |acc, cv| acc + cv)
Expand All @@ -310,6 +316,10 @@ impl ValueCommitTrapdoor {
// TODO: impl From<pallas::Scalar> for redpallas::SigningKey.
self.0.to_repr().try_into().unwrap()
}

pub(crate) fn from_bsk(bsk: redpallas::SigningKey<Binding>) -> Self {
ValueCommitTrapdoor(pallas::Scalar::from_repr(bsk.into()).unwrap())
}
}

/// A commitment to a [`ValueSum`].
Expand Down
27 changes: 25 additions & 2 deletions tests/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use bridgetree::BridgeTree;
use incrementalmerkletree::Hashable;
use orchard::{
builder::{Builder, BundleType},
bundle::{Authorized, Flags},
bundle::{ActionGroupAuthorized, Authorized, Flags, SwapBundle},
circuit::{ProvingKey, VerifyingKey},
keys::{FullViewingKey, PreparedIncomingViewingKey, Scope, SpendAuthorizingKey, SpendingKey},
note::{AssetBase, ExtractedNoteCommitment},
Expand Down Expand Up @@ -34,12 +34,35 @@ pub fn verify_bundle<FL: OrchardFlavor>(
);
}

pub fn verify_swap_bundle(swap_bundle: &SwapBundle<i64>, 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()) {
assert!(matches!(action_group.verify_proof(vk), Ok(())));
let action_group_sighash: [u8; 32] = action_group.commitment().into();
for action in action_group.actions() {
assert_eq!(
action
.rk()
.verify(&action_group_sighash, action.authorization()),
Ok(())
);
}
}

let sighash = swap_bundle.commitment().into();
let bvk = swap_bundle.binding_validating_key();
assert_eq!(
bvk.verify(&sighash, swap_bundle.binding_signature()),
Ok(())
);
}

// Verify an action group
// - verify the proof
// - verify the signature on each action
// - do not verify the binding signature because for some asset, the value balance could be not zero
pub fn verify_action_group<FL: OrchardFlavor>(
bundle: &Bundle<Authorized, i64, FL>,
bundle: &Bundle<ActionGroupAuthorized, i64, FL>,
vk: &VerifyingKey,
verify_proof: bool,
) {
Expand Down
Loading

0 comments on commit 5871966

Please sign in to comment.