Skip to main content

orchard/
circuit.rs

1//! The Orchard Action circuit implementation.
2
3use alloc::vec::Vec;
4
5use group::{Curve, GroupEncoding};
6use halo2_proofs::{
7    circuit::{floor_planner, Layouter, Value},
8    plonk::{
9        self, Advice, BatchVerifier, Column, Constraints, Expression, Instance as InstanceColumn,
10        Selector, SingleVerifier,
11    },
12    poly::Rotation,
13    transcript::{Blake2bRead, Blake2bWrite},
14};
15use pasta_curves::{arithmetic::CurveAffine, pallas, vesta};
16use rand::RngCore;
17
18use self::{
19    commit_ivk::{CommitIvkChip, CommitIvkConfig},
20    gadget::{
21        add_chip::{AddChip, AddConfig},
22        assign_free_advice,
23    },
24    note_commit::{NoteCommitChip, NoteCommitConfig},
25};
26use crate::{
27    builder::SpendInfo,
28    constants::{
29        OrchardCommitDomains, OrchardFixedBases, OrchardFixedBasesFull, OrchardHashDomains,
30        MERKLE_DEPTH_ORCHARD,
31    },
32    keys::{
33        CommitIvkRandomness, DiversifiedTransmissionKey, NullifierDerivingKey, SpendValidatingKey,
34    },
35    note::{
36        commitment::{NoteCommitTrapdoor, NoteCommitment},
37        nullifier::Nullifier,
38        ExtractedNoteCommitment, Note, Rho,
39    },
40    primitives::redpallas::{SpendAuth, VerificationKey},
41    spec::NonIdentityPallasPoint,
42    tree::{Anchor, MerkleHashOrchard},
43    value::{NoteValue, ValueCommitTrapdoor, ValueCommitment},
44};
45use halo2_gadgets::{
46    ecc::{
47        chip::{EccChip, EccConfig},
48        FixedPoint, NonIdentityPoint, Point, ScalarFixed, ScalarFixedShort, ScalarVar,
49    },
50    poseidon::{primitives as poseidon, Pow5Chip as PoseidonChip, Pow5Config as PoseidonConfig},
51    sinsemilla::{
52        chip::{SinsemillaChip, SinsemillaConfig},
53        merkle::{
54            chip::{MerkleChip, MerkleConfig},
55            MerklePath,
56        },
57    },
58    utilities::lookup_range_check::{LookupRangeCheck, LookupRangeCheckConfig},
59};
60
61#[allow(missing_docs)]
62pub mod commit_ivk;
63#[allow(missing_docs)]
64pub mod gadget;
65#[allow(missing_docs)]
66pub mod note_commit;
67
68pub use crate::Proof;
69
70/// Size of the Orchard circuit.
71const K: u32 = 11;
72
73// Absolute offsets for public inputs.
74const ANCHOR: usize = 0;
75const CV_NET_X: usize = 1;
76const CV_NET_Y: usize = 2;
77const NF_OLD: usize = 3;
78const RK_X: usize = 4;
79const RK_Y: usize = 5;
80const CMX: usize = 6;
81const ENABLE_SPEND: usize = 7;
82const ENABLE_OUTPUT: usize = 8;
83
84/// Configuration needed to use the Orchard Action circuit.
85#[derive(Clone, Debug)]
86pub struct Config {
87    primary: Column<InstanceColumn>,
88    q_orchard: Selector,
89    advices: [Column<Advice>; 10],
90    add_config: AddConfig,
91    ecc_config: EccConfig<OrchardFixedBases>,
92    poseidon_config: PoseidonConfig<pallas::Base, 3, 2>,
93    merkle_config_1: MerkleConfig<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>,
94    merkle_config_2: MerkleConfig<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>,
95    sinsemilla_config_1:
96        SinsemillaConfig<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>,
97    sinsemilla_config_2:
98        SinsemillaConfig<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>,
99    commit_ivk_config: CommitIvkConfig,
100    old_note_commit_config: NoteCommitConfig,
101    new_note_commit_config: NoteCommitConfig,
102}
103
104/// The Orchard Action circuit.
105#[derive(Clone, Debug, Default)]
106pub struct Circuit {
107    pub(crate) path: Value<[MerkleHashOrchard; MERKLE_DEPTH_ORCHARD]>,
108    pub(crate) pos: Value<u32>,
109    pub(crate) g_d_old: Value<NonIdentityPallasPoint>,
110    pub(crate) pk_d_old: Value<DiversifiedTransmissionKey>,
111    pub(crate) v_old: Value<NoteValue>,
112    pub(crate) rho_old: Value<Rho>,
113    pub(crate) psi_old: Value<pallas::Base>,
114    pub(crate) rcm_old: Value<NoteCommitTrapdoor>,
115    pub(crate) cm_old: Value<NoteCommitment>,
116    pub(crate) alpha: Value<pallas::Scalar>,
117    pub(crate) ak: Value<SpendValidatingKey>,
118    pub(crate) nk: Value<NullifierDerivingKey>,
119    pub(crate) rivk: Value<CommitIvkRandomness>,
120    pub(crate) g_d_new: Value<NonIdentityPallasPoint>,
121    pub(crate) pk_d_new: Value<DiversifiedTransmissionKey>,
122    pub(crate) v_new: Value<NoteValue>,
123    pub(crate) psi_new: Value<pallas::Base>,
124    pub(crate) rcm_new: Value<NoteCommitTrapdoor>,
125    pub(crate) rcv: Value<ValueCommitTrapdoor>,
126}
127
128impl Circuit {
129    /// This constructor is public to enable creation of custom builders.
130    /// If you are not creating a custom builder, use [`Builder`] to compose
131    /// and authorize a transaction.
132    ///
133    /// Constructs a `Circuit` from the following components:
134    /// - `spend`: [`SpendInfo`] of the note spent in scope of the action
135    /// - `output_note`: a note created in scope of the action
136    /// - `alpha`: a scalar used for randomization of the action spend validating key
137    /// - `rcv`: trapdoor for the action value commitment
138    ///
139    /// Returns `None` if the `rho` of the `output_note` is not equal
140    /// to the nullifier of the spent note.
141    ///
142    /// [`SpendInfo`]: crate::builder::SpendInfo
143    /// [`Builder`]: crate::builder::Builder
144    pub fn from_action_context(
145        spend: SpendInfo,
146        output_note: Note,
147        alpha: pallas::Scalar,
148        rcv: ValueCommitTrapdoor,
149    ) -> Option<Circuit> {
150        (Rho::from_nf_old(spend.note.nullifier(&spend.fvk)) == output_note.rho())
151            .then(|| Self::from_action_context_unchecked(spend, output_note, alpha, rcv))
152    }
153
154    pub(crate) fn from_action_context_unchecked(
155        spend: SpendInfo,
156        output_note: Note,
157        alpha: pallas::Scalar,
158        rcv: ValueCommitTrapdoor,
159    ) -> Circuit {
160        let sender_address = spend.note.recipient();
161        let rho_old = spend.note.rho();
162        let psi_old = spend.note.rseed().psi(&rho_old);
163        let rcm_old = spend.note.rseed().rcm(&rho_old);
164
165        let rho_new = output_note.rho();
166        let psi_new = output_note.rseed().psi(&rho_new);
167        let rcm_new = output_note.rseed().rcm(&rho_new);
168
169        Circuit {
170            path: Value::known(spend.merkle_path.auth_path()),
171            pos: Value::known(spend.merkle_path.position()),
172            g_d_old: Value::known(sender_address.g_d()),
173            pk_d_old: Value::known(*sender_address.pk_d()),
174            v_old: Value::known(spend.note.value()),
175            rho_old: Value::known(rho_old),
176            psi_old: Value::known(psi_old),
177            rcm_old: Value::known(rcm_old),
178            cm_old: Value::known(spend.note.commitment()),
179            alpha: Value::known(alpha),
180            ak: Value::known(spend.fvk.clone().into()),
181            nk: Value::known(*spend.fvk.nk()),
182            rivk: Value::known(spend.fvk.rivk(spend.scope)),
183            g_d_new: Value::known(output_note.recipient().g_d()),
184            pk_d_new: Value::known(*output_note.recipient().pk_d()),
185            v_new: Value::known(output_note.value()),
186            psi_new: Value::known(psi_new),
187            rcm_new: Value::known(rcm_new),
188            rcv: Value::known(rcv),
189        }
190    }
191}
192
193impl plonk::Circuit<pallas::Base> for Circuit {
194    type Config = Config;
195    type FloorPlanner = floor_planner::V1;
196
197    fn without_witnesses(&self) -> Self {
198        Self::default()
199    }
200
201    fn configure(meta: &mut plonk::ConstraintSystem<pallas::Base>) -> Self::Config {
202        // Advice columns used in the Orchard circuit.
203        let advices = [
204            meta.advice_column(),
205            meta.advice_column(),
206            meta.advice_column(),
207            meta.advice_column(),
208            meta.advice_column(),
209            meta.advice_column(),
210            meta.advice_column(),
211            meta.advice_column(),
212            meta.advice_column(),
213            meta.advice_column(),
214        ];
215
216        // Constrain v_old - v_new = magnitude * sign    (https://p.z.cash/ZKS:action-cv-net-integrity?partial).
217        // Either v_old = 0, or calculated root = anchor (https://p.z.cash/ZKS:action-merkle-path-validity?partial).
218        // Constrain v_old = 0 or enable_spends = 1      (https://p.z.cash/ZKS:action-enable-spend).
219        // Constrain v_new = 0 or enable_outputs = 1     (https://p.z.cash/ZKS:action-enable-output).
220        let q_orchard = meta.selector();
221        meta.create_gate("Orchard circuit checks", |meta| {
222            let q_orchard = meta.query_selector(q_orchard);
223            let v_old = meta.query_advice(advices[0], Rotation::cur());
224            let v_new = meta.query_advice(advices[1], Rotation::cur());
225            let magnitude = meta.query_advice(advices[2], Rotation::cur());
226            let sign = meta.query_advice(advices[3], Rotation::cur());
227
228            let root = meta.query_advice(advices[4], Rotation::cur());
229            let anchor = meta.query_advice(advices[5], Rotation::cur());
230
231            let enable_spends = meta.query_advice(advices[6], Rotation::cur());
232            let enable_outputs = meta.query_advice(advices[7], Rotation::cur());
233
234            let one = Expression::Constant(pallas::Base::one());
235
236            Constraints::with_selector(
237                q_orchard,
238                [
239                    (
240                        "v_old - v_new = magnitude * sign",
241                        v_old.clone() - v_new.clone() - magnitude * sign,
242                    ),
243                    (
244                        "Either v_old = 0, or root = anchor",
245                        v_old.clone() * (root - anchor),
246                    ),
247                    (
248                        "v_old = 0 or enable_spends = 1",
249                        v_old * (one.clone() - enable_spends),
250                    ),
251                    (
252                        "v_new = 0 or enable_outputs = 1",
253                        v_new * (one - enable_outputs),
254                    ),
255                ],
256            )
257        });
258
259        // Addition of two field elements.
260        let add_config = AddChip::configure(meta, advices[7], advices[8], advices[6]);
261
262        // Fixed columns for the Sinsemilla generator lookup table
263        let table_idx = meta.lookup_table_column();
264        let lookup = (
265            table_idx,
266            meta.lookup_table_column(),
267            meta.lookup_table_column(),
268        );
269
270        // Instance column used for public inputs
271        let primary = meta.instance_column();
272        meta.enable_equality(primary);
273
274        // Permutation over all advice columns.
275        for advice in advices.iter() {
276            meta.enable_equality(*advice);
277        }
278
279        // Poseidon requires four advice columns, while ECC incomplete addition requires
280        // six, so we could choose to configure them in parallel. However, we only use a
281        // single Poseidon invocation, and we have the rows to accommodate it serially.
282        // Instead, we reduce the proof size by sharing fixed columns between the ECC and
283        // Poseidon chips.
284        let lagrange_coeffs = [
285            meta.fixed_column(),
286            meta.fixed_column(),
287            meta.fixed_column(),
288            meta.fixed_column(),
289            meta.fixed_column(),
290            meta.fixed_column(),
291            meta.fixed_column(),
292            meta.fixed_column(),
293        ];
294        let rc_a = lagrange_coeffs[2..5].try_into().unwrap();
295        let rc_b = lagrange_coeffs[5..8].try_into().unwrap();
296
297        // Also use the first Lagrange coefficient column for loading global constants.
298        // It's free real estate :)
299        meta.enable_constant(lagrange_coeffs[0]);
300
301        // We have a lot of free space in the right-most advice columns; use one of them
302        // for all of our range checks.
303        let range_check = LookupRangeCheckConfig::configure(meta, advices[9], table_idx);
304
305        // Configuration for curve point operations.
306        // This uses 10 advice columns and spans the whole circuit.
307        let ecc_config =
308            EccChip::<OrchardFixedBases>::configure(meta, advices, lagrange_coeffs, range_check);
309
310        // Configuration for the Poseidon hash.
311        let poseidon_config = PoseidonChip::configure::<poseidon::P128Pow5T3>(
312            meta,
313            // We place the state columns after the partial_sbox column so that the
314            // pad-and-add region can be laid out more efficiently.
315            advices[6..9].try_into().unwrap(),
316            advices[5],
317            rc_a,
318            rc_b,
319        );
320
321        // Configuration for a Sinsemilla hash instantiation and a
322        // Merkle hash instantiation using this Sinsemilla instance.
323        // Since the Sinsemilla config uses only 5 advice columns,
324        // we can fit two instances side-by-side.
325        let (sinsemilla_config_1, merkle_config_1) = {
326            let sinsemilla_config_1 = SinsemillaChip::configure(
327                meta,
328                advices[..5].try_into().unwrap(),
329                advices[6],
330                lagrange_coeffs[0],
331                lookup,
332                range_check,
333                false,
334            );
335            let merkle_config_1 = MerkleChip::configure(meta, sinsemilla_config_1.clone());
336
337            (sinsemilla_config_1, merkle_config_1)
338        };
339
340        // Configuration for a Sinsemilla hash instantiation and a
341        // Merkle hash instantiation using this Sinsemilla instance.
342        // Since the Sinsemilla config uses only 5 advice columns,
343        // we can fit two instances side-by-side.
344        let (sinsemilla_config_2, merkle_config_2) = {
345            let sinsemilla_config_2 = SinsemillaChip::configure(
346                meta,
347                advices[5..].try_into().unwrap(),
348                advices[7],
349                lagrange_coeffs[1],
350                lookup,
351                range_check,
352                false,
353            );
354            let merkle_config_2 = MerkleChip::configure(meta, sinsemilla_config_2.clone());
355
356            (sinsemilla_config_2, merkle_config_2)
357        };
358
359        // Configuration to handle decomposition and canonicity checking
360        // for CommitIvk.
361        let commit_ivk_config = CommitIvkChip::configure(meta, advices);
362
363        // Configuration to handle decomposition and canonicity checking
364        // for NoteCommit_old.
365        let old_note_commit_config =
366            NoteCommitChip::configure(meta, advices, sinsemilla_config_1.clone());
367
368        // Configuration to handle decomposition and canonicity checking
369        // for NoteCommit_new.
370        let new_note_commit_config =
371            NoteCommitChip::configure(meta, advices, sinsemilla_config_2.clone());
372
373        Config {
374            primary,
375            q_orchard,
376            advices,
377            add_config,
378            ecc_config,
379            poseidon_config,
380            merkle_config_1,
381            merkle_config_2,
382            sinsemilla_config_1,
383            sinsemilla_config_2,
384            commit_ivk_config,
385            old_note_commit_config,
386            new_note_commit_config,
387        }
388    }
389
390    #[allow(non_snake_case)]
391    fn synthesize(
392        &self,
393        config: Self::Config,
394        mut layouter: impl Layouter<pallas::Base>,
395    ) -> Result<(), plonk::Error> {
396        // Load the Sinsemilla generator lookup table used by the whole circuit.
397        SinsemillaChip::load(config.sinsemilla_config_1.clone(), &mut layouter)?;
398
399        // Construct the ECC chip.
400        let ecc_chip = config.ecc_chip();
401
402        // Witness private inputs that are used across multiple checks.
403        let (psi_old, rho_old, cm_old, g_d_old, ak_P, nk, v_old, v_new) = {
404            // Witness psi_old
405            let psi_old = assign_free_advice(
406                layouter.namespace(|| "witness psi_old"),
407                config.advices[0],
408                self.psi_old,
409            )?;
410
411            // Witness rho_old
412            let rho_old = assign_free_advice(
413                layouter.namespace(|| "witness rho_old"),
414                config.advices[0],
415                self.rho_old.map(|rho| rho.into_inner()),
416            )?;
417
418            // Witness cm_old
419            let cm_old = Point::new(
420                ecc_chip.clone(),
421                layouter.namespace(|| "cm_old"),
422                self.cm_old.as_ref().map(|cm| cm.inner().to_affine()),
423            )?;
424
425            // Witness g_d_old
426            let g_d_old = NonIdentityPoint::new(
427                ecc_chip.clone(),
428                layouter.namespace(|| "gd_old"),
429                self.g_d_old.as_ref().map(|gd| gd.to_affine()),
430            )?;
431
432            // Witness ak_P.
433            let ak_P: Value<pallas::Point> = self.ak.as_ref().map(|ak| ak.into());
434            let ak_P = NonIdentityPoint::new(
435                ecc_chip.clone(),
436                layouter.namespace(|| "witness ak_P"),
437                ak_P.map(|ak_P| ak_P.to_affine()),
438            )?;
439
440            // Witness nk.
441            let nk = assign_free_advice(
442                layouter.namespace(|| "witness nk"),
443                config.advices[0],
444                self.nk.map(|nk| nk.inner()),
445            )?;
446
447            // Witness v_old.
448            let v_old = assign_free_advice(
449                layouter.namespace(|| "witness v_old"),
450                config.advices[0],
451                self.v_old,
452            )?;
453
454            // Witness v_new.
455            let v_new = assign_free_advice(
456                layouter.namespace(|| "witness v_new"),
457                config.advices[0],
458                self.v_new,
459            )?;
460
461            (psi_old, rho_old, cm_old, g_d_old, ak_P, nk, v_old, v_new)
462        };
463
464        // Merkle path validity check (https://p.z.cash/ZKS:action-merkle-path-validity?partial).
465        let root = {
466            let path = self
467                .path
468                .map(|typed_path| typed_path.map(|node| node.inner()));
469            let merkle_inputs = MerklePath::construct(
470                [config.merkle_chip_1(), config.merkle_chip_2()],
471                OrchardHashDomains::MerkleCrh,
472                self.pos,
473                path,
474            );
475            let leaf = cm_old.extract_p().inner().clone();
476            merkle_inputs.calculate_root(layouter.namespace(|| "Merkle path"), leaf)?
477        };
478
479        // Value commitment integrity (https://p.z.cash/ZKS:action-cv-net-integrity?partial).
480        let v_net_magnitude_sign = {
481            // Witness the magnitude and sign of v_net = v_old - v_new
482            let v_net_magnitude_sign = {
483                let v_net = self.v_old - self.v_new;
484                let magnitude_sign = v_net.map(|v_net| {
485                    let (magnitude, sign) = v_net.magnitude_sign();
486
487                    (
488                        // magnitude is guaranteed to be an unsigned 64-bit value.
489                        // Therefore, we can move it into the base field.
490                        pallas::Base::from(magnitude),
491                        match sign {
492                            crate::value::Sign::Positive => pallas::Base::one(),
493                            crate::value::Sign::Negative => -pallas::Base::one(),
494                        },
495                    )
496                });
497
498                let magnitude = assign_free_advice(
499                    layouter.namespace(|| "v_net magnitude"),
500                    config.advices[9],
501                    magnitude_sign.map(|m_s| m_s.0),
502                )?;
503                let sign = assign_free_advice(
504                    layouter.namespace(|| "v_net sign"),
505                    config.advices[9],
506                    magnitude_sign.map(|m_s| m_s.1),
507                )?;
508                (magnitude, sign)
509            };
510
511            let v_net = ScalarFixedShort::new(
512                ecc_chip.clone(),
513                layouter.namespace(|| "v_net"),
514                v_net_magnitude_sign.clone(),
515            )?;
516            let rcv = ScalarFixed::new(
517                ecc_chip.clone(),
518                layouter.namespace(|| "rcv"),
519                self.rcv.as_ref().map(|rcv| rcv.inner()),
520            )?;
521
522            let cv_net = gadget::value_commit_orchard(
523                layouter.namespace(|| "cv_net = ValueCommit^Orchard_rcv(v_net)"),
524                ecc_chip.clone(),
525                v_net,
526                rcv,
527            )?;
528
529            // Constrain cv_net to equal public input
530            layouter.constrain_instance(cv_net.inner().x().cell(), config.primary, CV_NET_X)?;
531            layouter.constrain_instance(cv_net.inner().y().cell(), config.primary, CV_NET_Y)?;
532
533            // Return the magnitude and sign so we can use them in the Orchard gate.
534            v_net_magnitude_sign
535        };
536
537        // Nullifier integrity (https://p.z.cash/ZKS:action-nullifier-integrity).
538        let nf_old = {
539            let nf_old = gadget::derive_nullifier(
540                layouter.namespace(|| "nf_old = DeriveNullifier_nk(rho_old, psi_old, cm_old)"),
541                config.poseidon_chip(),
542                config.add_chip(),
543                ecc_chip.clone(),
544                rho_old.clone(),
545                &psi_old,
546                &cm_old,
547                nk.clone(),
548            )?;
549
550            // Constrain nf_old to equal public input
551            layouter.constrain_instance(nf_old.inner().cell(), config.primary, NF_OLD)?;
552
553            nf_old
554        };
555
556        // Spend authority (https://p.z.cash/ZKS:action-spend-authority)
557        {
558            let alpha =
559                ScalarFixed::new(ecc_chip.clone(), layouter.namespace(|| "alpha"), self.alpha)?;
560
561            // alpha_commitment = [alpha] SpendAuthG
562            let (alpha_commitment, _) = {
563                let spend_auth_g = OrchardFixedBasesFull::SpendAuthG;
564                let spend_auth_g = FixedPoint::from_inner(ecc_chip.clone(), spend_auth_g);
565                spend_auth_g.mul(layouter.namespace(|| "[alpha] SpendAuthG"), alpha)?
566            };
567
568            // [alpha] SpendAuthG + ak_P
569            let rk = alpha_commitment.add(layouter.namespace(|| "rk"), &ak_P)?;
570
571            // Constrain rk to equal public input
572            layouter.constrain_instance(rk.inner().x().cell(), config.primary, RK_X)?;
573            layouter.constrain_instance(rk.inner().y().cell(), config.primary, RK_Y)?;
574        }
575
576        // Diversified address integrity (https://p.z.cash/ZKS:action-addr-integrity?partial).
577        let pk_d_old = {
578            let ivk = {
579                let ak = ak_P.extract_p().inner().clone();
580                let rivk = ScalarFixed::new(
581                    ecc_chip.clone(),
582                    layouter.namespace(|| "rivk"),
583                    self.rivk.map(|rivk| rivk.inner()),
584                )?;
585
586                gadget::commit_ivk(
587                    config.sinsemilla_chip_1(),
588                    ecc_chip.clone(),
589                    config.commit_ivk_chip(),
590                    layouter.namespace(|| "CommitIvk"),
591                    ak,
592                    nk,
593                    rivk,
594                )?
595            };
596            let ivk =
597                ScalarVar::from_base(ecc_chip.clone(), layouter.namespace(|| "ivk"), ivk.inner())?;
598
599            // [ivk] g_d_old
600            // The scalar value is passed through and discarded.
601            let (derived_pk_d_old, _ivk) =
602                g_d_old.mul(layouter.namespace(|| "[ivk] g_d_old"), ivk)?;
603
604            // Constrain derived pk_d_old to equal witnessed pk_d_old
605            //
606            // This equality constraint is technically superfluous, because the assigned
607            // value of `derived_pk_d_old` is an equivalent witness. But it's nice to see
608            // an explicit connection between circuit-synthesized values, and explicit
609            // prover witnesses. We could get the best of both worlds with a write-on-copy
610            // abstraction (https://github.com/zcash/halo2/issues/334).
611            let pk_d_old = NonIdentityPoint::new(
612                ecc_chip.clone(),
613                layouter.namespace(|| "witness pk_d_old"),
614                self.pk_d_old.map(|pk_d_old| pk_d_old.inner().to_affine()),
615            )?;
616            derived_pk_d_old
617                .constrain_equal(layouter.namespace(|| "pk_d_old equality"), &pk_d_old)?;
618
619            pk_d_old
620        };
621
622        // Old note commitment integrity (https://p.z.cash/ZKS:action-cm-old-integrity?partial).
623        {
624            let rcm_old = ScalarFixed::new(
625                ecc_chip.clone(),
626                layouter.namespace(|| "rcm_old"),
627                self.rcm_old.as_ref().map(|rcm_old| rcm_old.inner()),
628            )?;
629
630            // g★_d || pk★_d || i2lebsp_{64}(v) || i2lebsp_{255}(rho) || i2lebsp_{255}(psi)
631            let derived_cm_old = gadget::note_commit(
632                layouter.namespace(|| {
633                    "g★_d || pk★_d || i2lebsp_{64}(v) || i2lebsp_{255}(rho) || i2lebsp_{255}(psi)"
634                }),
635                config.sinsemilla_chip_1(),
636                config.ecc_chip(),
637                config.note_commit_chip_old(),
638                g_d_old.inner(),
639                pk_d_old.inner(),
640                v_old.clone(),
641                rho_old,
642                psi_old,
643                rcm_old,
644            )?;
645
646            // Constrain derived cm_old to equal witnessed cm_old
647            derived_cm_old.constrain_equal(layouter.namespace(|| "cm_old equality"), &cm_old)?;
648        }
649
650        // New note commitment integrity (https://p.z.cash/ZKS:action-cmx-new-integrity?partial).
651        {
652            // Witness g_d_new
653            let g_d_new = {
654                let g_d_new = self.g_d_new.map(|g_d_new| g_d_new.to_affine());
655                NonIdentityPoint::new(
656                    ecc_chip.clone(),
657                    layouter.namespace(|| "witness g_d_new_star"),
658                    g_d_new,
659                )?
660            };
661
662            // Witness pk_d_new
663            let pk_d_new = {
664                let pk_d_new = self.pk_d_new.map(|pk_d_new| pk_d_new.inner().to_affine());
665                NonIdentityPoint::new(
666                    ecc_chip.clone(),
667                    layouter.namespace(|| "witness pk_d_new"),
668                    pk_d_new,
669                )?
670            };
671
672            // ρ^new = nf^old
673            let rho_new = nf_old.inner().clone();
674
675            // Witness psi_new
676            let psi_new = assign_free_advice(
677                layouter.namespace(|| "witness psi_new"),
678                config.advices[0],
679                self.psi_new,
680            )?;
681
682            let rcm_new = ScalarFixed::new(
683                ecc_chip,
684                layouter.namespace(|| "rcm_new"),
685                self.rcm_new.as_ref().map(|rcm_new| rcm_new.inner()),
686            )?;
687
688            // g★_d || pk★_d || i2lebsp_{64}(v) || i2lebsp_{255}(rho) || i2lebsp_{255}(psi)
689            let cm_new = gadget::note_commit(
690                layouter.namespace(|| {
691                    "g★_d || pk★_d || i2lebsp_{64}(v) || i2lebsp_{255}(rho) || i2lebsp_{255}(psi)"
692                }),
693                config.sinsemilla_chip_2(),
694                config.ecc_chip(),
695                config.note_commit_chip_new(),
696                g_d_new.inner(),
697                pk_d_new.inner(),
698                v_new.clone(),
699                rho_new,
700                psi_new,
701                rcm_new,
702            )?;
703
704            let cmx = cm_new.extract_p();
705
706            // Constrain cmx to equal public input
707            layouter.constrain_instance(cmx.inner().cell(), config.primary, CMX)?;
708        }
709
710        // Constrain the remaining Orchard circuit checks.
711        layouter.assign_region(
712            || "Orchard circuit checks",
713            |mut region| {
714                v_old.copy_advice(|| "v_old", &mut region, config.advices[0], 0)?;
715                v_new.copy_advice(|| "v_new", &mut region, config.advices[1], 0)?;
716                v_net_magnitude_sign.0.copy_advice(
717                    || "v_net magnitude",
718                    &mut region,
719                    config.advices[2],
720                    0,
721                )?;
722                v_net_magnitude_sign.1.copy_advice(
723                    || "v_net sign",
724                    &mut region,
725                    config.advices[3],
726                    0,
727                )?;
728
729                root.copy_advice(|| "calculated root", &mut region, config.advices[4], 0)?;
730                region.assign_advice_from_instance(
731                    || "pub input anchor",
732                    config.primary,
733                    ANCHOR,
734                    config.advices[5],
735                    0,
736                )?;
737
738                region.assign_advice_from_instance(
739                    || "enable spends",
740                    config.primary,
741                    ENABLE_SPEND,
742                    config.advices[6],
743                    0,
744                )?;
745
746                region.assign_advice_from_instance(
747                    || "enable outputs",
748                    config.primary,
749                    ENABLE_OUTPUT,
750                    config.advices[7],
751                    0,
752                )?;
753
754                config.q_orchard.enable(&mut region, 0)
755            },
756        )?;
757
758        Ok(())
759    }
760}
761
762/// The verifying key for the Orchard Action circuit.
763#[derive(Debug)]
764pub struct VerifyingKey {
765    pub(crate) params: halo2_proofs::poly::commitment::Params<vesta::Affine>,
766    pub(crate) vk: plonk::VerifyingKey<vesta::Affine>,
767}
768
769impl VerifyingKey {
770    /// Builds the verifying key.
771    pub fn build() -> Self {
772        let params = halo2_proofs::poly::commitment::Params::new(K);
773        let circuit: Circuit = Default::default();
774
775        let vk = plonk::keygen_vk(&params, &circuit).unwrap();
776
777        VerifyingKey { params, vk }
778    }
779}
780
781/// The proving key for the Orchard Action circuit.
782#[derive(Debug)]
783pub struct ProvingKey {
784    params: halo2_proofs::poly::commitment::Params<vesta::Affine>,
785    pk: plonk::ProvingKey<vesta::Affine>,
786}
787
788impl ProvingKey {
789    /// Builds the proving key.
790    pub fn build() -> Self {
791        let params = halo2_proofs::poly::commitment::Params::new(K);
792        let circuit: Circuit = Default::default();
793
794        let vk = plonk::keygen_vk(&params, &circuit).unwrap();
795        let pk = plonk::keygen_pk(&params, vk, &circuit).unwrap();
796
797        ProvingKey { params, pk }
798    }
799}
800
801/// Public inputs to the Orchard Action circuit.
802#[derive(Clone, Debug)]
803pub struct Instance {
804    pub(crate) anchor: Anchor,
805    pub(crate) cv_net: ValueCommitment,
806    pub(crate) nf_old: Nullifier,
807    pub(crate) rk: VerificationKey<SpendAuth>,
808    pub(crate) cmx: ExtractedNoteCommitment,
809    pub(crate) enable_spend: bool,
810    pub(crate) enable_output: bool,
811}
812
813impl Instance {
814    /// Constructs an [`Instance`] from its constituent parts.
815    ///
816    /// This API can be used in combination with [`Proof::verify`] to build verification
817    /// pipelines for many proofs, where you don't want to pass around the full bundle.
818    /// Use [`Bundle::verify_proof`] instead if you have the full bundle.
819    ///
820    /// [`Bundle::verify_proof`]: crate::Bundle::verify_proof
821    pub fn from_parts(
822        anchor: Anchor,
823        cv_net: ValueCommitment,
824        nf_old: Nullifier,
825        rk: VerificationKey<SpendAuth>,
826        cmx: ExtractedNoteCommitment,
827        enable_spend: bool,
828        enable_output: bool,
829    ) -> Self {
830        Instance {
831            anchor,
832            cv_net,
833            nf_old,
834            rk,
835            cmx,
836            enable_spend,
837            enable_output,
838        }
839    }
840
841    fn to_halo2_instance(&self) -> [[vesta::Scalar; 9]; 1] {
842        let mut instance = [vesta::Scalar::zero(); 9];
843
844        instance[ANCHOR] = self.anchor.inner();
845        instance[CV_NET_X] = self.cv_net.x();
846        instance[CV_NET_Y] = self.cv_net.y();
847        instance[NF_OLD] = self.nf_old.0;
848
849        let rk = pallas::Point::from_bytes(&self.rk.clone().into())
850            .unwrap()
851            .to_affine()
852            .coordinates()
853            .unwrap();
854
855        instance[RK_X] = *rk.x();
856        instance[RK_Y] = *rk.y();
857        instance[CMX] = self.cmx.inner();
858        instance[ENABLE_SPEND] = vesta::Scalar::from(u64::from(self.enable_spend));
859        instance[ENABLE_OUTPUT] = vesta::Scalar::from(u64::from(self.enable_output));
860
861        [instance]
862    }
863}
864
865impl Proof {
866    /// Creates a proof for the given circuits and instances.
867    pub fn create(
868        pk: &ProvingKey,
869        circuits: &[Circuit],
870        instances: &[Instance],
871        mut rng: impl RngCore,
872    ) -> Result<Self, plonk::Error> {
873        let instances: Vec<_> = instances.iter().map(|i| i.to_halo2_instance()).collect();
874        let instances: Vec<Vec<_>> = instances
875            .iter()
876            .map(|i| i.iter().map(|c| &c[..]).collect())
877            .collect();
878        let instances: Vec<_> = instances.iter().map(|i| &i[..]).collect();
879
880        let mut transcript = Blake2bWrite::<_, vesta::Affine, _>::init(vec![]);
881        plonk::create_proof(
882            &pk.params,
883            &pk.pk,
884            circuits,
885            &instances,
886            &mut rng,
887            &mut transcript,
888        )?;
889        Ok(Proof(transcript.finalize()))
890    }
891
892    /// Verifies this proof with the given instances.
893    pub fn verify(&self, vk: &VerifyingKey, instances: &[Instance]) -> Result<(), plonk::Error> {
894        let instances: Vec<_> = instances.iter().map(|i| i.to_halo2_instance()).collect();
895        let instances: Vec<Vec<_>> = instances
896            .iter()
897            .map(|i| i.iter().map(|c| &c[..]).collect())
898            .collect();
899        let instances: Vec<_> = instances.iter().map(|i| &i[..]).collect();
900
901        let strategy = SingleVerifier::new(&vk.params);
902        let mut transcript = Blake2bRead::init(&self.0[..]);
903        plonk::verify_proof(&vk.params, &vk.vk, strategy, &instances, &mut transcript)
904    }
905
906    /// Adds this proof to the given batch for verification with the given instances.
907    ///
908    /// Use this API if you want more control over how proof batches are processed. If you
909    /// just want to batch-validate Orchard bundles, use [`bundle::BatchValidator`].
910    ///
911    /// [`bundle::BatchValidator`]: crate::bundle::BatchValidator
912    pub fn add_to_batch(&self, batch: &mut BatchVerifier<vesta::Affine>, instances: Vec<Instance>) {
913        let instances = instances
914            .iter()
915            .map(|i| {
916                i.to_halo2_instance()
917                    .into_iter()
918                    .map(|c| c.into_iter().collect())
919                    .collect()
920            })
921            .collect();
922
923        batch.add_proof(instances, self.0.clone());
924    }
925}
926
927#[cfg(test)]
928mod tests {
929    use alloc::vec::Vec;
930    use core::iter;
931
932    use ff::Field;
933    use halo2_proofs::{circuit::Value, dev::MockProver};
934    use pasta_curves::pallas;
935    use rand::{rngs::OsRng, RngCore};
936
937    use super::{Circuit, Instance, Proof, ProvingKey, VerifyingKey, K};
938    use crate::{
939        keys::SpendValidatingKey,
940        note::{Note, Rho},
941        tree::MerklePath,
942        value::{ValueCommitTrapdoor, ValueCommitment},
943    };
944
945    fn generate_circuit_instance<R: RngCore>(mut rng: R) -> (Circuit, Instance) {
946        let (_, fvk, spent_note) = Note::dummy(&mut rng, None);
947
948        let sender_address = spent_note.recipient();
949        let nk = *fvk.nk();
950        let rivk = fvk.rivk(fvk.scope_for_address(&spent_note.recipient()).unwrap());
951        let nf_old = spent_note.nullifier(&fvk);
952        let rho = Rho::from_nf_old(nf_old);
953        let ak: SpendValidatingKey = fvk.into();
954        let alpha = pallas::Scalar::random(&mut rng);
955        let rk = ak.randomize(&alpha);
956
957        let (_, _, output_note) = Note::dummy(&mut rng, Some(rho));
958        let cmx = output_note.commitment().into();
959
960        let value = spent_note.value() - output_note.value();
961        let rcv = ValueCommitTrapdoor::random(&mut rng);
962        let cv_net = ValueCommitment::derive(value, rcv.clone());
963
964        let path = MerklePath::dummy(&mut rng);
965        let anchor = path.root(spent_note.commitment().into());
966
967        (
968            Circuit {
969                path: Value::known(path.auth_path()),
970                pos: Value::known(path.position()),
971                g_d_old: Value::known(sender_address.g_d()),
972                pk_d_old: Value::known(*sender_address.pk_d()),
973                v_old: Value::known(spent_note.value()),
974                rho_old: Value::known(spent_note.rho()),
975                psi_old: Value::known(spent_note.rseed().psi(&spent_note.rho())),
976                rcm_old: Value::known(spent_note.rseed().rcm(&spent_note.rho())),
977                cm_old: Value::known(spent_note.commitment()),
978                alpha: Value::known(alpha),
979                ak: Value::known(ak),
980                nk: Value::known(nk),
981                rivk: Value::known(rivk),
982                g_d_new: Value::known(output_note.recipient().g_d()),
983                pk_d_new: Value::known(*output_note.recipient().pk_d()),
984                v_new: Value::known(output_note.value()),
985                psi_new: Value::known(output_note.rseed().psi(&output_note.rho())),
986                rcm_new: Value::known(output_note.rseed().rcm(&output_note.rho())),
987                rcv: Value::known(rcv),
988            },
989            Instance {
990                anchor,
991                cv_net,
992                nf_old,
993                rk,
994                cmx,
995                enable_spend: true,
996                enable_output: true,
997            },
998        )
999    }
1000
1001    // TODO: recast as a proptest
1002    #[test]
1003    fn round_trip() {
1004        let mut rng = OsRng;
1005
1006        let (circuits, instances): (Vec<_>, Vec<_>) = iter::once(())
1007            .map(|()| generate_circuit_instance(&mut rng))
1008            .unzip();
1009
1010        let vk = VerifyingKey::build();
1011
1012        // Test that the pinned verification key (representing the circuit)
1013        // is as expected.
1014        {
1015            // panic!("{:#?}", vk.vk.pinned());
1016            assert_eq!(
1017                format!("{:#?}\n", vk.vk.pinned()),
1018                include_str!("circuit_description").replace("\r\n", "\n")
1019            );
1020        }
1021
1022        // Test that the proof size is as expected.
1023        let expected_proof_size = {
1024            let circuit_cost =
1025                halo2_proofs::dev::CircuitCost::<pasta_curves::vesta::Point, _>::measure(
1026                    K,
1027                    &circuits[0],
1028                );
1029            assert_eq!(usize::from(circuit_cost.proof_size(1)), 4992);
1030            assert_eq!(usize::from(circuit_cost.proof_size(2)), 7264);
1031            usize::from(circuit_cost.proof_size(instances.len()))
1032        };
1033
1034        for (circuit, instance) in circuits.iter().zip(instances.iter()) {
1035            assert_eq!(
1036                MockProver::run(
1037                    K,
1038                    circuit,
1039                    instance
1040                        .to_halo2_instance()
1041                        .iter()
1042                        .map(|p| p.to_vec())
1043                        .collect()
1044                )
1045                .unwrap()
1046                .verify(),
1047                Ok(())
1048            );
1049        }
1050
1051        let pk = ProvingKey::build();
1052        let proof = Proof::create(&pk, &circuits, &instances, &mut rng).unwrap();
1053        assert!(proof.verify(&vk, &instances).is_ok());
1054        assert_eq!(proof.0.len(), expected_proof_size);
1055    }
1056
1057    #[test]
1058    fn serialized_proof_test_case() {
1059        use std::io::{Read, Write};
1060
1061        let vk = VerifyingKey::build();
1062
1063        fn write_test_case<W: Write>(
1064            mut w: W,
1065            instance: &Instance,
1066            proof: &Proof,
1067        ) -> std::io::Result<()> {
1068            w.write_all(&instance.anchor.to_bytes())?;
1069            w.write_all(&instance.cv_net.to_bytes())?;
1070            w.write_all(&instance.nf_old.to_bytes())?;
1071            w.write_all(&<[u8; 32]>::from(instance.rk.clone()))?;
1072            w.write_all(&instance.cmx.to_bytes())?;
1073            w.write_all(&[
1074                u8::from(instance.enable_spend),
1075                u8::from(instance.enable_output),
1076            ])?;
1077
1078            w.write_all(proof.as_ref())?;
1079            Ok(())
1080        }
1081
1082        fn read_test_case<R: Read>(mut r: R) -> std::io::Result<(Instance, Proof)> {
1083            let read_32_bytes = |r: &mut R| {
1084                let mut ret = [0u8; 32];
1085                r.read_exact(&mut ret).unwrap();
1086                ret
1087            };
1088            let read_bool = |r: &mut R| {
1089                let mut byte = [0u8; 1];
1090                r.read_exact(&mut byte).unwrap();
1091                match byte {
1092                    [0] => false,
1093                    [1] => true,
1094                    _ => panic!("Unexpected non-boolean byte"),
1095                }
1096            };
1097
1098            let anchor = crate::Anchor::from_bytes(read_32_bytes(&mut r)).unwrap();
1099            let cv_net = ValueCommitment::from_bytes(&read_32_bytes(&mut r)).unwrap();
1100            let nf_old = crate::note::Nullifier::from_bytes(&read_32_bytes(&mut r)).unwrap();
1101            let rk = read_32_bytes(&mut r).try_into().unwrap();
1102            let cmx =
1103                crate::note::ExtractedNoteCommitment::from_bytes(&read_32_bytes(&mut r)).unwrap();
1104            let enable_spend = read_bool(&mut r);
1105            let enable_output = read_bool(&mut r);
1106            let instance =
1107                Instance::from_parts(anchor, cv_net, nf_old, rk, cmx, enable_spend, enable_output);
1108
1109            let mut proof_bytes = vec![];
1110            r.read_to_end(&mut proof_bytes)?;
1111            let proof = Proof::new(proof_bytes);
1112
1113            Ok((instance, proof))
1114        }
1115
1116        if std::env::var_os("ORCHARD_CIRCUIT_TEST_GENERATE_NEW_PROOF").is_some() {
1117            let create_proof = || -> std::io::Result<()> {
1118                let mut rng = OsRng;
1119
1120                let (circuit, instance) = generate_circuit_instance(OsRng);
1121                let instances = core::slice::from_ref(&instance);
1122
1123                let pk = ProvingKey::build();
1124                let proof = Proof::create(&pk, &[circuit], instances, &mut rng).unwrap();
1125                assert!(proof.verify(&vk, instances).is_ok());
1126
1127                let file = std::fs::File::create("circuit_proof_test_case.bin")?;
1128                write_test_case(file, &instance, &proof)
1129            };
1130            create_proof().expect("should be able to write new proof");
1131        }
1132
1133        // Parse the hardcoded proof test case.
1134        let (instance, proof) = {
1135            let test_case_bytes = include_bytes!("circuit_proof_test_case.bin");
1136            read_test_case(&test_case_bytes[..]).expect("proof must be valid")
1137        };
1138        assert_eq!(proof.0.len(), 4992);
1139
1140        assert!(proof.verify(&vk, &[instance]).is_ok());
1141    }
1142
1143    #[cfg(feature = "dev-graph")]
1144    #[test]
1145    fn print_action_circuit() {
1146        use plotters::prelude::*;
1147
1148        let root = BitMapBackend::new("action-circuit-layout.png", (1024, 768)).into_drawing_area();
1149        root.fill(&WHITE).unwrap();
1150        let root = root
1151            .titled("Orchard Action Circuit", ("sans-serif", 60))
1152            .unwrap();
1153
1154        let circuit = Circuit {
1155            path: Value::unknown(),
1156            pos: Value::unknown(),
1157            g_d_old: Value::unknown(),
1158            pk_d_old: Value::unknown(),
1159            v_old: Value::unknown(),
1160            rho_old: Value::unknown(),
1161            psi_old: Value::unknown(),
1162            rcm_old: Value::unknown(),
1163            cm_old: Value::unknown(),
1164            alpha: Value::unknown(),
1165            ak: Value::unknown(),
1166            nk: Value::unknown(),
1167            rivk: Value::unknown(),
1168            g_d_new: Value::unknown(),
1169            pk_d_new: Value::unknown(),
1170            v_new: Value::unknown(),
1171            psi_new: Value::unknown(),
1172            rcm_new: Value::unknown(),
1173            rcv: Value::unknown(),
1174        };
1175        halo2_proofs::dev::CircuitLayout::default()
1176            .show_labels(false)
1177            .view_height(0..(1 << 11))
1178            .render(K, &circuit, &root)
1179            .unwrap();
1180    }
1181}