diff --git a/CHANGELOG.md b/CHANGELOG.md index 57233897..dc8c6280 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ and this project adheres to Rust's notion of - `orchard::note`: - `RandomSeed` - `Note::{from_parts, rseed}` +- `orchard::builder::SpendInfo::new` +- `orchard::circuit::Circuit::from_action_context` ## [0.2.0] - 2022-06-24 ### Added diff --git a/src/builder.rs b/src/builder.rs index fe6b6226..7d92a490 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -4,7 +4,6 @@ use core::fmt; use core::iter; use ff::Field; -use halo2_proofs::circuit::Value; use nonempty::NonEmpty; use pasta_curves::pallas; use rand::{prelude::SliceRandom, CryptoRng, RngCore}; @@ -58,15 +57,35 @@ impl From for Error { /// Information about a specific note to be spent in an [`Action`]. #[derive(Debug)] -struct SpendInfo { - dummy_sk: Option, - fvk: FullViewingKey, - scope: Scope, - note: Note, - merkle_path: MerklePath, +pub struct SpendInfo { + pub(crate) dummy_sk: Option, + pub(crate) fvk: FullViewingKey, + pub(crate) scope: Scope, + pub(crate) note: Note, + pub(crate) merkle_path: MerklePath, } impl SpendInfo { + /// This constructor is public to enable creation of custom builders. + /// If you are not creating a custom builder, use [`Builder::add_spend`] instead. + /// + /// Creates a `SpendInfo` from note, full viewing key owning the note, + /// and merkle path witness of the note. + /// + /// Returns `None` if the `fvk` does not own the `note`. + /// + /// [`Builder::add_spend`]: Builder::add_spend + pub fn new(fvk: FullViewingKey, note: Note, merkle_path: MerklePath) -> Option { + let scope = fvk.scope_for_address(¬e.recipient())?; + Some(SpendInfo { + dummy_sk: None, + fvk, + scope, + note, + merkle_path, + }) + } + /// Defined in [Zcash Protocol Spec ยง 4.8.3: Dummy Notes (Orchard)][orcharddummynotes]. /// /// [orcharddummynotes]: https://zips.z.cash/protocol/nu5.pdf#orcharddummynotes @@ -144,10 +163,6 @@ impl ActionInfo { let cv_net = ValueCommitment::derive(v_net, self.rcv.clone()); let nf_old = self.spend.note.nullifier(&self.spend.fvk); - let sender_address = self.spend.note.recipient(); - let rho_old = self.spend.note.rho(); - let psi_old = self.spend.note.rseed().psi(&rho_old); - let rcm_old = self.spend.note.rseed().rcm(&rho_old); let ak: SpendValidatingKey = self.spend.fvk.clone().into(); let alpha = pallas::Scalar::random(&mut rng); let rk = ak.randomize(&alpha); @@ -182,33 +197,10 @@ impl ActionInfo { cv_net, SigningMetadata { dummy_ask: self.spend.dummy_sk.as_ref().map(SpendAuthorizingKey::from), - parts: SigningParts { - ak: ak.clone(), - alpha, - }, + parts: SigningParts { ak, alpha }, }, ), - Circuit { - path: Value::known(self.spend.merkle_path.auth_path()), - pos: Value::known(self.spend.merkle_path.position()), - g_d_old: Value::known(sender_address.g_d()), - pk_d_old: Value::known(*sender_address.pk_d()), - v_old: Value::known(self.spend.note.value()), - rho_old: Value::known(rho_old), - psi_old: Value::known(psi_old), - rcm_old: Value::known(rcm_old), - cm_old: Value::known(self.spend.note.commitment()), - alpha: Value::known(alpha), - ak: Value::known(ak), - nk: Value::known(*self.spend.fvk.nk()), - rivk: Value::known(self.spend.fvk.rivk(self.spend.scope)), - g_d_new: Value::known(note.recipient().g_d()), - pk_d_new: Value::known(*note.recipient().pk_d()), - v_new: Value::known(note.value()), - psi_new: Value::known(note.rseed().psi(¬e.rho())), - rcm_new: Value::known(note.rseed().rcm(¬e.rho())), - rcv: Value::known(self.rcv), - }, + Circuit::from_action_context_unchecked(self.spend, note, alpha, self.rcv), ) } } diff --git a/src/circuit.rs b/src/circuit.rs index 0cb7dae7..d349180e 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -25,6 +25,7 @@ use self::{ note_commit::{NoteCommitChip, NoteCommitConfig}, }; use crate::{ + builder::SpendInfo, constants::{ OrchardCommitDomains, OrchardFixedBases, OrchardFixedBasesFull, OrchardHashDomains, MERKLE_DEPTH_ORCHARD, @@ -35,7 +36,7 @@ use crate::{ note::{ commitment::{NoteCommitTrapdoor, NoteCommitment}, nullifier::Nullifier, - ExtractedNoteCommitment, + ExtractedNoteCommitment, Note, }, primitives::redpallas::{SpendAuth, VerificationKey}, spec::NonIdentityPallasPoint, @@ -120,6 +121,71 @@ pub struct Circuit { pub(crate) rcv: Value, } +impl Circuit { + /// This constructor is public to enable creation of custom builders. + /// If you are not creating a custom builder, use [`Builder`] to compose + /// and authorize a transaction. + /// + /// Constructs a `Circuit` from the following components: + /// - `spend`: [`SpendInfo`] of the note spent in scope of the action + /// - `output_note`: a note created in scope of the action + /// - `alpha`: a scalar used for randomization of the action spend validating key + /// - `rcv`: trapdoor for the action value commitment + /// + /// Returns `None` if the `rho` of the `output_note` is not equal + /// to the nullifier of the spent note. + /// + /// [`SpendInfo`]: crate::builder::SpendInfo + /// [`Builder`]: crate::builder::Builder + pub fn from_action_context( + spend: SpendInfo, + output_note: Note, + alpha: pallas::Scalar, + rcv: ValueCommitTrapdoor, + ) -> Option { + (spend.note.nullifier(&spend.fvk) == output_note.rho()) + .then(|| Self::from_action_context_unchecked(spend, output_note, alpha, rcv)) + } + + pub(crate) fn from_action_context_unchecked( + spend: SpendInfo, + output_note: Note, + alpha: pallas::Scalar, + rcv: ValueCommitTrapdoor, + ) -> Circuit { + let sender_address = spend.note.recipient(); + let rho_old = spend.note.rho(); + let psi_old = spend.note.rseed().psi(&rho_old); + let rcm_old = spend.note.rseed().rcm(&rho_old); + + let rho_new = output_note.rho(); + let psi_new = output_note.rseed().psi(&rho_new); + let rcm_new = output_note.rseed().rcm(&rho_new); + + Circuit { + path: Value::known(spend.merkle_path.auth_path()), + pos: Value::known(spend.merkle_path.position()), + g_d_old: Value::known(sender_address.g_d()), + pk_d_old: Value::known(*sender_address.pk_d()), + v_old: Value::known(spend.note.value()), + rho_old: Value::known(rho_old), + psi_old: Value::known(psi_old), + rcm_old: Value::known(rcm_old), + cm_old: Value::known(spend.note.commitment()), + alpha: Value::known(alpha), + ak: Value::known(spend.fvk.clone().into()), + nk: Value::known(*spend.fvk.nk()), + rivk: Value::known(spend.fvk.rivk(spend.scope)), + g_d_new: Value::known(output_note.recipient().g_d()), + pk_d_new: Value::known(*output_note.recipient().pk_d()), + v_new: Value::known(output_note.value()), + psi_new: Value::known(psi_new), + rcm_new: Value::known(rcm_new), + rcv: Value::known(rcv), + } + } +} + impl plonk::Circuit for Circuit { type Config = Config; type FloorPlanner = floor_planner::V1;