1use 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
70const K: u32 = 11;
72
73const 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#[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#[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 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 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 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 let add_config = AddChip::configure(meta, advices[7], advices[8], advices[6]);
261
262 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 let primary = meta.instance_column();
272 meta.enable_equality(primary);
273
274 for advice in advices.iter() {
276 meta.enable_equality(*advice);
277 }
278
279 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 meta.enable_constant(lagrange_coeffs[0]);
300
301 let range_check = LookupRangeCheckConfig::configure(meta, advices[9], table_idx);
304
305 let ecc_config =
308 EccChip::<OrchardFixedBases>::configure(meta, advices, lagrange_coeffs, range_check);
309
310 let poseidon_config = PoseidonChip::configure::<poseidon::P128Pow5T3>(
312 meta,
313 advices[6..9].try_into().unwrap(),
316 advices[5],
317 rc_a,
318 rc_b,
319 );
320
321 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 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 let commit_ivk_config = CommitIvkChip::configure(meta, advices);
362
363 let old_note_commit_config =
366 NoteCommitChip::configure(meta, advices, sinsemilla_config_1.clone());
367
368 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 SinsemillaChip::load(config.sinsemilla_config_1.clone(), &mut layouter)?;
398
399 let ecc_chip = config.ecc_chip();
401
402 let (psi_old, rho_old, cm_old, g_d_old, ak_P, nk, v_old, v_new) = {
404 let psi_old = assign_free_advice(
406 layouter.namespace(|| "witness psi_old"),
407 config.advices[0],
408 self.psi_old,
409 )?;
410
411 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 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 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 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 let nk = assign_free_advice(
442 layouter.namespace(|| "witness nk"),
443 config.advices[0],
444 self.nk.map(|nk| nk.inner()),
445 )?;
446
447 let v_old = assign_free_advice(
449 layouter.namespace(|| "witness v_old"),
450 config.advices[0],
451 self.v_old,
452 )?;
453
454 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 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 let v_net_magnitude_sign = {
481 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 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 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 v_net_magnitude_sign
535 };
536
537 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 layouter.constrain_instance(nf_old.inner().cell(), config.primary, NF_OLD)?;
552
553 nf_old
554 };
555
556 {
558 let alpha =
559 ScalarFixed::new(ecc_chip.clone(), layouter.namespace(|| "alpha"), self.alpha)?;
560
561 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 let rk = alpha_commitment.add(layouter.namespace(|| "rk"), &ak_P)?;
570
571 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 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 let (derived_pk_d_old, _ivk) =
602 g_d_old.mul(layouter.namespace(|| "[ivk] g_d_old"), ivk)?;
603
604 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 {
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 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 derived_cm_old.constrain_equal(layouter.namespace(|| "cm_old equality"), &cm_old)?;
648 }
649
650 {
652 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 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 let rho_new = nf_old.inner().clone();
674
675 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 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 layouter.constrain_instance(cmx.inner().cell(), config.primary, CMX)?;
708 }
709
710 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#[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 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(¶ms, &circuit).unwrap();
776
777 VerifyingKey { params, vk }
778 }
779}
780
781#[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 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(¶ms, &circuit).unwrap();
795 let pk = plonk::keygen_pk(¶ms, vk, &circuit).unwrap();
796
797 ProvingKey { params, pk }
798 }
799}
800
801#[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 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 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 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 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 #[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 {
1015 assert_eq!(
1017 format!("{:#?}\n", vk.vk.pinned()),
1018 include_str!("circuit_description").replace("\r\n", "\n")
1019 );
1020 }
1021
1022 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 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}