From 30f01d122cc9ad79c85ae4816cef5e0215f64f72 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 15 Apr 2021 16:14:34 +1200 Subject: [PATCH 1/6] Bundle builder --- Cargo.toml | 1 + src/builder.rs | 472 ++++++++++++++++++++++++++++++++++++ src/bundle.rs | 14 +- src/keys.rs | 19 +- src/lib.rs | 1 + src/note.rs | 23 +- src/note/commitment.rs | 2 +- src/primitives/redpallas.rs | 10 +- src/tree.rs | 15 +- src/value.rs | 2 +- 10 files changed, 550 insertions(+), 9 deletions(-) create mode 100644 src/builder.rs diff --git a/Cargo.toml b/Cargo.toml index 6c4a15f9..8d276f62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ ff = "0.9" fpe = "0.4" group = "0.9" rand = "0.8" +rand_7 = { package = "rand", version = "0.7" } nonempty = "0.6" subtle = "2.3" diff --git a/src/builder.rs b/src/builder.rs new file mode 100644 index 00000000..39af8eb3 --- /dev/null +++ b/src/builder.rs @@ -0,0 +1,472 @@ +//! Logic for building Orchard components of transactions. + +use std::iter; + +use ff::Field; +use nonempty::NonEmpty; +use pasta_curves::pallas; +use rand::RngCore; + +use crate::{ + bundle::{Action, Authorization, Authorized, Bundle, Flags}, + circuit::{Circuit, Proof, ProvingKey}, + keys::{ + FullViewingKey, OutgoingViewingKey, SpendAuthorizingKey, SpendValidatingKey, SpendingKey, + }, + primitives::redpallas::{self, Binding, SpendAuth}, + tree::{Anchor, MerklePath}, + value::{self, NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum}, + Address, EncryptedNote, Note, +}; + +const MIN_ACTIONS: usize = 2; + +#[derive(Debug)] +pub enum Error { + MissingSignatures, + Proof(halo2::plonk::Error), + ValueSum(value::OverflowError), +} + +impl From for Error { + fn from(e: halo2::plonk::Error) -> Self { + Error::Proof(e) + } +} + +impl From for Error { + fn from(e: value::OverflowError) -> Self { + Error::ValueSum(e) + } +} + +/// Information about a specific note to be spent in an [`Action`]. +#[derive(Debug)] +struct SpendInfo { + dummy_sk: Option, + fvk: FullViewingKey, + note: Note, + merkle_path: MerklePath, +} + +impl SpendInfo { + /// 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, + 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<()>, +} + +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.default_address(); + + RecipientInfo { + ovk: None, + recipient, + value: NoteValue::default(), + 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) -> Result { + 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<(Option, SpendValidatingKey)>, + Circuit, + ) { + let v_net = self.value_sum().expect("already checked this"); + let cv_net = ValueCommitment::derive(v_net, self.rcv); + + let nf_old = self.spend.note.nullifier(&self.spend.fvk); + let ak: SpendValidatingKey = self.spend.fvk.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.clone(), + rng, + ); + let cm_new = note.commitment(); + + // TODO: Note encryption + let encrypted_note = EncryptedNote; + + ( + Action::from_parts( + nf_old, + rk, + cm_new.into(), + encrypted_note, + cv_net, + ( + self.spend.dummy_sk.as_ref().map(SpendAuthorizingKey::from), + ak, + ), + ), + Circuit {}, + ) + } +} + +/// A builder that constructs a [`Bundle`] from a set of notes to be spent, and recipients +/// to receive funds. +pub struct Builder { + spends: Vec, + recipients: Vec, + flags: Flags, + anchor: Anchor, +} + +impl Builder { + pub fn new(flags: Flags, anchor: Anchor) -> Self { + Builder { + spends: vec![], + recipients: vec![], + flags, + anchor, + } + } + + /// Adds a note to be spent in this transaction. + /// + /// Returns an error if the given Merkle path does not have the required anchor for + /// the given note. + pub fn add_spend( + &mut self, + fvk: FullViewingKey, + note: Note, + merkle_path: MerklePath, + ) -> Result<(), &'static str> { + if !self.flags.spends_enabled() { + return Err("Spends are not enabled for this builder"); + } + + // Consistency check: all anchors must be equal. + let cm = note.commitment(); + // TODO: Once we have tree logic. + // let path_root: bls12_381::Scalar = merkle_path.root(cmu).into(); + // if path_root != anchor { + // return Err(Error::AnchorMismatch); + // } + + self.spends.push(SpendInfo { + dummy_sk: None, + fvk, + 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<()>, + ) -> Result<(), &'static str> { + if !self.flags.outputs_enabled() { + return Err("Outputs are not enabled for this builder"); + } + + self.recipients.push(RecipientInfo { + ovk, + recipient, + value, + memo, + }); + + Ok(()) + } + + /// Builds a bundle containing the given spent notes and recipients. + /// + /// This API assumes that none of the notes being spent are controlled by (threshold) + /// multisignatures, and immediately constructs the bundle proof. + fn build( + mut self, + mut rng: impl RngCore, + pk: &ProvingKey, + ) -> Result, Error> { + // Pair up the spends and recipients, extending with dummy values as necessary. + // + // TODO: Do we want to shuffle the order like we do for Sapling? And if we do, do + // we need the extra logic for mapping the user-provided input order to the + // shuffled order? + 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), + ); + + 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: ValueSum = pre_actions + .iter() + .fold(Ok(ValueSum::default()), |acc, action| { + acc? + action.value_sum()? + })?; + + // 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); + + // Create the proof. + let instances: Vec<_> = actions + .iter() + .map(|a| a.to_instance(flags, anchor.clone())) + .collect(); + let proof = Proof::create(pk, &circuits, &instances)?; + + Ok(Bundle::from_parts( + NonEmpty::from_vec(actions).unwrap(), + flags, + value_balance, + anchor, + Unauthorized { proof, bsk }, + )) + } +} + +/// Marker for an unauthorized bundle, with a proof but no signatures. +#[derive(Debug)] +pub struct Unauthorized { + proof: Proof, + bsk: redpallas::SigningKey, +} + +impl Authorization for Unauthorized { + type SpendAuth = (Option, SpendValidatingKey); +} + +/// Marker for a partially-authorized bundle, in the process of being signed. +#[derive(Debug)] +pub struct PartiallyAuthorized { + proof: Proof, + binding_signature: redpallas::Signature, + sighash: [u8; 32], +} + +impl Authorization for PartiallyAuthorized { + type SpendAuth = (Option>, SpendValidatingKey); +} + +impl Bundle { + /// 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 { + self.authorize( + &mut rng, + |rng, _, (dummy_ask, ak)| { + ( + if let Some(ask) = dummy_ask { + // We can create signatures for dummy spends immediately. + Some(ask.sign(rng, &sighash)) + } else { + None + }, + ak, + ) + }, + |rng, unauth| PartiallyAuthorized { + proof: unauth.proof, + binding_signature: unauth.bsk.sign(rng, &sighash), + sighash, + }, + ) + } + + /// Applies signatures to this bundle, in order to authorize it. + pub fn apply_signatures( + self, + mut rng: R, + sighash: [u8; 32], + signing_keys: &[SpendAuthorizingKey], + ) -> Result, Error> { + signing_keys + .iter() + .fold(self.prepare(&mut rng, sighash), |partial, ask| { + partial.sign(&mut rng, ask) + }) + .finalize() + } +} + +impl Bundle { + /// 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.authorize( + &mut rng, + |rng, partial, (sig, ak)| { + ( + sig.or_else(|| { + if ak == expected_ak { + Some(ask.sign(rng, &partial.sighash)) + } else { + None + } + }), + ak, + ) + }, + |_, partial| partial, + ) + } + + /// Finalizes this bundle, enabling it to be included in a transaction. + /// + /// Returns an error if any signatures are missing. + pub fn finalize(self) -> Result, Error> { + self.try_authorize( + &mut (), + |_, _, (sig, _)| match sig { + Some(sig) => Ok(sig), + None => Err(Error::MissingSignatures), + }, + |_, partial| { + Ok(Authorized::from_parts( + partial.proof, + partial.binding_signature, + )) + }, + ) + } +} + +#[cfg(test)] +mod tests { + use rand::rngs::OsRng; + + use super::Builder; + use crate::{ + bundle::Flags, + circuit::ProvingKey, + keys::{FullViewingKey, SpendingKey}, + tree::Anchor, + value::{NoteValue, ValueSum}, + }; + + #[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.default_address(); + + let mut builder = Builder::new(Flags::from_parts(true, true), Anchor); + builder + .add_recipient(None, recipient, NoteValue::from_raw(5000), None) + .unwrap(); + let bundle = dbg!(builder + .build(&mut rng, &pk) + .unwrap() + .prepare(rand_7::rngs::OsRng, [0; 32])) + .finalize() + .unwrap(); + assert_eq!(bundle.value_balance(), &ValueSum::from_raw(-5000)) + } +} diff --git a/src/bundle.rs b/src/bundle.rs index 5141b472..37254e3b 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -3,7 +3,7 @@ use nonempty::NonEmpty; use crate::{ - circuit::Proof, + circuit::{Instance, Proof}, note::{EncryptedNote, ExtractedNoteCommitment, Nullifier}, primitives::redpallas::{self, Binding, SpendAuth}, tree::Anchor, @@ -107,6 +107,18 @@ impl Action { 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. diff --git a/src/keys.rs b/src/keys.rs index f760b389..52074693 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -68,13 +68,22 @@ impl SpendingKey { /// /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents #[derive(Debug)] -pub(crate) struct SpendAuthorizingKey(redpallas::SigningKey); +pub struct SpendAuthorizingKey(redpallas::SigningKey); impl SpendAuthorizingKey { /// Derives ask from sk. Internal use only, does not enforce all constraints. fn derive_inner(sk: &SpendingKey) -> pallas::Scalar { to_scalar(prf_expand(&sk.0, &[0x06])) } + + /// Creates a spend authorization signature over the given message. + pub fn sign( + &self, + rng: R, + msg: &[u8], + ) -> redpallas::Signature { + self.0.sign(rng, msg) + } } impl From<&SpendingKey> for SpendAuthorizingKey { @@ -101,7 +110,7 @@ impl From<&SpendingKey> for SpendAuthorizingKey { /// /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents #[derive(Debug)] -pub(crate) struct SpendValidatingKey(redpallas::VerificationKey); +pub struct SpendValidatingKey(redpallas::VerificationKey); impl From<&SpendAuthorizingKey> for SpendValidatingKey { fn from(ask: &SpendAuthorizingKey) -> Self { @@ -109,6 +118,12 @@ impl From<&SpendAuthorizingKey> for SpendValidatingKey { } } +impl PartialEq for SpendValidatingKey { + fn eq(&self, other: &Self) -> bool { + <[u8; 32]>::from(&self.0).eq(&<[u8; 32]>::from(&other.0)) + } +} + impl SpendValidatingKey { /// Randomizes this spend validating key with the given `randomizer`. pub fn randomize(&self, randomizer: &pallas::Scalar) -> redpallas::VerificationKey { diff --git a/src/lib.rs b/src/lib.rs index de14b2ba..0d56315c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,7 @@ #![deny(unsafe_code)] mod address; +mod builder; pub mod bundle; mod circuit; mod constants; diff --git a/src/note.rs b/src/note.rs index 39227cf1..0294a079 100644 --- a/src/note.rs +++ b/src/note.rs @@ -16,8 +16,8 @@ mod nullifier; pub use self::nullifier::Nullifier; /// The ZIP 212 seed randomness for a note. -#[derive(Debug)] -struct RandomSeed([u8; 32]); +#[derive(Clone, Debug)] +pub(crate) struct RandomSeed([u8; 32]); impl RandomSeed { pub(crate) fn random(rng: &mut impl RngCore) -> Self { @@ -60,6 +60,25 @@ pub struct Note { } impl Note { + /// Generates a new note. + /// + /// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend]. + /// + /// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend + pub(crate) fn new( + recipient: Address, + value: NoteValue, + rho: Nullifier, + mut rng: impl RngCore, + ) -> Self { + Note { + recipient, + value, + rho, + rseed: RandomSeed::random(&mut rng), + } + } + /// Generates a dummy spent note. /// /// Defined in [Zcash Protocol Spec § 4.8.3: Dummy Notes (Orchard)][orcharddummynotes]. diff --git a/src/note/commitment.rs b/src/note/commitment.rs index 212b2980..00c6d853 100644 --- a/src/note/commitment.rs +++ b/src/note/commitment.rs @@ -56,7 +56,7 @@ impl NoteCommitment { } /// The x-coordinate of the commitment to a note. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct ExtractedNoteCommitment(pub(super) pallas::Base); impl From for ExtractedNoteCommitment { diff --git a/src/primitives/redpallas.rs b/src/primitives/redpallas.rs index 848bc8f7..7db402ec 100644 --- a/src/primitives/redpallas.rs +++ b/src/primitives/redpallas.rs @@ -3,6 +3,7 @@ use std::convert::{TryFrom, TryInto}; use pasta_curves::pallas; +use rand_7::{CryptoRng, RngCore}; /// A RedPallas signature type. pub trait SigType: reddsa::SigType + private::Sealed {} @@ -39,8 +40,15 @@ impl TryFrom<[u8; 32]> for SigningKey { } } +impl SigningKey { + /// Creates a signature of type `T` on `msg` using this `SigningKey`. + pub fn sign(&self, rng: R, msg: &[u8]) -> Signature { + Signature(self.0.sign(rng, msg)) + } +} + /// A RedPallas verification key. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct VerificationKey(reddsa::VerificationKey); impl From> for [u8; 32] { diff --git a/src/tree.rs b/src/tree.rs index e6157326..a9cd1074 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -1,3 +1,16 @@ +use rand::RngCore; + /// The root of an Orchard commitment tree. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Anchor; + +#[derive(Debug)] +pub struct MerklePath; + +impl MerklePath { + /// Generates a dummy Merkle path for use in dummy spent notes. + pub(crate) fn dummy(rng: &mut impl RngCore) -> Self { + // TODO + MerklePath + } +} diff --git a/src/value.rs b/src/value.rs index ce1ae43c..2f1ede92 100644 --- a/src/value.rs +++ b/src/value.rs @@ -76,7 +76,7 @@ impl Sub for NoteValue { } /// A sum of Orchard note values. -#[derive(Clone, Copy, Debug, Default)] +#[derive(Clone, Copy, Debug, Default, PartialEq)] pub struct ValueSum(i64); impl ValueSum { From 6d4ceb989f497e76ef3fb59a40dc4cd980378531 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 27 Apr 2021 14:13:48 +1200 Subject: [PATCH 2/6] Fix CI to run builder tests successfully Now that the tests include real prover logic, we need to run them in release mode. --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df41eeb1..f1a30747 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: test - args: --verbose --all + args: --verbose --release bitrot: name: Bitrot check @@ -37,7 +37,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: build - args: --all --benches + args: --benches book: name: Book tests @@ -156,4 +156,4 @@ jobs: - uses: actions-rs/cargo@v1 with: command: fmt - args: --all -- --check + args: -- --check From 186914166af51094e041e7af4b0d236096b51f31 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 28 Apr 2021 09:06:33 +1200 Subject: [PATCH 3/6] Use `zero` instead of `default` for empty values --- src/builder.rs | 4 ++-- src/value.rs | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 39af8eb3..f3e09f12 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -86,7 +86,7 @@ impl RecipientInfo { RecipientInfo { ovk: None, recipient, - value: NoteValue::default(), + value: NoteValue::zero(), memo: None, } } @@ -280,7 +280,7 @@ impl Builder { // Determine the value balance for this bundle, ensuring it is valid. let value_balance: ValueSum = pre_actions .iter() - .fold(Ok(ValueSum::default()), |acc, action| { + .fold(Ok(ValueSum::zero()), |acc, action| { acc? + action.value_sum()? })?; diff --git a/src/value.rs b/src/value.rs index 2f1ede92..c636d173 100644 --- a/src/value.rs +++ b/src/value.rs @@ -80,6 +80,11 @@ impl Sub for NoteValue { pub struct ValueSum(i64); impl ValueSum { + pub(crate) fn zero() -> Self { + // Default for i64 is zero. + Default::default() + } + /// Creates a value sum from its raw numeric value. /// /// This only enforces that the value is a signed 63-bit integer. Callers should From 374391b217644456349fac303b82d3f6ac265247 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 28 Apr 2021 14:19:58 +1200 Subject: [PATCH 4/6] Bring in reddsa fix that re-enables tests in debug mode --- .github/workflows/ci.yml | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f1a30747..e17daf98 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: test - args: --verbose --release + args: --verbose bitrot: name: Bitrot check diff --git a/Cargo.toml b/Cargo.toml index 8d276f62..decaf783 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ rev = "b55a6960dfafd7f767e2820ddf1adaa499322f98" [dependencies.reddsa] git = "https://github.com/str4d/redjubjub.git" -rev = "62a26a02fda5165b68a4b4773aa18ea6eb749dbc" +rev = "f1e76dbc9abf2b68cc609e874fe39f2a15b75b12" [dev-dependencies] criterion = "0.3" From 223b7ac5338457255a9b92306039709d6b543dbb Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 29 Apr 2021 10:38:08 +1200 Subject: [PATCH 5/6] Replace signing metadata tuple with struct This enables the dummy-only first field to be properly documented. --- src/builder.rs | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index f3e09f12..413b158f 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -119,13 +119,7 @@ impl ActionInfo { /// 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<(Option, SpendValidatingKey)>, - Circuit, - ) { + fn build(self, mut rng: impl RngCore) -> (Action, Circuit) { let v_net = self.value_sum().expect("already checked this"); let cv_net = ValueCommitment::derive(v_net, self.rcv); @@ -152,10 +146,10 @@ impl ActionInfo { cm_new.into(), encrypted_note, cv_net, - ( - self.spend.dummy_sk.as_ref().map(SpendAuthorizingKey::from), + SigningMetadata { + dummy_ask: self.spend.dummy_sk.as_ref().map(SpendAuthorizingKey::from), ak, - ), + }, ), Circuit {}, ) @@ -326,7 +320,21 @@ pub struct Unauthorized { } impl Authorization for Unauthorized { - type SpendAuth = (Option, SpendValidatingKey); + type SpendAuth = SigningMetadata; +} + +/// Container for metadata needed to sign an [`Action`]. +#[derive(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, + /// The spend validating key for this action. Used to match spend authorizing keys to + /// actions they can create signatures for. + ak: SpendValidatingKey, } /// Marker for a partially-authorized bundle, in the process of being signed. @@ -352,7 +360,7 @@ impl Bundle { ) -> Bundle { self.authorize( &mut rng, - |rng, _, (dummy_ask, ak)| { + |rng, _, SigningMetadata { dummy_ask, ak }| { ( if let Some(ask) = dummy_ask { // We can create signatures for dummy spends immediately. From d383ff5054114833916e2aeb6c46c54f9fc533e8 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 29 Apr 2021 10:57:53 +1200 Subject: [PATCH 6/6] Fix clippy lints --- src/builder.rs | 10 +++------- src/tree.rs | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 413b158f..88a86868 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -190,7 +190,7 @@ impl Builder { } // Consistency check: all anchors must be equal. - let cm = note.commitment(); + let _cm = note.commitment(); // TODO: Once we have tree logic. // let path_root: bls12_381::Scalar = merkle_path.root(cmu).into(); // if path_root != anchor { @@ -362,12 +362,8 @@ impl Bundle { &mut rng, |rng, _, SigningMetadata { dummy_ask, ak }| { ( - if let Some(ask) = dummy_ask { - // We can create signatures for dummy spends immediately. - Some(ask.sign(rng, &sighash)) - } else { - None - }, + // We can create signatures for dummy spends immediately. + dummy_ask.map(|ask| ask.sign(rng, &sighash)), ak, ) }, diff --git a/src/tree.rs b/src/tree.rs index a9cd1074..63089e31 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -9,7 +9,7 @@ pub struct MerklePath; impl MerklePath { /// Generates a dummy Merkle path for use in dummy spent notes. - pub(crate) fn dummy(rng: &mut impl RngCore) -> Self { + pub(crate) fn dummy(_rng: &mut impl RngCore) -> Self { // TODO MerklePath }