1use std::vec::Vec;
54
55use halo2_gadgets::{
56 ecc::{
57 chip::{EccChip, EccConfig},
58 NonIdentityPoint, ScalarFixed,
59 },
60 poseidon::{
61 primitives::{self as poseidon, ConstantLength},
62 Hash as PoseidonHash, Pow5Chip as PoseidonChip, Pow5Config as PoseidonConfig,
63 },
64 sinsemilla::chip::{SinsemillaChip, SinsemillaConfig},
65 utilities::lookup_range_check::{LookupRangeCheck, LookupRangeCheckConfig},
66};
67use halo2_proofs::{
68 circuit::{floor_planner, AssignedCell, Layouter, Value},
69 plonk::{self, Advice, Column, ConstraintSystem, Fixed, Instance as InstanceColumn},
70};
71use orchard::{
72 circuit::{
73 commit_ivk::{CommitIvkChip, CommitIvkConfig},
74 gadget::{
75 add_chip::{AddChip, AddConfig},
76 assign_free_advice, AddInstruction,
77 },
78 },
79 constants::{OrchardCommitDomains, OrchardFixedBases, OrchardHashDomains},
80};
81use pasta_curves::{pallas, vesta};
82
83use super::gadgets::authority_decrement::{AuthorityDecrementChip, AuthorityDecrementConfig};
84use crate::{
85 domain_tags,
86 gadgets::{
87 address_ownership::{prove_address_ownership, spend_auth_g_mul},
88 elgamal::{prove_elgamal_encryptions, EaPkInstanceLoc},
89 nonzero::NonZeroConfig,
90 poseidon_merkle::{synthesize_poseidon_merkle_path, MerkleSwapGate},
91 van_integrity, vote_commitment,
92 },
93 params::VOTE_COMM_TREE_DEPTH,
94 shares_hash::compute_shares_hash_in_circuit,
95};
96
97pub const K: u32 = 13;
119
120pub(super) use van_integrity::DOMAIN_VAN;
121pub(super) use vote_commitment::DOMAIN_VC;
122
123pub(super) const MAX_PROPOSAL_ID: usize = 16;
144
145const VAN_NULLIFIER_PUBLIC_OFFSET: usize = 0;
151const R_VPK_X_PUBLIC_OFFSET: usize = 1;
154const R_VPK_Y_PUBLIC_OFFSET: usize = 2;
156const VOTE_AUTHORITY_NOTE_NEW_PUBLIC_OFFSET: usize = 3;
158const VOTE_COMMITMENT_PUBLIC_OFFSET: usize = 4;
160const VOTE_COMM_TREE_ROOT_PUBLIC_OFFSET: usize = 5;
162#[allow(dead_code)]
170const VOTE_COMM_TREE_ANCHOR_HEIGHT_PUBLIC_OFFSET: usize = 6;
171const PROPOSAL_ID_PUBLIC_OFFSET: usize = 7;
177const VOTING_ROUND_ID_PUBLIC_OFFSET: usize = 8;
183const EA_PK_X_PUBLIC_OFFSET: usize = 9;
185const EA_PK_Y_PUBLIC_OFFSET: usize = 10;
187
188pub(super) use van_integrity::van_integrity_hash;
193pub(super) use vote_commitment::vote_commitment_hash;
194
195pub(super) fn domain_van_nullifier() -> pallas::Base {
200 domain_tags::vote_authority_spend()
201}
202
203pub(super) fn van_nullifier_hash(
212 vsk_nk: pallas::Base,
213 voting_round_id: pallas::Base,
214 vote_authority_note_old: pallas::Base,
215) -> pallas::Base {
216 poseidon::Hash::<_, poseidon::P128Pow5T3, ConstantLength<4>, 3, 2>::init().hash([
217 vsk_nk,
218 domain_van_nullifier(),
219 voting_round_id,
220 vote_authority_note_old,
221 ])
222}
223
224#[derive(Clone, Debug)]
236pub struct Config {
237 primary: Column<InstanceColumn>,
239 advices: [Column<Advice>; 10],
247 poseidon_config: PoseidonConfig<pallas::Base, 3, 2>,
254 add_config: AddConfig,
262 ecc_config: EccConfig<OrchardFixedBases>,
268 sinsemilla_config:
274 SinsemillaConfig<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>,
275 commit_ivk_config: CommitIvkConfig,
280 range_check: LookupRangeCheckConfig<pallas::Base, 10>,
287 merkle_swap: MerkleSwapGate,
293 authority_decrement: AuthorityDecrementConfig,
295 nonzero: NonZeroConfig,
297}
298
299impl Config {
300 fn poseidon_chip(&self) -> PoseidonChip<pallas::Base, 3, 2> {
305 PoseidonChip::construct(self.poseidon_config.clone())
306 }
307
308 fn add_chip(&self) -> AddChip {
310 AddChip::construct(self.add_config.clone())
311 }
312
313 fn ecc_chip(&self) -> EccChip<OrchardFixedBases> {
315 EccChip::construct(self.ecc_config.clone())
316 }
317
318 fn sinsemilla_chip(
320 &self,
321 ) -> SinsemillaChip<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases> {
322 SinsemillaChip::construct(self.sinsemilla_config.clone())
323 }
324
325 fn commit_ivk_chip(&self) -> CommitIvkChip {
327 CommitIvkChip::construct(self.commit_ivk_config.clone())
328 }
329
330 fn range_check_config(&self) -> LookupRangeCheckConfig<pallas::Base, 10> {
332 self.range_check
333 }
334}
335
336#[derive(Clone, Debug, Default)]
349pub struct Circuit {
350 pub(super) vote_comm_tree_path: Value<[pallas::Base; VOTE_COMM_TREE_DEPTH]>,
356 pub(super) vote_comm_tree_position: Value<u32>,
358
359 pub(super) vpk_g_d: Value<pallas::Affine>,
372 pub(super) vpk_pk_d: Value<pallas::Affine>,
376 pub(super) total_note_value: Value<pallas::Base>,
380 pub(super) proposal_authority_old: Value<pallas::Base>,
383 pub(super) van_comm_rand: Value<pallas::Base>,
385 pub(super) vote_authority_note_old: Value<pallas::Base>,
388
389 pub(super) vsk: Value<pallas::Scalar>,
395 pub(super) rivk_v: Value<pallas::Scalar>,
398 pub(super) alpha_v: Value<pallas::Scalar>,
400
401 pub(super) vsk_nk: Value<pallas::Base>,
405
406 pub(super) one_shifted: Value<pallas::Base>,
415
416 pub(super) shares: [Value<pallas::Base>; 16],
424
425 pub(super) enc_share_c1_x: [Value<pallas::Base>; 16],
432 pub(super) enc_share_c2_x: [Value<pallas::Base>; 16],
434 pub(super) enc_share_c1_y: [Value<pallas::Base>; 16],
436 pub(super) enc_share_c2_y: [Value<pallas::Base>; 16],
438
439 pub(super) share_blinds: [Value<pallas::Base>; 16],
442
443 pub(super) share_randomness: [Value<pallas::Base>; 16],
447 pub(super) ea_pk: Value<pallas::Affine>,
453
454 pub(super) vote_decision: Value<pallas::Base>,
457}
458
459impl Circuit {
460 pub(super) fn with_van_witnesses(
477 vote_comm_tree_path: Value<[pallas::Base; VOTE_COMM_TREE_DEPTH]>,
478 vote_comm_tree_position: Value<u32>,
479 vpk_g_d: Value<pallas::Affine>,
480 vpk_pk_d: Value<pallas::Affine>,
481 total_note_value: Value<pallas::Base>,
482 proposal_authority_old: Value<pallas::Base>,
483 van_comm_rand: Value<pallas::Base>,
484 vote_authority_note_old: Value<pallas::Base>,
485 vsk: Value<pallas::Scalar>,
486 rivk_v: Value<pallas::Scalar>,
487 vsk_nk: Value<pallas::Base>,
488 alpha_v: Value<pallas::Scalar>,
489 ) -> Self {
490 Circuit {
491 vote_comm_tree_path,
492 vote_comm_tree_position,
493 vpk_g_d,
494 vpk_pk_d,
495 total_note_value,
496 proposal_authority_old,
497 van_comm_rand,
498 vote_authority_note_old,
499 vsk,
500 rivk_v,
501 alpha_v,
502 vsk_nk,
503 ..Default::default()
504 }
505 }
506}
507
508impl plonk::Circuit<pallas::Base> for Circuit {
514 type Config = Config;
515 type FloorPlanner = floor_planner::V1;
516
517 fn without_witnesses(&self) -> Self {
518 Self::default()
519 }
520
521 fn configure(meta: &mut ConstraintSystem<pallas::Base>) -> Self::Config {
522 let advices: [Column<Advice>; 10] = core::array::from_fn(|_| meta.advice_column());
538 for col in &advices {
539 meta.enable_equality(*col);
540 }
541
542 let primary = meta.instance_column();
544 meta.enable_equality(primary);
545
546 let lagrange_coeffs: [Column<Fixed>; 8] = core::array::from_fn(|_| meta.fixed_column());
551 let rc_a = lagrange_coeffs[2..5].try_into().unwrap();
552 let rc_b = lagrange_coeffs[5..8].try_into().unwrap();
553
554 let constants = meta.fixed_column();
559 meta.enable_constant(constants);
560
561 let add_config = AddChip::configure(meta, advices[7], advices[8], advices[6]);
565
566 let table_idx = meta.lookup_table_column();
571 let lookup = (
572 table_idx,
573 meta.lookup_table_column(),
574 meta.lookup_table_column(),
575 );
576
577 let range_check = LookupRangeCheckConfig::configure(meta, advices[9], table_idx);
579
580 let ecc_config =
585 EccChip::<OrchardFixedBases>::configure(meta, advices, lagrange_coeffs, range_check);
586
587 let sinsemilla_config = SinsemillaChip::configure(
592 meta,
593 advices[..5].try_into().unwrap(),
594 advices[6],
595 lagrange_coeffs[0],
596 lookup,
597 range_check,
598 false,
599 );
600
601 let commit_ivk_config = CommitIvkChip::configure(meta, advices);
604
605 let poseidon_config = PoseidonChip::configure::<poseidon::P128Pow5T3>(
610 meta,
611 advices[6..9].try_into().unwrap(),
612 advices[5],
613 rc_a,
614 rc_b,
615 );
616
617 let merkle_swap = MerkleSwapGate::configure(
619 meta,
620 [advices[0], advices[1], advices[2], advices[3], advices[4]],
621 );
622
623 let authority_decrement = AuthorityDecrementChip::configure(meta, advices);
625 let nonzero = NonZeroConfig::configure(meta, [advices[0], advices[1]]);
626
627 Config {
628 primary,
629 advices,
630 poseidon_config,
631 add_config,
632 ecc_config,
633 sinsemilla_config,
634 commit_ivk_config,
635 range_check,
636 merkle_swap,
637 authority_decrement,
638 nonzero,
639 }
640 }
641
642 #[allow(non_snake_case)]
643 fn synthesize(
644 &self,
645 config: Self::Config,
646 mut layouter: impl Layouter<pallas::Base>,
647 ) -> Result<(), plonk::Error> {
648 SinsemillaChip::load(config.sinsemilla_config.clone(), &mut layouter)?;
656
657 AuthorityDecrementChip::load_table(&config.authority_decrement, &mut layouter)?;
659
660 let ecc_chip = config.ecc_chip();
662
663 let voting_round_id = layouter.assign_region(
672 || "copy voting_round_id from instance",
673 |mut region| {
674 region.assign_advice_from_instance(
675 || "voting_round_id",
676 config.primary,
677 VOTING_ROUND_ID_PUBLIC_OFFSET,
678 config.advices[0],
679 0,
680 )
681 },
682 )?;
683 let voting_round_id_cond12 = voting_round_id.clone();
686
687 let vpk_g_d_point = NonIdentityPoint::new(
691 ecc_chip.clone(),
692 layouter.namespace(|| "witness vpk_g_d"),
693 self.vpk_g_d.map(|p| p),
694 )?;
695 let vpk_g_d = vpk_g_d_point.extract_p().inner().clone();
696
697 let vpk_pk_d_point = NonIdentityPoint::new(
700 ecc_chip.clone(),
701 layouter.namespace(|| "witness vpk_pk_d"),
702 self.vpk_pk_d.map(|p| p),
703 )?;
704 let vpk_pk_d = vpk_pk_d_point.extract_p().inner().clone();
705
706 let total_note_value = assign_free_advice(
707 layouter.namespace(|| "witness total_note_value"),
708 config.advices[0],
709 self.total_note_value,
710 )?;
711
712 let proposal_authority_old = assign_free_advice(
713 layouter.namespace(|| "witness proposal_authority_old"),
714 config.advices[0],
715 self.proposal_authority_old,
716 )?;
717
718 let van_comm_rand = assign_free_advice(
719 layouter.namespace(|| "witness van_comm_rand"),
720 config.advices[0],
721 self.van_comm_rand,
722 )?;
723
724 let vote_authority_note_old = assign_free_advice(
725 layouter.namespace(|| "witness vote_authority_note_old"),
726 config.advices[0],
727 self.vote_authority_note_old,
728 )?;
729
730 let domain_van = layouter.assign_region(
733 || "DOMAIN_VAN constant",
734 |mut region| {
735 region.assign_advice_from_constant(
736 || "domain_van",
737 config.advices[0],
738 0,
739 pallas::Base::from(DOMAIN_VAN),
740 )
741 },
742 )?;
743
744 let vsk_nk = assign_free_advice(
754 layouter.namespace(|| "witness vsk_nk"),
755 config.advices[0],
756 self.vsk_nk,
757 )?;
758
759 let vote_authority_note_old_cond1 = vote_authority_note_old.clone();
768 let voting_round_id_cond4 = voting_round_id.clone();
769 let domain_van_cond6 = domain_van.clone();
770 let vpk_g_d_cond6 = vpk_g_d.clone();
771 let vpk_pk_d_cond6 = vpk_pk_d.clone();
772 let total_note_value_cond6 = total_note_value.clone();
773 let total_note_value_cond8 = total_note_value.clone();
774 let voting_round_id_cond6 = voting_round_id.clone();
775 let van_comm_rand_cond6 = van_comm_rand.clone();
776 let vsk_nk_cond4 = vsk_nk.clone();
777
778 let derived_van = van_integrity::van_integrity_poseidon(
786 &config.poseidon_config,
787 &mut layouter,
788 "Old VAN integrity",
789 domain_van,
790 vpk_g_d,
791 vpk_pk_d,
792 total_note_value,
793 voting_round_id,
794 proposal_authority_old.clone(),
795 van_comm_rand,
796 )?;
797
798 layouter.assign_region(
800 || "VAN integrity check",
801 |mut region| region.constrain_equal(derived_van.cell(), vote_authority_note_old.cell()),
802 )?;
803
804 let vsk_scalar = ScalarFixed::new(
810 ecc_chip.clone(),
811 layouter.namespace(|| "cond3 vsk"),
812 self.vsk,
813 )?;
814 let vsk_ak_point = spend_auth_g_mul(
815 ecc_chip.clone(),
816 layouter.namespace(|| "cond3 [vsk]G"),
817 "cond3: [vsk] SpendAuthG",
818 vsk_scalar,
819 )?;
820 let ak = vsk_ak_point.extract_p().inner().clone();
821 let rivk_v_scalar = ScalarFixed::new(
822 ecc_chip.clone(),
823 layouter.namespace(|| "cond3 rivk_v"),
824 self.rivk_v,
825 )?;
826 prove_address_ownership(
827 config.sinsemilla_chip(),
828 ecc_chip.clone(),
829 config.commit_ivk_chip(),
830 layouter.namespace(|| "cond3 address"),
831 "cond3",
832 ak,
833 vsk_nk.clone(),
834 rivk_v_scalar,
835 &vpk_g_d_point,
836 &vpk_pk_d_point,
837 )?;
838
839 crate::gadgets::spend_authority::prove_spend_authority(
851 ecc_chip.clone(),
852 layouter.namespace(|| "cond4 spend authority"),
853 self.alpha_v,
854 &vsk_ak_point,
855 config.primary,
856 R_VPK_X_PUBLIC_OFFSET,
857 R_VPK_Y_PUBLIC_OFFSET,
858 )?;
859
860 {
878 let root = synthesize_poseidon_merkle_path::<VOTE_COMM_TREE_DEPTH>(
879 &config.merkle_swap,
880 &config.poseidon_config,
881 &mut layouter,
882 config.advices[0],
883 vote_authority_note_old_cond1,
884 self.vote_comm_tree_position,
885 self.vote_comm_tree_path,
886 "cond1: merkle",
887 )?;
888
889 layouter.constrain_instance(
893 root.cell(),
894 config.primary,
895 VOTE_COMM_TREE_ROOT_PUBLIC_OFFSET,
896 )?;
897 }
898
899 let domain_van_nf = layouter.assign_region(
909 || "DOMAIN_VAN_NULLIFIER constant",
910 |mut region| {
911 region.assign_advice_from_constant(
912 || "domain_van_nullifier",
913 config.advices[0],
914 0,
915 domain_van_nullifier(),
916 )
917 },
918 )?;
919
920 let van_nullifier = {
932 let hasher = PoseidonHash::<
933 pallas::Base,
934 _,
935 poseidon::P128Pow5T3,
936 ConstantLength<4>,
937 3, 2, >::init(
940 config.poseidon_chip(),
941 layouter.namespace(|| "VAN nullifier Poseidon init"),
942 )?;
943 hasher.hash(
944 layouter.namespace(|| "Poseidon(vsk_nk, domain, round_id, van_old)"),
945 [
946 vsk_nk_cond4,
947 domain_van_nf,
948 voting_round_id_cond4,
949 vote_authority_note_old,
950 ],
951 )?
952 };
953
954 layouter.constrain_instance(
958 van_nullifier.cell(),
959 config.primary,
960 VAN_NULLIFIER_PUBLIC_OFFSET,
961 )?;
962
963 let proposal_id = layouter.assign_region(
975 || "copy proposal_id from instance",
976 |mut region| {
977 region.assign_advice_from_instance(
978 || "proposal_id",
979 config.primary,
980 PROPOSAL_ID_PUBLIC_OFFSET,
981 config.advices[0],
982 0,
983 )
984 },
985 )?;
986
987 let proposal_authority_new = AuthorityDecrementChip::assign(
988 &config.authority_decrement,
989 &mut layouter,
990 proposal_id.clone(),
991 proposal_authority_old,
992 self.one_shifted,
993 )?;
994
995 let derived_van_new = van_integrity::van_integrity_poseidon(
1004 &config.poseidon_config,
1005 &mut layouter,
1006 "New VAN integrity",
1007 domain_van_cond6,
1008 vpk_g_d_cond6,
1009 vpk_pk_d_cond6,
1010 total_note_value_cond6,
1011 voting_round_id_cond6,
1012 proposal_authority_new,
1013 van_comm_rand_cond6,
1014 )?;
1015
1016 layouter.constrain_instance(
1020 derived_van_new.cell(),
1021 config.primary,
1022 VOTE_AUTHORITY_NOTE_NEW_PUBLIC_OFFSET,
1023 )?;
1024
1025 let share_cells: [_; 16] = (0..16usize)
1043 .map(|i| {
1044 assign_free_advice(
1045 layouter.namespace(|| format!("witness share_{i}")),
1046 config.advices[0],
1047 self.shares[i],
1048 )
1049 })
1050 .collect::<Result<Vec<_>, _>>()?
1051 .try_into()
1052 .expect("always 16 elements");
1053
1054 let shares_sum = share_cells[1..].iter().enumerate().try_fold(
1056 share_cells[0].clone(),
1057 |acc, (i, share)| {
1058 config.add_chip().add(
1059 layouter.namespace(|| format!("shares sum step {}", i + 1)),
1060 &acc,
1061 share,
1062 )
1063 },
1064 )?;
1065
1066 layouter.assign_region(
1070 || "shares sum == total_note_value",
1071 |mut region| region.constrain_equal(shares_sum.cell(), total_note_value_cond8.cell()),
1072 )?;
1073
1074 for (i, cell) in share_cells.iter().enumerate() {
1110 config.range_check_config().copy_check(
1111 layouter.namespace(|| format!("share_{i} < 2^30")),
1112 cell.clone(),
1113 3, true, )?;
1116 }
1117
1118 let blinds: [AssignedCell<pallas::Base, pallas::Base>; 16] = (0..16)
1136 .map(|i| {
1137 assign_free_advice(
1138 layouter.namespace(|| format!("witness share_blind[{i}]")),
1139 config.advices[0],
1140 self.share_blinds[i],
1141 )
1142 })
1143 .collect::<Result<Vec<_>, _>>()?
1144 .try_into()
1145 .expect("always 16 elements");
1146
1147 let enc_c1: [AssignedCell<pallas::Base, pallas::Base>; 16] = (0..16)
1148 .map(|i| {
1149 assign_free_advice(
1150 layouter.namespace(|| format!("witness enc_c1_x[{i}]")),
1151 config.advices[0],
1152 self.enc_share_c1_x[i],
1153 )
1154 })
1155 .collect::<Result<Vec<_>, _>>()?
1156 .try_into()
1157 .expect("always 16 elements");
1158
1159 let enc_c2: [AssignedCell<pallas::Base, pallas::Base>; 16] = (0..16)
1160 .map(|i| {
1161 assign_free_advice(
1162 layouter.namespace(|| format!("witness enc_c2_x[{i}]")),
1163 config.advices[0],
1164 self.enc_share_c2_x[i],
1165 )
1166 })
1167 .collect::<Result<Vec<_>, _>>()?
1168 .try_into()
1169 .expect("always 16 elements");
1170
1171 let enc_c1_y: [AssignedCell<pallas::Base, pallas::Base>; 16] = (0..16)
1172 .map(|i| {
1173 assign_free_advice(
1174 layouter.namespace(|| format!("witness enc_c1_y[{i}]")),
1175 config.advices[0],
1176 self.enc_share_c1_y[i],
1177 )
1178 })
1179 .collect::<Result<Vec<_>, _>>()?
1180 .try_into()
1181 .expect("always 16 elements");
1182
1183 let enc_c2_y: [AssignedCell<pallas::Base, pallas::Base>; 16] = (0..16)
1184 .map(|i| {
1185 assign_free_advice(
1186 layouter.namespace(|| format!("witness enc_c2_y[{i}]")),
1187 config.advices[0],
1188 self.enc_share_c2_y[i],
1189 )
1190 })
1191 .collect::<Result<Vec<_>, _>>()?
1192 .try_into()
1193 .expect("always 16 elements");
1194
1195 let enc_c1_cond11: [AssignedCell<pallas::Base, pallas::Base>; 16] =
1197 core::array::from_fn(|i| enc_c1[i].clone());
1198 let enc_c2_cond11: [AssignedCell<pallas::Base, pallas::Base>; 16] =
1199 core::array::from_fn(|i| enc_c2[i].clone());
1200 let enc_c1_y_cond11: [AssignedCell<pallas::Base, pallas::Base>; 16] =
1201 core::array::from_fn(|i| enc_c1_y[i].clone());
1202 let enc_c2_y_cond11: [AssignedCell<pallas::Base, pallas::Base>; 16] =
1203 core::array::from_fn(|i| enc_c2_y[i].clone());
1204
1205 let shares_hash = compute_shares_hash_in_circuit(
1206 || config.poseidon_chip(),
1207 layouter.namespace(|| "cond10: shares hash"),
1208 blinds,
1209 enc_c1,
1210 enc_c2,
1211 enc_c1_y,
1212 enc_c2_y,
1213 )?;
1214
1215 {
1224 let r_cells: [_; 16] = (0..16usize)
1225 .map(|i| {
1226 assign_free_advice(
1227 layouter.namespace(|| format!("witness r[{i}]")),
1228 config.advices[0],
1229 self.share_randomness[i],
1230 )
1231 })
1232 .collect::<Result<Vec<_>, _>>()?
1233 .try_into()
1234 .expect("always 16 elements");
1235
1236 prove_elgamal_encryptions(
1237 ecc_chip.clone(),
1238 config.nonzero,
1239 layouter.namespace(|| "cond11 El Gamal"),
1240 "cond11",
1241 self.ea_pk,
1242 EaPkInstanceLoc {
1243 instance: config.primary,
1244 x_row: EA_PK_X_PUBLIC_OFFSET,
1245 y_row: EA_PK_Y_PUBLIC_OFFSET,
1246 },
1247 config.advices[0],
1248 share_cells,
1249 r_cells,
1250 enc_c1_cond11,
1251 enc_c2_cond11,
1252 enc_c1_y_cond11,
1253 enc_c2_y_cond11,
1254 )?;
1255 }
1256
1257 let domain_vc = layouter.assign_region(
1275 || "DOMAIN_VC constant",
1276 |mut region| {
1277 region.assign_advice_from_constant(
1278 || "domain_vc",
1279 config.advices[0],
1280 0,
1281 pallas::Base::from(DOMAIN_VC),
1282 )
1283 },
1284 )?;
1285
1286 let vote_decision = assign_free_advice(
1290 layouter.namespace(|| "witness vote_decision"),
1291 config.advices[0],
1292 self.vote_decision,
1293 )?;
1294
1295 let vote_commitment = vote_commitment::vote_commitment_poseidon(
1298 &config.poseidon_config,
1299 &mut layouter,
1300 "cond12",
1301 domain_vc,
1302 voting_round_id_cond12,
1303 shares_hash,
1304 proposal_id,
1305 vote_decision,
1306 )?;
1307
1308 layouter.constrain_instance(
1310 vote_commitment.cell(),
1311 config.primary,
1312 VOTE_COMMITMENT_PUBLIC_OFFSET,
1313 )?;
1314
1315 Ok(())
1316 }
1317}
1318
1319#[derive(Clone, Debug)]
1339pub struct Instance {
1340 pub van_nullifier: pallas::Base,
1342 pub r_vpk_x: pallas::Base,
1344 pub r_vpk_y: pallas::Base,
1346 pub vote_authority_note_new: pallas::Base,
1348 pub vote_commitment: pallas::Base,
1350 pub vote_comm_tree_root: pallas::Base,
1352 pub vote_comm_tree_anchor_height: pallas::Base,
1358 pub proposal_id: pallas::Base,
1364 pub voting_round_id: pallas::Base,
1369 pub ea_pk_x: pallas::Base,
1375 pub ea_pk_y: pallas::Base,
1380}
1381
1382impl Instance {
1383 pub const NUM_PUBLIC_INPUTS: usize = 11;
1385
1386 pub fn from_parts(
1400 van_nullifier: pallas::Base,
1401 r_vpk_x: pallas::Base,
1402 r_vpk_y: pallas::Base,
1403 vote_authority_note_new: pallas::Base,
1404 vote_commitment: pallas::Base,
1405 vote_comm_tree_root: pallas::Base,
1406 vote_comm_tree_anchor_height: pallas::Base,
1407 proposal_id: pallas::Base,
1408 voting_round_id: pallas::Base,
1409 ea_pk_x: pallas::Base,
1410 ea_pk_y: pallas::Base,
1411 ) -> Self {
1412 Instance {
1413 van_nullifier,
1414 r_vpk_x,
1415 r_vpk_y,
1416 vote_authority_note_new,
1417 vote_commitment,
1418 vote_comm_tree_root,
1419 vote_comm_tree_anchor_height,
1420 proposal_id,
1421 voting_round_id,
1422 ea_pk_x,
1423 ea_pk_y,
1424 }
1425 }
1426
1427 pub fn to_halo2_instance(&self) -> Vec<vesta::Scalar> {
1433 vec![
1434 self.van_nullifier,
1435 self.r_vpk_x,
1436 self.r_vpk_y,
1437 self.vote_authority_note_new,
1438 self.vote_commitment,
1439 self.vote_comm_tree_root,
1440 self.vote_comm_tree_anchor_height,
1441 self.proposal_id,
1442 self.voting_round_id,
1443 self.ea_pk_x,
1444 self.ea_pk_y,
1445 ]
1446 }
1447}
1448
1449#[cfg(test)]
1454mod tests {
1455 use super::*;
1456 use crate::gadgets::elgamal::{base_to_scalar, elgamal_encrypt, spend_auth_g_affine};
1457 use crate::protocol_hash::poseidon_hash_2;
1458 use crate::shares_hash::{hash_share_commitment_in_circuit, share_commitment, shares_hash};
1459 use core::iter;
1460 use ff::{Field, PrimeField};
1461 use group::ff::PrimeFieldBits;
1462 use group::{Curve, Group};
1463 use halo2_gadgets::sinsemilla::primitives::CommitDomain;
1464 use halo2_proofs::dev::MockProver;
1465 use pasta_curves::arithmetic::CurveAffine;
1466 use pasta_curves::pallas;
1467 use rand::rngs::OsRng;
1468
1469 use orchard::constants::{fixed_bases::COMMIT_IVK_PERSONALIZATION, L_ORCHARD_BASE};
1470
1471 fn generate_ea_keypair() -> (pallas::Scalar, pallas::Affine) {
1473 let ea_sk = pallas::Scalar::from(42u64);
1474 let ea_pk = (spend_auth_g_affine() * ea_sk).to_affine();
1475 (ea_sk, ea_pk)
1476 }
1477
1478 fn encrypt_shares(
1487 shares: [u64; 16],
1488 ea_pk: pallas::Affine,
1489 ) -> (
1490 [pallas::Base; 16],
1491 [pallas::Base; 16],
1492 [pallas::Base; 16],
1493 [pallas::Base; 16],
1494 [pallas::Base; 16],
1495 [pallas::Base; 16],
1496 pallas::Base,
1497 ) {
1498 let mut c1_x = [pallas::Base::zero(); 16];
1499 let mut c2_x = [pallas::Base::zero(); 16];
1500 let mut c1_y = [pallas::Base::zero(); 16];
1501 let mut c2_y = [pallas::Base::zero(); 16];
1502 let randomness: [pallas::Base; 16] =
1504 core::array::from_fn(|i| pallas::Base::from((i as u64 + 1) * 101));
1505 let share_blinds: [pallas::Base; 16] =
1507 core::array::from_fn(|i| pallas::Base::from(1001u64 + i as u64));
1508 for i in 0..16 {
1509 let (cx1, cx2, cy1, cy2) =
1510 elgamal_encrypt(pallas::Base::from(shares[i]), randomness[i], ea_pk)
1511 .expect("test encryption inputs should be valid");
1512 c1_x[i] = cx1;
1513 c2_x[i] = cx2;
1514 c1_y[i] = cy1;
1515 c2_y[i] = cy2;
1516 }
1517 let hash = shares_hash(share_blinds, c1_x, c2_x, c1_y, c2_y);
1518 (c1_x, c2_x, c1_y, c2_y, randomness, share_blinds, hash)
1519 }
1520
1521 fn derive_voting_address(
1534 vsk: pallas::Scalar,
1535 nk: pallas::Base,
1536 rivk_v: pallas::Scalar,
1537 ) -> (pallas::Affine, pallas::Affine) {
1538 let g = spend_auth_g_affine();
1540 let ak_point = g * vsk;
1541 let ak_x = *ak_point.to_affine().coordinates().unwrap().x();
1542
1543 let domain = CommitDomain::new(COMMIT_IVK_PERSONALIZATION);
1545 let ivk_v = domain
1546 .short_commit(
1547 iter::empty()
1548 .chain(ak_x.to_le_bits().iter().by_vals().take(L_ORCHARD_BASE))
1549 .chain(nk.to_le_bits().iter().by_vals().take(L_ORCHARD_BASE)),
1550 &rivk_v,
1551 )
1552 .expect("CommitIvk should not produce ⊥ for random inputs");
1553
1554 let g_d = pallas::Point::generator() * pallas::Scalar::from(12345u64);
1558 let g_d_affine = g_d.to_affine();
1559
1560 let ivk_v_scalar = base_to_scalar(ivk_v).expect("ivk_v must be < scalar field modulus");
1562 let pk_d = g_d * ivk_v_scalar;
1563 let pk_d_affine = pk_d.to_affine();
1564
1565 (g_d_affine, pk_d_affine)
1566 }
1567
1568 const TEST_PROPOSAL_ID: u64 = 3;
1570 const TEST_VOTE_DECISION: u64 = 1;
1571
1572 fn set_condition_11(
1580 circuit: &mut Circuit,
1581 shares_hash_val: pallas::Base,
1582 proposal_id: u64,
1583 voting_round_id: pallas::Base,
1584 ) -> pallas::Base {
1585 let proposal_id_base = pallas::Base::from(proposal_id);
1586 let vote_decision = pallas::Base::from(TEST_VOTE_DECISION);
1587 circuit.vote_decision = Value::known(vote_decision);
1588 vote_commitment_hash(
1589 voting_round_id,
1590 shares_hash_val,
1591 proposal_id_base,
1592 vote_decision,
1593 )
1594 }
1595
1596 fn build_single_leaf_merkle_path(
1601 leaf: pallas::Base,
1602 ) -> ([pallas::Base; VOTE_COMM_TREE_DEPTH], u32, pallas::Base) {
1603 let auth_path = empty_vote_comm_tree_path();
1604 let mut current = leaf;
1605 for i in 0..VOTE_COMM_TREE_DEPTH {
1606 current = poseidon_hash_2(current, auth_path[i]);
1607 }
1608 (auth_path, 0, current)
1609 }
1610
1611 fn empty_vote_comm_tree_path() -> [pallas::Base; VOTE_COMM_TREE_DEPTH] {
1612 let mut empty_roots = [pallas::Base::zero(); VOTE_COMM_TREE_DEPTH];
1613 empty_roots[0] = poseidon_hash_2(pallas::Base::zero(), pallas::Base::zero());
1614 for i in 1..VOTE_COMM_TREE_DEPTH {
1615 empty_roots[i] = poseidon_hash_2(empty_roots[i - 1], empty_roots[i - 1]);
1616 }
1617 empty_roots
1618 }
1619
1620 fn build_left_leaf_merkle_path_with_sibling(
1621 left_leaf: pallas::Base,
1622 right_leaf: pallas::Base,
1623 ) -> ([pallas::Base; VOTE_COMM_TREE_DEPTH], u32, pallas::Base) {
1624 let mut auth_path = empty_vote_comm_tree_path();
1625 auth_path[0] = right_leaf;
1626
1627 let mut current = left_leaf;
1628 for i in 0..VOTE_COMM_TREE_DEPTH {
1629 current = poseidon_hash_2(current, auth_path[i]);
1630 }
1631 (auth_path, 0, current)
1632 }
1633
1634 struct VoteReuseFixture {
1635 vsk: pallas::Scalar,
1636 vsk_nk: pallas::Base,
1637 rivk_v: pallas::Scalar,
1638 alpha_v: pallas::Scalar,
1639 vpk_g_d_affine: pallas::Affine,
1640 vpk_pk_d_affine: pallas::Affine,
1641 total_note_value: pallas::Base,
1642 proposal_authority_old: pallas::Base,
1643 proposal_id: u64,
1644 van_comm_rand: pallas::Base,
1645 shares_u64: [u64; 16],
1646 ea_pk: pallas::Affine,
1647 }
1648
1649 impl VoteReuseFixture {
1650 fn new() -> Self {
1651 let mut rng = OsRng;
1652 let vsk = pallas::Scalar::random(&mut rng);
1653 let vsk_nk = pallas::Base::random(&mut rng);
1654 let rivk_v = pallas::Scalar::random(&mut rng);
1655 let alpha_v = pallas::Scalar::random(&mut rng);
1656 let (vpk_g_d_affine, vpk_pk_d_affine) = derive_voting_address(vsk, vsk_nk, rivk_v);
1657 let (_ea_sk, ea_pk) = generate_ea_keypair();
1658
1659 Self {
1660 vsk,
1661 vsk_nk,
1662 rivk_v,
1663 alpha_v,
1664 vpk_g_d_affine,
1665 vpk_pk_d_affine,
1666 total_note_value: pallas::Base::from(10_000u64),
1667 proposal_authority_old: pallas::Base::from(13u64),
1668 proposal_id: TEST_PROPOSAL_ID,
1669 van_comm_rand: pallas::Base::random(&mut rng),
1670 shares_u64: [625; 16],
1671 ea_pk,
1672 }
1673 }
1674
1675 fn vpk_x_coordinates(&self) -> (pallas::Base, pallas::Base) {
1676 (
1677 *self.vpk_g_d_affine.coordinates().unwrap().x(),
1678 *self.vpk_pk_d_affine.coordinates().unwrap().x(),
1679 )
1680 }
1681
1682 fn vote_authority_note_old(&self, voting_round_id: pallas::Base) -> pallas::Base {
1683 let (vpk_g_d_x, vpk_pk_d_x) = self.vpk_x_coordinates();
1684 van_integrity_hash(
1685 vpk_g_d_x,
1686 vpk_pk_d_x,
1687 self.total_note_value,
1688 voting_round_id,
1689 self.proposal_authority_old,
1690 self.van_comm_rand,
1691 )
1692 }
1693
1694 fn vote_authority_note_new(&self, voting_round_id: pallas::Base) -> pallas::Base {
1695 let (vpk_g_d_x, vpk_pk_d_x) = self.vpk_x_coordinates();
1696 let proposal_authority_new =
1697 self.proposal_authority_old - pallas::Base::from(1u64 << self.proposal_id);
1698 van_integrity_hash(
1699 vpk_g_d_x,
1700 vpk_pk_d_x,
1701 self.total_note_value,
1702 voting_round_id,
1703 proposal_authority_new,
1704 self.van_comm_rand,
1705 )
1706 }
1707
1708 fn build_vote_data(
1709 &self,
1710 voting_round_id: pallas::Base,
1711 auth_path: [pallas::Base; VOTE_COMM_TREE_DEPTH],
1712 position: u32,
1713 vote_comm_tree_root: pallas::Base,
1714 anchor_height: u64,
1715 ) -> (Circuit, Instance) {
1716 let vote_authority_note_old = self.vote_authority_note_old(voting_round_id);
1717 let vote_authority_note_new = self.vote_authority_note_new(voting_round_id);
1718 let van_nullifier =
1719 van_nullifier_hash(self.vsk_nk, voting_round_id, vote_authority_note_old);
1720
1721 let g = spend_auth_g_affine();
1722 let r_vpk = (g * (self.vsk + self.alpha_v)).to_affine();
1723 let r_vpk_x = *r_vpk.coordinates().unwrap().x();
1724 let r_vpk_y = *r_vpk.coordinates().unwrap().y();
1725
1726 let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
1727 encrypt_shares(self.shares_u64, self.ea_pk);
1728
1729 let mut circuit = Circuit::with_van_witnesses(
1730 Value::known(auth_path),
1731 Value::known(position),
1732 Value::known(self.vpk_g_d_affine),
1733 Value::known(self.vpk_pk_d_affine),
1734 Value::known(self.total_note_value),
1735 Value::known(self.proposal_authority_old),
1736 Value::known(self.van_comm_rand),
1737 Value::known(vote_authority_note_old),
1738 Value::known(self.vsk),
1739 Value::known(self.rivk_v),
1740 Value::known(self.vsk_nk),
1741 Value::known(self.alpha_v),
1742 );
1743 circuit.one_shifted = Value::known(pallas::Base::from(1u64 << self.proposal_id));
1744 circuit.shares = self.shares_u64.map(|s| Value::known(pallas::Base::from(s)));
1745 circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
1746 circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
1747 circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
1748 circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
1749 circuit.share_blinds = share_blinds.map(Value::known);
1750 circuit.share_randomness = randomness.map(Value::known);
1751 circuit.ea_pk = Value::known(self.ea_pk);
1752 let vote_commitment = set_condition_11(
1753 &mut circuit,
1754 shares_hash_val,
1755 self.proposal_id,
1756 voting_round_id,
1757 );
1758
1759 let instance = Instance::from_parts(
1760 van_nullifier,
1761 r_vpk_x,
1762 r_vpk_y,
1763 vote_authority_note_new,
1764 vote_commitment,
1765 vote_comm_tree_root,
1766 pallas::Base::from(anchor_height),
1767 pallas::Base::from(self.proposal_id),
1768 voting_round_id,
1769 *self.ea_pk.coordinates().unwrap().x(),
1770 *self.ea_pk.coordinates().unwrap().y(),
1771 );
1772
1773 (circuit, instance)
1774 }
1775 }
1776
1777 fn make_test_data_with_authority_proposal_and_alpha(
1781 proposal_authority_old: pallas::Base,
1782 proposal_id: u64,
1783 alpha_v_override: Option<pallas::Scalar>,
1784 ) -> (Circuit, Instance) {
1785 let mut rng = OsRng;
1786
1787 let vsk = pallas::Scalar::random(&mut rng);
1790 let vsk_nk = pallas::Base::random(&mut rng);
1791 let rivk_v = pallas::Scalar::random(&mut rng);
1792 let alpha_v = alpha_v_override.unwrap_or_else(|| pallas::Scalar::random(&mut rng));
1793
1794 let (vpk_g_d_affine, vpk_pk_d_affine) = derive_voting_address(vsk, vsk_nk, rivk_v);
1795
1796 let g = spend_auth_g_affine();
1798 let r_vpk = (g * (vsk + alpha_v)).to_affine();
1799 let r_vpk_x = *r_vpk.coordinates().unwrap().x();
1800 let r_vpk_y = *r_vpk.coordinates().unwrap().y();
1801
1802 let vpk_g_d_x = *vpk_g_d_affine.coordinates().unwrap().x();
1804 let vpk_pk_d_x = *vpk_pk_d_affine.coordinates().unwrap().x();
1805
1806 let total_note_value = pallas::Base::from(10_000u64);
1809 let voting_round_id = pallas::Base::random(&mut rng);
1810 let van_comm_rand = pallas::Base::random(&mut rng);
1811
1812 let vote_authority_note_old = van_integrity_hash(
1813 vpk_g_d_x,
1814 vpk_pk_d_x,
1815 total_note_value,
1816 voting_round_id,
1817 proposal_authority_old,
1818 van_comm_rand,
1819 );
1820 let (auth_path, position, vote_comm_tree_root) =
1821 build_single_leaf_merkle_path(vote_authority_note_old);
1822 let van_nullifier = van_nullifier_hash(vsk_nk, voting_round_id, vote_authority_note_old);
1823 let one_shifted = pallas::Base::from(1u64 << proposal_id);
1825 let proposal_authority_new = proposal_authority_old - one_shifted;
1826 let vote_authority_note_new = van_integrity_hash(
1827 vpk_g_d_x,
1828 vpk_pk_d_x,
1829 total_note_value,
1830 voting_round_id,
1831 proposal_authority_new,
1832 van_comm_rand,
1833 );
1834
1835 let shares_u64: [u64; 16] = [625; 16]; let (_ea_sk, ea_pk) = generate_ea_keypair();
1841 let ea_pk_x = *ea_pk.coordinates().unwrap().x();
1842 let ea_pk_y = *ea_pk.coordinates().unwrap().y();
1843 let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
1844 encrypt_shares(shares_u64, ea_pk);
1845
1846 let mut circuit = Circuit::with_van_witnesses(
1847 Value::known(auth_path),
1848 Value::known(position),
1849 Value::known(vpk_g_d_affine),
1850 Value::known(vpk_pk_d_affine),
1851 Value::known(total_note_value),
1852 Value::known(proposal_authority_old),
1853 Value::known(van_comm_rand),
1854 Value::known(vote_authority_note_old),
1855 Value::known(vsk),
1856 Value::known(rivk_v),
1857 Value::known(vsk_nk),
1858 Value::known(alpha_v),
1859 );
1860 circuit.one_shifted = Value::known(one_shifted);
1861 circuit.shares = shares_u64.map(|s| Value::known(pallas::Base::from(s)));
1862 circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
1863 circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
1864 circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
1865 circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
1866 circuit.share_blinds = share_blinds.map(Value::known);
1867 circuit.share_randomness = randomness.map(Value::known);
1868 circuit.ea_pk = Value::known(ea_pk);
1869
1870 let vote_commitment =
1872 set_condition_11(&mut circuit, shares_hash_val, proposal_id, voting_round_id);
1873
1874 let instance = Instance::from_parts(
1875 van_nullifier,
1876 r_vpk_x,
1877 r_vpk_y,
1878 vote_authority_note_new,
1879 vote_commitment,
1880 vote_comm_tree_root,
1881 pallas::Base::zero(),
1882 pallas::Base::from(proposal_id),
1883 voting_round_id,
1884 ea_pk_x,
1885 ea_pk_y,
1886 );
1887
1888 (circuit, instance)
1889 }
1890
1891 fn make_test_data_with_authority_and_proposal(
1892 proposal_authority_old: pallas::Base,
1893 proposal_id: u64,
1894 ) -> (Circuit, Instance) {
1895 make_test_data_with_authority_proposal_and_alpha(proposal_authority_old, proposal_id, None)
1896 }
1897
1898 fn make_test_data_with_authority(proposal_authority_old: pallas::Base) -> (Circuit, Instance) {
1899 make_test_data_with_authority_and_proposal(proposal_authority_old, TEST_PROPOSAL_ID)
1900 }
1901
1902 fn make_test_data() -> (Circuit, Instance) {
1903 make_test_data_with_authority(pallas::Base::from(13u64))
1906 }
1907
1908 #[test]
1913 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
1914 fn van_integrity_valid_proof() {
1915 let (circuit, instance) = make_test_data();
1916
1917 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
1918
1919 assert_eq!(prover.verify(), Ok(()));
1920 }
1921
1922 #[test]
1923 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
1924 fn van_integrity_wrong_hash_fails() {
1925 let mut rng = OsRng;
1926 let (_, mut instance) = make_test_data();
1927
1928 let wrong_van = pallas::Base::random(&mut rng);
1930 let (auth_path, position, root) = build_single_leaf_merkle_path(wrong_van);
1931 instance.vote_comm_tree_root = root;
1932
1933 let vsk = pallas::Scalar::random(&mut rng);
1936 let vsk_nk = pallas::Base::random(&mut rng);
1937 let rivk_v = pallas::Scalar::random(&mut rng);
1938 let alpha_v = pallas::Scalar::random(&mut rng);
1939 let (vpk_g_d_affine, vpk_pk_d_affine) = derive_voting_address(vsk, vsk_nk, rivk_v);
1940 let g = spend_auth_g_affine();
1941 let r_vpk = (g * (vsk + alpha_v)).to_affine();
1942 instance.r_vpk_x = *r_vpk.coordinates().unwrap().x();
1943 instance.r_vpk_y = *r_vpk.coordinates().unwrap().y();
1944
1945 let shares_u64: [u64; 16] = [625; 16];
1946 let (_ea_sk, ea_pk) = generate_ea_keypair();
1947 let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
1948 encrypt_shares(shares_u64, ea_pk);
1949
1950 let proposal_authority_old = pallas::Base::from(13u64);
1953 let van_comm_rand = pallas::Base::random(&mut rng);
1954 let mut circuit = Circuit::with_van_witnesses(
1955 Value::known(auth_path),
1956 Value::known(position),
1957 Value::known(vpk_g_d_affine),
1958 Value::known(vpk_pk_d_affine),
1959 Value::known(pallas::Base::from(10_000u64)),
1960 Value::known(proposal_authority_old),
1961 Value::known(van_comm_rand),
1962 Value::known(wrong_van),
1963 Value::known(vsk),
1964 Value::known(rivk_v),
1965 Value::known(vsk_nk),
1966 Value::known(alpha_v),
1967 );
1968 circuit.one_shifted = Value::known(pallas::Base::from(1u64 << TEST_PROPOSAL_ID));
1969 circuit.shares = shares_u64.map(|s| Value::known(pallas::Base::from(s)));
1970 circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
1971 circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
1972 circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
1973 circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
1974 circuit.share_blinds = share_blinds.map(Value::known);
1975 circuit.share_randomness = randomness.map(Value::known);
1976 circuit.ea_pk = Value::known(ea_pk);
1977 let vc = set_condition_11(
1978 &mut circuit,
1979 shares_hash_val,
1980 TEST_PROPOSAL_ID,
1981 instance.voting_round_id,
1982 );
1983 instance.vote_commitment = vc;
1984 instance.proposal_id = pallas::Base::from(TEST_PROPOSAL_ID);
1985 instance.ea_pk_x = *ea_pk.coordinates().unwrap().x();
1986 instance.ea_pk_y = *ea_pk.coordinates().unwrap().y();
1987
1988 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
1989 assert!(prover.verify().is_err());
1991 }
1992
1993 #[test]
1994 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
1995 fn van_integrity_wrong_round_id_fails() {
1996 let (circuit, mut instance) = make_test_data();
1997
1998 instance.voting_round_id = pallas::Base::random(&mut OsRng);
2000
2001 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2002 assert!(prover.verify().is_err());
2005 }
2006
2007 #[test]
2008 fn round_scoped_van_redelegation_changes_nullifier() {
2009 let fixture = VoteReuseFixture::new();
2010 let round_1 = pallas::Base::from(0xCAFEu64);
2011 let round_2 = pallas::Base::from(0xCAFFu64);
2012
2013 let van_round_1 = fixture.vote_authority_note_old(round_1);
2014 let van_round_2 = fixture.vote_authority_note_old(round_2);
2015 assert_ne!(
2016 van_round_1, van_round_2,
2017 "voting_round_id is part of the VAN preimage"
2018 );
2019
2020 let nullifier_round_1 = van_nullifier_hash(fixture.vsk_nk, round_1, van_round_1);
2021 let nullifier_round_2 = van_nullifier_hash(fixture.vsk_nk, round_2, van_round_2);
2022 assert_ne!(
2023 nullifier_round_1, nullifier_round_2,
2024 "honest redelegation in a new round must not collide with the old round"
2025 );
2026 }
2027
2028 #[test]
2029 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2030 fn round_scoped_van_redelegation_verifies_with_distinct_nullifiers() {
2031 let fixture = VoteReuseFixture::new();
2032 let round_1 = pallas::Base::from(0xCAFEu64);
2033 let round_2 = pallas::Base::from(0xCAFFu64);
2034
2035 let van_round_1 = fixture.vote_authority_note_old(round_1);
2036 let (path_round_1, position_round_1, root_round_1) =
2037 build_single_leaf_merkle_path(van_round_1);
2038 let (circuit_round_1, instance_round_1) =
2039 fixture.build_vote_data(round_1, path_round_1, position_round_1, root_round_1, 10);
2040
2041 let van_round_2 = fixture.vote_authority_note_old(round_2);
2042 let (path_round_2, position_round_2, root_round_2) =
2043 build_single_leaf_merkle_path(van_round_2);
2044 let (circuit_round_2, instance_round_2) =
2045 fixture.build_vote_data(round_2, path_round_2, position_round_2, root_round_2, 20);
2046
2047 assert_ne!(van_round_1, van_round_2);
2048 assert_ne!(
2049 instance_round_1.van_nullifier,
2050 instance_round_2.van_nullifier
2051 );
2052
2053 let prover_round_1 = MockProver::run(
2054 K,
2055 &circuit_round_1,
2056 vec![instance_round_1.to_halo2_instance()],
2057 )
2058 .unwrap();
2059 assert_eq!(prover_round_1.verify(), Ok(()));
2060
2061 let prover_round_2 = MockProver::run(
2062 K,
2063 &circuit_round_2,
2064 vec![instance_round_2.to_halo2_instance()],
2065 )
2066 .unwrap();
2067 assert_eq!(prover_round_2.verify(), Ok(()));
2068 }
2069
2070 #[test]
2072 fn van_integrity_hash_deterministic() {
2073 let mut rng = OsRng;
2074
2075 let vpk_g_d = pallas::Base::random(&mut rng);
2076 let vpk_pk_d = pallas::Base::random(&mut rng);
2077 let val = pallas::Base::random(&mut rng);
2078 let round = pallas::Base::random(&mut rng);
2079 let auth = pallas::Base::random(&mut rng);
2080 let rand = pallas::Base::random(&mut rng);
2081
2082 let h1 = van_integrity_hash(vpk_g_d, vpk_pk_d, val, round, auth, rand);
2083 let h2 = van_integrity_hash(vpk_g_d, vpk_pk_d, val, round, auth, rand);
2084 assert_eq!(h1, h2);
2085
2086 let h3 = van_integrity_hash(
2088 pallas::Base::random(&mut rng),
2089 vpk_pk_d,
2090 val,
2091 round,
2092 auth,
2093 rand,
2094 );
2095 assert_ne!(h1, h3);
2096 }
2097
2098 #[test]
2110 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2111 fn condition_3_wrong_vsk_fails() {
2112 let mut rng = OsRng;
2113
2114 let vsk = pallas::Scalar::random(&mut rng);
2115 let vsk_nk = pallas::Base::random(&mut rng);
2116 let rivk_v = pallas::Scalar::random(&mut rng);
2117 let (vpk_g_d_affine, vpk_pk_d_affine) = derive_voting_address(vsk, vsk_nk, rivk_v);
2118 let vpk_g_d_x = *vpk_g_d_affine.coordinates().unwrap().x();
2119 let vpk_pk_d_x = *vpk_pk_d_affine.coordinates().unwrap().x();
2120
2121 let total_note_value = pallas::Base::from(10_000u64);
2122 let voting_round_id = pallas::Base::random(&mut rng);
2123 let proposal_authority_old = pallas::Base::from(13u64);
2124 let proposal_id = 3u64;
2125 let van_comm_rand = pallas::Base::random(&mut rng);
2126
2127 let vote_authority_note_old = van_integrity_hash(
2128 vpk_g_d_x,
2129 vpk_pk_d_x,
2130 total_note_value,
2131 voting_round_id,
2132 proposal_authority_old,
2133 van_comm_rand,
2134 );
2135 let (auth_path, position, vote_comm_tree_root) =
2136 build_single_leaf_merkle_path(vote_authority_note_old);
2137 let van_nullifier = van_nullifier_hash(vsk_nk, voting_round_id, vote_authority_note_old);
2138 let one_shifted = pallas::Base::from(1u64 << proposal_id);
2139 let proposal_authority_new = proposal_authority_old - one_shifted;
2140 let vote_authority_note_new = van_integrity_hash(
2141 vpk_g_d_x,
2142 vpk_pk_d_x,
2143 total_note_value,
2144 voting_round_id,
2145 proposal_authority_new,
2146 van_comm_rand,
2147 );
2148
2149 let shares_u64: [u64; 16] = [625; 16];
2150 let (_ea_sk, ea_pk) = generate_ea_keypair();
2151 let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
2152 encrypt_shares(shares_u64, ea_pk);
2153
2154 let wrong_vsk = pallas::Scalar::random(&mut rng);
2155 assert_ne!(
2156 wrong_vsk, vsk,
2157 "test assumes distinct vsk with high probability"
2158 );
2159 let alpha_v = pallas::Scalar::random(&mut rng);
2160 let g = spend_auth_g_affine();
2161 let r_vpk = (g * (vsk + alpha_v)).to_affine();
2162 let r_vpk_x = *r_vpk.coordinates().unwrap().x();
2163 let r_vpk_y = *r_vpk.coordinates().unwrap().y();
2164
2165 let mut circuit = Circuit::with_van_witnesses(
2166 Value::known(auth_path),
2167 Value::known(position),
2168 Value::known(vpk_g_d_affine),
2169 Value::known(vpk_pk_d_affine),
2170 Value::known(total_note_value),
2171 Value::known(proposal_authority_old),
2172 Value::known(van_comm_rand),
2173 Value::known(vote_authority_note_old),
2174 Value::known(wrong_vsk),
2175 Value::known(rivk_v),
2176 Value::known(vsk_nk),
2177 Value::known(alpha_v),
2178 );
2179 circuit.one_shifted = Value::known(one_shifted);
2180 circuit.shares = shares_u64.map(|s| Value::known(pallas::Base::from(s)));
2181 circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
2182 circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
2183 circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
2184 circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
2185 circuit.share_blinds = share_blinds.map(Value::known);
2186 circuit.share_randomness = randomness.map(Value::known);
2187 circuit.ea_pk = Value::known(ea_pk);
2188 let vc = set_condition_11(&mut circuit, shares_hash_val, proposal_id, voting_round_id);
2189
2190 let instance = Instance::from_parts(
2191 van_nullifier,
2192 r_vpk_x,
2193 r_vpk_y,
2194 vote_authority_note_new,
2195 vc,
2196 vote_comm_tree_root,
2197 pallas::Base::zero(),
2198 pallas::Base::from(proposal_id),
2199 voting_round_id,
2200 *ea_pk.coordinates().unwrap().x(),
2201 *ea_pk.coordinates().unwrap().y(),
2202 );
2203
2204 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2205 assert!(
2206 prover.verify().is_err(),
2207 "condition 3 must reject wrong vsk"
2208 );
2209 }
2210
2211 #[test]
2215 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2216 fn condition_3_wrong_vpk_pk_d_fails() {
2217 let mut rng = OsRng;
2218
2219 let vsk = pallas::Scalar::random(&mut rng);
2220 let vsk_nk = pallas::Base::random(&mut rng);
2221 let rivk_v = pallas::Scalar::random(&mut rng);
2222 let (vpk_g_d_affine, _vpk_pk_d_correct) = derive_voting_address(vsk, vsk_nk, rivk_v);
2223 let vpk_g_d_x = *vpk_g_d_affine.coordinates().unwrap().x();
2224
2225 let wrong_vpk_pk_d_affine =
2226 (pallas::Point::generator() * pallas::Scalar::from(99999u64)).to_affine();
2227 let wrong_vpk_pk_d_x = *wrong_vpk_pk_d_affine.coordinates().unwrap().x();
2228
2229 let total_note_value = pallas::Base::from(10_000u64);
2230 let voting_round_id = pallas::Base::random(&mut rng);
2231 let proposal_authority_old = pallas::Base::from(13u64);
2232 let proposal_id = 3u64;
2233 let van_comm_rand = pallas::Base::random(&mut rng);
2234
2235 let vote_authority_note_old = van_integrity_hash(
2236 vpk_g_d_x,
2237 wrong_vpk_pk_d_x,
2238 total_note_value,
2239 voting_round_id,
2240 proposal_authority_old,
2241 van_comm_rand,
2242 );
2243 let (auth_path, position, vote_comm_tree_root) =
2244 build_single_leaf_merkle_path(vote_authority_note_old);
2245 let van_nullifier = van_nullifier_hash(vsk_nk, voting_round_id, vote_authority_note_old);
2246 let one_shifted = pallas::Base::from(1u64 << proposal_id);
2247 let proposal_authority_new = proposal_authority_old - one_shifted;
2248 let vote_authority_note_new = van_integrity_hash(
2249 vpk_g_d_x,
2250 wrong_vpk_pk_d_x,
2251 total_note_value,
2252 voting_round_id,
2253 proposal_authority_new,
2254 van_comm_rand,
2255 );
2256
2257 let shares_u64: [u64; 16] = [625; 16];
2258 let (_ea_sk, ea_pk) = generate_ea_keypair();
2259 let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
2260 encrypt_shares(shares_u64, ea_pk);
2261
2262 let alpha_v = pallas::Scalar::random(&mut rng);
2263 let g = spend_auth_g_affine();
2264 let r_vpk = (g * (vsk + alpha_v)).to_affine();
2265 let r_vpk_x = *r_vpk.coordinates().unwrap().x();
2266 let r_vpk_y = *r_vpk.coordinates().unwrap().y();
2267
2268 let mut circuit = Circuit::with_van_witnesses(
2269 Value::known(auth_path),
2270 Value::known(position),
2271 Value::known(vpk_g_d_affine),
2272 Value::known(wrong_vpk_pk_d_affine),
2273 Value::known(total_note_value),
2274 Value::known(proposal_authority_old),
2275 Value::known(van_comm_rand),
2276 Value::known(vote_authority_note_old),
2277 Value::known(vsk),
2278 Value::known(rivk_v),
2279 Value::known(vsk_nk),
2280 Value::known(alpha_v),
2281 );
2282 circuit.one_shifted = Value::known(one_shifted);
2283 circuit.shares = shares_u64.map(|s| Value::known(pallas::Base::from(s)));
2284 circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
2285 circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
2286 circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
2287 circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
2288 circuit.share_blinds = share_blinds.map(Value::known);
2289 circuit.share_randomness = randomness.map(Value::known);
2290 circuit.ea_pk = Value::known(ea_pk);
2291 let vc = set_condition_11(&mut circuit, shares_hash_val, proposal_id, voting_round_id);
2292
2293 let instance = Instance::from_parts(
2294 van_nullifier,
2295 r_vpk_x,
2296 r_vpk_y,
2297 vote_authority_note_new,
2298 vc,
2299 vote_comm_tree_root,
2300 pallas::Base::zero(),
2301 pallas::Base::from(proposal_id),
2302 voting_round_id,
2303 *ea_pk.coordinates().unwrap().x(),
2304 *ea_pk.coordinates().unwrap().y(),
2305 );
2306
2307 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2308 assert!(
2309 prover.verify().is_err(),
2310 "condition 3 must reject wrong vpk_pk_d"
2311 );
2312 }
2313
2314 #[test]
2320 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2321 fn condition_4_wrong_r_vpk_fails() {
2322 let (circuit, mut instance) = make_test_data();
2323
2324 instance.r_vpk_x = pallas::Base::random(&mut OsRng);
2325
2326 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2327 assert!(
2328 prover.verify().is_err(),
2329 "condition 4 must reject wrong r_vpk"
2330 );
2331 }
2332
2333 #[test]
2338 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2339 fn condition_4_alpha_zero_is_accepted_by_relation() {
2340 let (circuit, instance) = make_test_data_with_authority_proposal_and_alpha(
2341 pallas::Base::from(13u64),
2342 TEST_PROPOSAL_ID,
2343 Some(pallas::Scalar::zero()),
2344 );
2345
2346 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2347 assert_eq!(prover.verify(), Ok(()));
2348 }
2349
2350 #[test]
2356 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2357 fn van_nullifier_wrong_public_input_fails() {
2358 let (circuit, mut instance) = make_test_data();
2359
2360 instance.van_nullifier = pallas::Base::random(&mut OsRng);
2362
2363 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2364
2365 assert!(prover.verify().is_err());
2367 }
2368
2369 #[test]
2375 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2376 fn van_nullifier_wrong_vsk_nk_fails() {
2377 let mut rng = OsRng;
2378
2379 let vsk = pallas::Scalar::random(&mut rng);
2381 let vsk_nk = pallas::Base::random(&mut rng);
2382 let rivk_v = pallas::Scalar::random(&mut rng);
2383 let (vpk_g_d_affine, vpk_pk_d_affine) = derive_voting_address(vsk, vsk_nk, rivk_v);
2384 let vpk_g_d_x = *vpk_g_d_affine.coordinates().unwrap().x();
2385 let vpk_pk_d_x = *vpk_pk_d_affine.coordinates().unwrap().x();
2386
2387 let total_note_value = pallas::Base::from(10_000u64);
2388 let voting_round_id = pallas::Base::random(&mut rng);
2389 let proposal_authority_old = pallas::Base::from(5u64); let van_comm_rand = pallas::Base::random(&mut rng);
2391 let proposal_id = 0u64; let vote_authority_note_old = van_integrity_hash(
2394 vpk_g_d_x,
2395 vpk_pk_d_x,
2396 total_note_value,
2397 voting_round_id,
2398 proposal_authority_old,
2399 van_comm_rand,
2400 );
2401 let (auth_path, position, vote_comm_tree_root) =
2402 build_single_leaf_merkle_path(vote_authority_note_old);
2403 let van_nullifier = van_nullifier_hash(vsk_nk, voting_round_id, vote_authority_note_old);
2404 let one_shifted = pallas::Base::from(1u64 << proposal_id);
2405 let proposal_authority_new = proposal_authority_old - one_shifted;
2406 let vote_authority_note_new = van_integrity_hash(
2407 vpk_g_d_x,
2408 vpk_pk_d_x,
2409 total_note_value,
2410 voting_round_id,
2411 proposal_authority_new,
2412 van_comm_rand,
2413 );
2414
2415 let wrong_vsk_nk = pallas::Base::random(&mut rng);
2417 let alpha_v = pallas::Scalar::random(&mut rng);
2418 let g = spend_auth_g_affine();
2419 let r_vpk = (g * (vsk + alpha_v)).to_affine();
2420 let r_vpk_x = *r_vpk.coordinates().unwrap().x();
2421 let r_vpk_y = *r_vpk.coordinates().unwrap().y();
2422
2423 let shares_u64: [u64; 16] = [625; 16];
2425
2426 let (_ea_sk, ea_pk) = generate_ea_keypair();
2428 let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
2429 encrypt_shares(shares_u64, ea_pk);
2430
2431 let mut circuit = Circuit::with_van_witnesses(
2432 Value::known(auth_path),
2433 Value::known(position),
2434 Value::known(vpk_g_d_affine),
2435 Value::known(vpk_pk_d_affine),
2436 Value::known(total_note_value),
2437 Value::known(proposal_authority_old),
2438 Value::known(van_comm_rand),
2439 Value::known(vote_authority_note_old),
2440 Value::known(vsk),
2441 Value::known(rivk_v),
2442 Value::known(wrong_vsk_nk),
2443 Value::known(alpha_v),
2444 );
2445 circuit.one_shifted = Value::known(one_shifted);
2446 circuit.shares = shares_u64.map(|s| Value::known(pallas::Base::from(s)));
2447 circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
2448 circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
2449 circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
2450 circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
2451 circuit.share_blinds = share_blinds.map(Value::known);
2452 circuit.share_randomness = randomness.map(Value::known);
2453 circuit.ea_pk = Value::known(ea_pk);
2454 let vc = set_condition_11(&mut circuit, shares_hash_val, proposal_id, voting_round_id);
2455
2456 let instance = Instance::from_parts(
2457 van_nullifier,
2458 r_vpk_x,
2459 r_vpk_y,
2460 vote_authority_note_new,
2461 vc,
2462 vote_comm_tree_root,
2463 pallas::Base::zero(),
2464 pallas::Base::from(proposal_id),
2465 voting_round_id,
2466 *ea_pk.coordinates().unwrap().x(),
2467 *ea_pk.coordinates().unwrap().y(),
2468 );
2469
2470 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2471 assert!(prover.verify().is_err());
2475 }
2476
2477 #[test]
2479 fn van_nullifier_hash_deterministic() {
2480 let mut rng = OsRng;
2481
2482 let nk = pallas::Base::random(&mut rng);
2483 let round = pallas::Base::random(&mut rng);
2484 let van = pallas::Base::random(&mut rng);
2485
2486 let h1 = van_nullifier_hash(nk, round, van);
2487 let h2 = van_nullifier_hash(nk, round, van);
2488 assert_eq!(h1, h2);
2489
2490 let h3 = van_nullifier_hash(pallas::Base::random(&mut rng), round, van);
2492 assert_ne!(h1, h3);
2493 }
2494
2495 #[test]
2496 fn van_nullifier_hash_frozen_vector() {
2497 assert_eq!(
2498 van_nullifier_hash(
2499 pallas::Base::from(1u64),
2500 pallas::Base::from(42u64),
2501 pallas::Base::from(100u64),
2502 ),
2503 pallas::Base::from_repr([
2504 114, 56, 62, 208, 155, 244, 76, 209, 125, 210, 149, 109, 176, 88, 34, 116, 123, 56,
2505 62, 216, 108, 204, 55, 120, 28, 155, 217, 186, 29, 159, 128, 2,
2506 ])
2507 .expect("frozen vector must be canonical")
2508 );
2509 }
2510
2511 #[test]
2512 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2513 fn stale_and_current_anchor_proofs_for_same_van_share_nullifier() {
2514 let fixture = VoteReuseFixture::new();
2515 let voting_round_id = pallas::Base::from(0xCAFEu64);
2516 let stale_van = fixture.vote_authority_note_old(voting_round_id);
2517 let successor_van = fixture.vote_authority_note_new(voting_round_id);
2518
2519 let (stale_path, stale_position, stale_root) = build_single_leaf_merkle_path(stale_van);
2520 let (stale_circuit, stale_instance) =
2521 fixture.build_vote_data(voting_round_id, stale_path, stale_position, stale_root, 10);
2522
2523 let (current_path, current_position, current_root) =
2524 build_left_leaf_merkle_path_with_sibling(stale_van, successor_van);
2525 let (current_circuit, current_instance) = fixture.build_vote_data(
2526 voting_round_id,
2527 current_path,
2528 current_position,
2529 current_root,
2530 11,
2531 );
2532
2533 assert_ne!(
2534 stale_root, current_root,
2535 "the successor VAN changes the supplied tree anchor"
2536 );
2537 assert_eq!(
2538 stale_instance.van_nullifier, current_instance.van_nullifier,
2539 "same (vsk_nk, voting_round_id, VAN) must collide for chain-side nullifier uniqueness"
2540 );
2541
2542 let stale_prover =
2545 MockProver::run(K, &stale_circuit, vec![stale_instance.to_halo2_instance()]).unwrap();
2546 assert_eq!(stale_prover.verify(), Ok(()));
2547
2548 let current_prover = MockProver::run(
2549 K,
2550 ¤t_circuit,
2551 vec![current_instance.to_halo2_instance()],
2552 )
2553 .unwrap();
2554 assert_eq!(current_prover.verify(), Ok(()));
2555 }
2556
2557 #[test]
2559 fn domain_van_nullifier_deterministic() {
2560 let d1 = domain_van_nullifier();
2561 let d2 = domain_van_nullifier();
2562 assert_eq!(d1, d2);
2563
2564 assert_ne!(d1, pallas::Base::zero());
2566 }
2567
2568 #[test]
2574 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2575 fn proposal_authority_decrement_minimum_valid() {
2576 let (circuit, instance) =
2580 make_test_data_with_authority_and_proposal(pallas::Base::from(2u64), 1);
2581
2582 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2583 assert_eq!(prover.verify(), Ok(()));
2584 }
2585
2586 #[test]
2589 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2590 fn proposal_authority_zero_fails() {
2591 let (circuit, instance) = make_test_data_with_authority(pallas::Base::zero());
2592
2593 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2594
2595 assert!(prover.verify().is_err());
2596 }
2597
2598 #[test]
2600 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2601 fn proposal_id_zero_fails() {
2602 let (circuit, instance) =
2605 make_test_data_with_authority_and_proposal(pallas::Base::one(), 0);
2606
2607 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2608 assert!(prover.verify().is_err(), "proposal_id = 0 must be rejected");
2609 }
2610
2611 #[test]
2613 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2614 fn proposal_authority_full_authority_proposal_1_passes() {
2615 const MAX_PROPOSAL_AUTHORITY: u64 = 65535;
2616 let (circuit, instance) = make_test_data_with_authority_and_proposal(
2617 pallas::Base::from(MAX_PROPOSAL_AUTHORITY),
2618 1,
2619 );
2620
2621 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2622 assert_eq!(prover.verify(), Ok(()));
2623 }
2624
2625 #[test]
2627 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2628 fn proposal_authority_wrong_new_fails() {
2629 let (circuit, mut instance) =
2630 make_test_data_with_authority_and_proposal(pallas::Base::from(65535u64), 1);
2631
2632 instance.vote_authority_note_new = pallas::Base::random(&mut OsRng);
2633
2634 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2635 assert!(prover.verify().is_err());
2636 }
2637
2638 #[test]
2643 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2644 fn proposal_authority_bit_not_set_fails() {
2645 let (circuit, instance) =
2646 make_test_data_with_authority_and_proposal(pallas::Base::from(4u64), 1);
2647
2648 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2649 assert!(prover.verify().is_err());
2650 }
2651
2652 #[test]
2656 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2657 fn proposal_authority_condition6_run_sel_constraint() {
2658 let (circuit, instance) =
2659 make_test_data_with_authority_and_proposal(pallas::Base::from(3u64), 1);
2660
2661 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2662 assert_eq!(prover.verify(), Ok(()));
2663 }
2664
2665 #[test]
2670 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2671 fn proposal_authority_exceeds_16_bits_fails() {
2672 let (circuit, instance) = make_test_data_with_authority(pallas::Base::from(65536u64));
2674 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2675 assert!(
2676 prover.verify().is_err(),
2677 "authority > 65535 must be rejected by the 16-bit bit decomposition"
2678 );
2679 }
2680
2681 #[test]
2687 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2688 fn new_van_integrity_wrong_public_input_fails() {
2689 let (circuit, mut instance) = make_test_data();
2690
2691 instance.vote_authority_note_new = pallas::Base::random(&mut OsRng);
2693
2694 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2695
2696 assert!(prover.verify().is_err());
2698 }
2699
2700 #[test]
2703 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2704 fn new_van_integrity_large_authority() {
2705 let (circuit, instance) = make_test_data_with_authority(pallas::Base::from(0xFFF8u64));
2706
2707 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2708 assert_eq!(prover.verify(), Ok(()));
2709 }
2710
2711 #[test]
2717 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2718 fn van_membership_wrong_root_fails() {
2719 let (circuit, mut instance) = make_test_data();
2720
2721 instance.vote_comm_tree_root = pallas::Base::random(&mut OsRng);
2723
2724 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2725 assert!(prover.verify().is_err());
2726 }
2727
2728 #[test]
2730 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2731 fn van_membership_nonzero_position() {
2732 let mut rng = OsRng;
2733
2734 let vsk = pallas::Scalar::random(&mut rng);
2736 let vsk_nk = pallas::Base::random(&mut rng);
2737 let rivk_v = pallas::Scalar::random(&mut rng);
2738 let (vpk_g_d_affine, vpk_pk_d_affine) = derive_voting_address(vsk, vsk_nk, rivk_v);
2739 let vpk_g_d_x = *vpk_g_d_affine.coordinates().unwrap().x();
2740 let vpk_pk_d_x = *vpk_pk_d_affine.coordinates().unwrap().x();
2741
2742 let total_note_value = pallas::Base::from(10_000u64);
2743 let voting_round_id = pallas::Base::random(&mut rng);
2744 let proposal_authority_old = pallas::Base::from(5u64); let proposal_id = 2u64;
2747 let van_comm_rand = pallas::Base::random(&mut rng);
2748
2749 let vote_authority_note_old = van_integrity_hash(
2750 vpk_g_d_x,
2751 vpk_pk_d_x,
2752 total_note_value,
2753 voting_round_id,
2754 proposal_authority_old,
2755 van_comm_rand,
2756 );
2757
2758 let position: u32 = 7;
2760 let mut empty_roots = [pallas::Base::zero(); VOTE_COMM_TREE_DEPTH];
2761 empty_roots[0] = poseidon_hash_2(pallas::Base::zero(), pallas::Base::zero());
2762 for i in 1..VOTE_COMM_TREE_DEPTH {
2763 empty_roots[i] = poseidon_hash_2(empty_roots[i - 1], empty_roots[i - 1]);
2764 }
2765 let auth_path = empty_roots;
2766 let mut current = vote_authority_note_old;
2767 for i in 0..VOTE_COMM_TREE_DEPTH {
2768 if (position >> i) & 1 == 0 {
2769 current = poseidon_hash_2(current, auth_path[i]);
2770 } else {
2771 current = poseidon_hash_2(auth_path[i], current);
2772 }
2773 }
2774 let vote_comm_tree_root = current;
2775
2776 let van_nullifier = van_nullifier_hash(vsk_nk, voting_round_id, vote_authority_note_old);
2777 let one_shifted = pallas::Base::from(1u64 << proposal_id);
2778 let proposal_authority_new = proposal_authority_old - one_shifted;
2779 let vote_authority_note_new = van_integrity_hash(
2780 vpk_g_d_x,
2781 vpk_pk_d_x,
2782 total_note_value,
2783 voting_round_id,
2784 proposal_authority_new,
2785 van_comm_rand,
2786 );
2787
2788 let alpha_v = pallas::Scalar::random(&mut rng);
2789 let g = spend_auth_g_affine();
2790 let r_vpk = (g * (vsk + alpha_v)).to_affine();
2791 let r_vpk_x = *r_vpk.coordinates().unwrap().x();
2792 let r_vpk_y = *r_vpk.coordinates().unwrap().y();
2793
2794 let shares_u64: [u64; 16] = [625; 16];
2796
2797 let (_ea_sk, ea_pk) = generate_ea_keypair();
2799 let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
2800 encrypt_shares(shares_u64, ea_pk);
2801
2802 let mut circuit = Circuit::with_van_witnesses(
2803 Value::known(auth_path),
2804 Value::known(position),
2805 Value::known(vpk_g_d_affine),
2806 Value::known(vpk_pk_d_affine),
2807 Value::known(total_note_value),
2808 Value::known(proposal_authority_old),
2809 Value::known(van_comm_rand),
2810 Value::known(vote_authority_note_old),
2811 Value::known(vsk),
2812 Value::known(rivk_v),
2813 Value::known(vsk_nk),
2814 Value::known(alpha_v),
2815 );
2816 circuit.one_shifted = Value::known(one_shifted);
2817 circuit.shares = shares_u64.map(|s| Value::known(pallas::Base::from(s)));
2818 circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
2819 circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
2820 circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
2821 circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
2822 circuit.share_blinds = share_blinds.map(Value::known);
2823 circuit.share_randomness = randomness.map(Value::known);
2824 circuit.ea_pk = Value::known(ea_pk);
2825 let vc = set_condition_11(&mut circuit, shares_hash_val, proposal_id, voting_round_id);
2826
2827 let instance = Instance::from_parts(
2828 van_nullifier,
2829 r_vpk_x,
2830 r_vpk_y,
2831 vote_authority_note_new,
2832 vc,
2833 vote_comm_tree_root,
2834 pallas::Base::zero(),
2835 pallas::Base::from(proposal_id),
2836 voting_round_id,
2837 *ea_pk.coordinates().unwrap().x(),
2838 *ea_pk.coordinates().unwrap().y(),
2839 );
2840
2841 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2842 assert_eq!(prover.verify(), Ok(()));
2843 }
2844
2845 #[test]
2847 fn poseidon_hash_2_deterministic() {
2848 let mut rng = OsRng;
2849 let a = pallas::Base::random(&mut rng);
2850 let b = pallas::Base::random(&mut rng);
2851
2852 assert_eq!(poseidon_hash_2(a, b), poseidon_hash_2(a, b));
2853 assert_ne!(poseidon_hash_2(a, b), poseidon_hash_2(b, a));
2855 }
2856
2857 #[test]
2863 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2864 fn shares_sum_wrong_total_fails() {
2865 let (mut circuit, instance) = make_test_data();
2866
2867 circuit.shares[3] = Value::known(pallas::Base::from(999u64));
2871
2872 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2873 assert!(prover.verify().is_err());
2875 }
2876
2877 #[test]
2883 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2884 fn shares_range_max_valid() {
2885 let max_share = pallas::Base::from((1u64 << 30) - 1); let total = (0..16).fold(pallas::Base::zero(), |acc, _| acc + max_share);
2887
2888 let mut rng = OsRng;
2889 let vsk = pallas::Scalar::random(&mut rng);
2891 let vsk_nk = pallas::Base::random(&mut rng);
2892 let rivk_v = pallas::Scalar::random(&mut rng);
2893 let (vpk_g_d_affine, vpk_pk_d_affine) = derive_voting_address(vsk, vsk_nk, rivk_v);
2894 let vpk_g_d_x = *vpk_g_d_affine.coordinates().unwrap().x();
2895 let vpk_pk_d_x = *vpk_pk_d_affine.coordinates().unwrap().x();
2896
2897 let voting_round_id = pallas::Base::random(&mut rng);
2898 let proposal_authority_old = pallas::Base::from(5u64); let proposal_id = 2u64;
2901 let van_comm_rand = pallas::Base::random(&mut rng);
2902
2903 let vote_authority_note_old = van_integrity_hash(
2904 vpk_g_d_x,
2905 vpk_pk_d_x,
2906 total,
2907 voting_round_id,
2908 proposal_authority_old,
2909 van_comm_rand,
2910 );
2911 let (auth_path, position, vote_comm_tree_root) =
2912 build_single_leaf_merkle_path(vote_authority_note_old);
2913 let van_nullifier = van_nullifier_hash(vsk_nk, voting_round_id, vote_authority_note_old);
2914 let one_shifted = pallas::Base::from(1u64 << proposal_id);
2915 let proposal_authority_new = proposal_authority_old - one_shifted;
2916 let vote_authority_note_new = van_integrity_hash(
2917 vpk_g_d_x,
2918 vpk_pk_d_x,
2919 total,
2920 voting_round_id,
2921 proposal_authority_new,
2922 van_comm_rand,
2923 );
2924
2925 let max_share_u64 = (1u64 << 30) - 1;
2927 let shares_u64: [u64; 16] = [max_share_u64; 16];
2928 let (_ea_sk, ea_pk) = generate_ea_keypair();
2929 let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
2930 encrypt_shares(shares_u64, ea_pk);
2931
2932 let alpha_v = pallas::Scalar::random(&mut rng);
2933 let g = spend_auth_g_affine();
2934 let r_vpk = (g * (vsk + alpha_v)).to_affine();
2935 let r_vpk_x = *r_vpk.coordinates().unwrap().x();
2936 let r_vpk_y = *r_vpk.coordinates().unwrap().y();
2937
2938 let mut circuit = Circuit::with_van_witnesses(
2939 Value::known(auth_path),
2940 Value::known(position),
2941 Value::known(vpk_g_d_affine),
2942 Value::known(vpk_pk_d_affine),
2943 Value::known(total),
2944 Value::known(proposal_authority_old),
2945 Value::known(van_comm_rand),
2946 Value::known(vote_authority_note_old),
2947 Value::known(vsk),
2948 Value::known(rivk_v),
2949 Value::known(vsk_nk),
2950 Value::known(alpha_v),
2951 );
2952 circuit.one_shifted = Value::known(one_shifted);
2953 circuit.shares = [Value::known(max_share); 16];
2954 circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
2955 circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
2956 circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
2957 circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
2958 circuit.share_blinds = share_blinds.map(Value::known);
2959 circuit.share_randomness = randomness.map(Value::known);
2960 circuit.ea_pk = Value::known(ea_pk);
2961 let vc = set_condition_11(&mut circuit, shares_hash_val, proposal_id, voting_round_id);
2962
2963 let instance = Instance::from_parts(
2964 van_nullifier,
2965 r_vpk_x,
2966 r_vpk_y,
2967 vote_authority_note_new,
2968 vc,
2969 vote_comm_tree_root,
2970 pallas::Base::zero(),
2971 pallas::Base::from(proposal_id),
2972 voting_round_id,
2973 *ea_pk.coordinates().unwrap().x(),
2974 *ea_pk.coordinates().unwrap().y(),
2975 );
2976
2977 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2978 assert_eq!(prover.verify(), Ok(()));
2979 }
2980
2981 #[test]
2983 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2984 fn shares_range_overflow_fails() {
2985 let (mut circuit, instance) = make_test_data();
2986
2987 circuit.shares[0] = Value::known(pallas::Base::from(1u64 << 30));
2991
2992 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2993 assert!(prover.verify().is_err());
2994 }
2995
2996 #[test]
2999 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3000 fn shares_range_field_wrap_fails() {
3001 let (mut circuit, instance) = make_test_data();
3002
3003 circuit.shares[0] = Value::known(-pallas::Base::one());
3006
3007 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3008 assert!(prover.verify().is_err());
3009 }
3010
3011 #[test]
3017 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3018 fn shares_range_single_overflow_correct_sum_fails() {
3019 let mut rng = OsRng;
3020
3021 let overflow_share = pallas::Base::from(1u64 << 30); let normal_share_u64 = 625u64;
3023 let total_note_value = overflow_share + pallas::Base::from(15u64 * normal_share_u64);
3025
3026 let vsk = pallas::Scalar::random(&mut rng);
3027 let vsk_nk = pallas::Base::random(&mut rng);
3028 let rivk_v = pallas::Scalar::random(&mut rng);
3029 let alpha_v = pallas::Scalar::random(&mut rng);
3030 let (vpk_g_d_affine, vpk_pk_d_affine) = derive_voting_address(vsk, vsk_nk, rivk_v);
3031 let vpk_g_d_x = *vpk_g_d_affine.coordinates().unwrap().x();
3032 let vpk_pk_d_x = *vpk_pk_d_affine.coordinates().unwrap().x();
3033
3034 let voting_round_id = pallas::Base::random(&mut rng);
3035 let proposal_authority_old = pallas::Base::from(13u64); let proposal_id = TEST_PROPOSAL_ID;
3037 let van_comm_rand = pallas::Base::random(&mut rng);
3038
3039 let vote_authority_note_old = van_integrity_hash(
3040 vpk_g_d_x,
3041 vpk_pk_d_x,
3042 total_note_value,
3043 voting_round_id,
3044 proposal_authority_old,
3045 van_comm_rand,
3046 );
3047 let (auth_path, position, vote_comm_tree_root) =
3048 build_single_leaf_merkle_path(vote_authority_note_old);
3049 let van_nullifier = van_nullifier_hash(vsk_nk, voting_round_id, vote_authority_note_old);
3050 let one_shifted = pallas::Base::from(1u64 << proposal_id);
3051 let proposal_authority_new = proposal_authority_old - one_shifted;
3052 let vote_authority_note_new = van_integrity_hash(
3053 vpk_g_d_x,
3054 vpk_pk_d_x,
3055 total_note_value,
3056 voting_round_id,
3057 proposal_authority_new,
3058 van_comm_rand,
3059 );
3060
3061 let shares_u64: [u64; 16] = {
3064 let mut arr = [normal_share_u64; 16];
3065 arr[0] = 1u64 << 30;
3066 arr
3067 };
3068 let (_ea_sk, ea_pk) = generate_ea_keypair();
3069 let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
3070 encrypt_shares(shares_u64, ea_pk);
3071
3072 let g = spend_auth_g_affine();
3073 let r_vpk = (g * (vsk + alpha_v)).to_affine();
3074
3075 let mut circuit = Circuit::with_van_witnesses(
3076 Value::known(auth_path),
3077 Value::known(position),
3078 Value::known(vpk_g_d_affine),
3079 Value::known(vpk_pk_d_affine),
3080 Value::known(total_note_value),
3081 Value::known(proposal_authority_old),
3082 Value::known(van_comm_rand),
3083 Value::known(vote_authority_note_old),
3084 Value::known(vsk),
3085 Value::known(rivk_v),
3086 Value::known(vsk_nk),
3087 Value::known(alpha_v),
3088 );
3089 circuit.one_shifted = Value::known(one_shifted);
3090 circuit.shares = shares_u64.map(|s| Value::known(pallas::Base::from(s)));
3091 circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
3092 circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
3093 circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
3094 circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
3095 circuit.share_blinds = share_blinds.map(Value::known);
3096 circuit.share_randomness = randomness.map(Value::known);
3097 circuit.ea_pk = Value::known(ea_pk);
3098
3099 let vote_commitment =
3100 set_condition_11(&mut circuit, shares_hash_val, proposal_id, voting_round_id);
3101
3102 let instance = Instance::from_parts(
3103 van_nullifier,
3104 *r_vpk.coordinates().unwrap().x(),
3105 *r_vpk.coordinates().unwrap().y(),
3106 vote_authority_note_new,
3107 vote_commitment,
3108 vote_comm_tree_root,
3109 pallas::Base::zero(),
3110 pallas::Base::from(proposal_id),
3111 voting_round_id,
3112 *ea_pk.coordinates().unwrap().x(),
3113 *ea_pk.coordinates().unwrap().y(),
3114 );
3115
3116 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3117 assert!(
3120 prover.verify().is_err(),
3121 "range check must reject a share equal to 2^30 even when the total sum is correct"
3122 );
3123 }
3124
3125 #[test]
3131 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3132 fn shares_hash_valid_proof() {
3133 let (circuit, instance) = make_test_data();
3134
3135 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3136 assert_eq!(prover.verify(), Ok(()));
3137 }
3138
3139 #[test]
3142 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3143 fn shares_hash_wrong_enc_share_fails() {
3144 let (mut circuit, instance) = make_test_data();
3145
3146 circuit.enc_share_c1_x[0] = Value::known(pallas::Base::random(&mut OsRng));
3149
3150 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3151 assert!(prover.verify().is_err());
3152 }
3153
3154 #[test]
3157 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3158 fn shares_hash_wrong_instance_fails() {
3159 let (circuit, mut instance) = make_test_data();
3160
3161 instance.vote_commitment = pallas::Base::random(&mut OsRng);
3163
3164 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3165 assert!(prover.verify().is_err());
3166 }
3167
3168 #[test]
3170 fn shares_hash_deterministic() {
3171 let mut rng = OsRng;
3172
3173 let blinds: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
3174 let c1_x: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
3175 let c2_x: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
3176 let c1_y: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
3177 let c2_y: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
3178
3179 let h1 = shares_hash(blinds, c1_x, c2_x, c1_y, c2_y);
3180 let h2 = shares_hash(blinds, c1_x, c2_x, c1_y, c2_y);
3181 assert_eq!(h1, h2);
3182
3183 let mut c1_x_alt = c1_x;
3185 c1_x_alt[2] = pallas::Base::random(&mut rng);
3186 let h3 = shares_hash(blinds, c1_x_alt, c2_x, c1_y, c2_y);
3187 assert_ne!(h1, h3);
3188
3189 let h4 = shares_hash(blinds, c2_x, c1_x, c2_y, c1_y);
3191 assert_ne!(h1, h4);
3192
3193 let blinds_alt: [pallas::Base; 16] =
3195 core::array::from_fn(|_| pallas::Base::random(&mut rng));
3196 let h5 = shares_hash(blinds_alt, c1_x, c2_x, c1_y, c2_y);
3197 assert_ne!(h1, h5);
3198 }
3199
3200 #[test]
3204 fn share_commitment_deterministic() {
3205 let mut rng = OsRng;
3206 let blind = pallas::Base::random(&mut rng);
3207 let c1_x = pallas::Base::random(&mut rng);
3208 let c2_x = pallas::Base::random(&mut rng);
3209 let c1_y = pallas::Base::random(&mut rng);
3210 let c2_y = pallas::Base::random(&mut rng);
3211
3212 let h1 = share_commitment(blind, c1_x, c2_x, c1_y, c2_y);
3213 let h2 = share_commitment(blind, c1_x, c2_x, c1_y, c2_y);
3214 assert_eq!(h1, h2);
3215
3216 let h3 = share_commitment(blind, c2_x, c1_x, c2_y, c1_y);
3218 assert_ne!(h1, h3);
3219
3220 let blind_alt = pallas::Base::random(&mut rng);
3222 let h4 = share_commitment(blind_alt, c1_x, c2_x, c1_y, c2_y);
3223 assert_ne!(h1, h4);
3224 }
3225
3226 #[derive(Clone, Default)]
3230 struct ShareCommitmentTestCircuit {
3231 blind: pallas::Base,
3232 c1_x: pallas::Base,
3233 c2_x: pallas::Base,
3234 c1_y: pallas::Base,
3235 c2_y: pallas::Base,
3236 }
3237
3238 #[derive(Clone)]
3239 struct ShareCommitmentTestConfig {
3240 primary: Column<InstanceColumn>,
3241 advices: [Column<Advice>; 5],
3242 poseidon_config: PoseidonConfig<pallas::Base, 3, 2>,
3243 }
3244
3245 impl plonk::Circuit<pallas::Base> for ShareCommitmentTestCircuit {
3246 type Config = ShareCommitmentTestConfig;
3247 type FloorPlanner = floor_planner::V1;
3248
3249 fn without_witnesses(&self) -> Self {
3250 Self::default()
3251 }
3252
3253 fn configure(meta: &mut ConstraintSystem<pallas::Base>) -> Self::Config {
3254 let primary = meta.instance_column();
3255 meta.enable_equality(primary);
3256 let advices: [Column<Advice>; 5] = core::array::from_fn(|_| meta.advice_column());
3257 for col in &advices {
3258 meta.enable_equality(*col);
3259 }
3260 let fixed: [Column<Fixed>; 6] = core::array::from_fn(|_| meta.fixed_column());
3261 let constants = meta.fixed_column();
3262 meta.enable_constant(constants);
3263 let rc_a = fixed[0..3].try_into().unwrap();
3264 let rc_b = fixed[3..6].try_into().unwrap();
3265 let poseidon_config = PoseidonChip::configure::<poseidon::P128Pow5T3>(
3266 meta,
3267 advices[1..4].try_into().unwrap(),
3268 advices[4],
3269 rc_a,
3270 rc_b,
3271 );
3272 ShareCommitmentTestConfig {
3273 primary,
3274 advices,
3275 poseidon_config,
3276 }
3277 }
3278
3279 fn synthesize(
3280 &self,
3281 config: Self::Config,
3282 mut layouter: impl Layouter<pallas::Base>,
3283 ) -> Result<(), plonk::Error> {
3284 let blind_cell = assign_free_advice(
3285 layouter.namespace(|| "blind"),
3286 config.advices[0],
3287 Value::known(self.blind),
3288 )?;
3289 let c1_x_cell = assign_free_advice(
3290 layouter.namespace(|| "c1_x"),
3291 config.advices[0],
3292 Value::known(self.c1_x),
3293 )?;
3294 let c2_x_cell = assign_free_advice(
3295 layouter.namespace(|| "c2_x"),
3296 config.advices[0],
3297 Value::known(self.c2_x),
3298 )?;
3299 let c1_y_cell = assign_free_advice(
3300 layouter.namespace(|| "c1_y"),
3301 config.advices[0],
3302 Value::known(self.c1_y),
3303 )?;
3304 let c2_y_cell = assign_free_advice(
3305 layouter.namespace(|| "c2_y"),
3306 config.advices[0],
3307 Value::known(self.c2_y),
3308 )?;
3309 let chip = PoseidonChip::construct(config.poseidon_config.clone());
3310 let result = hash_share_commitment_in_circuit(
3311 chip,
3312 layouter.namespace(|| "share_comm"),
3313 blind_cell,
3314 c1_x_cell,
3315 c2_x_cell,
3316 c1_y_cell,
3317 c2_y_cell,
3318 0,
3319 )?;
3320 layouter.constrain_instance(result.cell(), config.primary, 0)?;
3321 Ok(())
3322 }
3323 }
3324
3325 #[test]
3330 fn hash_share_commitment_in_circuit_matches_native() {
3331 let mut rng = OsRng;
3332 let blind = pallas::Base::random(&mut rng);
3333 let c1_x = pallas::Base::random(&mut rng);
3334 let c2_x = pallas::Base::random(&mut rng);
3335 let c1_y = pallas::Base::random(&mut rng);
3336 let c2_y = pallas::Base::random(&mut rng);
3337
3338 let expected = share_commitment(blind, c1_x, c2_x, c1_y, c2_y);
3339 let circuit = ShareCommitmentTestCircuit {
3340 blind,
3341 c1_x,
3342 c2_x,
3343 c1_y,
3344 c2_y,
3345 };
3346 let instance = vec![vec![expected]];
3347 const TEST_K: u32 = 10;
3349 let prover = MockProver::run(TEST_K, &circuit, instance).expect("MockProver::run failed");
3350 assert_eq!(prover.verify(), Ok(()));
3351 }
3352
3353 #[test]
3359 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3360 fn encryption_integrity_valid_proof() {
3361 let (circuit, instance) = make_test_data();
3362
3363 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3364 assert_eq!(prover.verify(), Ok(()));
3365 }
3366
3367 #[test]
3370 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3371 fn encryption_integrity_randomness_zero_is_rejected() {
3372 let (mut circuit, mut instance) = make_test_data();
3373 let shares_u64 = [625u64; 16];
3374 let (_ea_sk, ea_pk) = generate_ea_keypair();
3375 let (mut c1_x, mut c2_x, mut c1_y, mut c2_y, mut randomness, blinds, _) =
3376 encrypt_shares(shares_u64, ea_pk);
3377 let c2 = spend_auth_g_affine() * pallas::Scalar::from(shares_u64[0]);
3378 let c2_coords = c2.to_affine().coordinates().unwrap();
3379
3380 randomness[0] = pallas::Base::zero();
3381 c1_x[0] = pallas::Base::zero();
3382 c1_y[0] = pallas::Base::zero();
3383 c2_x[0] = *c2_coords.x();
3384 c2_y[0] = *c2_coords.y();
3385
3386 circuit.share_randomness = randomness.map(Value::known);
3387 circuit.enc_share_c1_x = c1_x.map(Value::known);
3388 circuit.enc_share_c1_y = c1_y.map(Value::known);
3389 circuit.enc_share_c2_x = c2_x.map(Value::known);
3390 circuit.enc_share_c2_y = c2_y.map(Value::known);
3391 let shares_hash_val = shares_hash(blinds, c1_x, c2_x, c1_y, c2_y);
3392 instance.vote_commitment = set_condition_11(
3393 &mut circuit,
3394 shares_hash_val,
3395 TEST_PROPOSAL_ID,
3396 instance.voting_round_id,
3397 );
3398
3399 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3400 assert!(prover.verify().is_err());
3401 }
3402
3403 #[test]
3406 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3407 fn encryption_integrity_wrong_randomness_fails() {
3408 let (mut circuit, instance) = make_test_data();
3409
3410 circuit.share_randomness[0] = Value::known(pallas::Base::from(9999u64));
3412
3413 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3414 assert!(prover.verify().is_err());
3415 }
3416
3417 #[test]
3420 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3421 fn encryption_integrity_wrong_ea_pk_instance_fails() {
3422 let (circuit, mut instance) = make_test_data();
3423
3424 instance.ea_pk_x = pallas::Base::from(12345u64);
3427
3428 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3429 assert!(prover.verify().is_err());
3430 }
3431
3432 #[test]
3435 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3436 fn encryption_integrity_wrong_share_fails() {
3437 let (mut circuit, instance) = make_test_data();
3438
3439 circuit.shares[0] = Value::known(pallas::Base::from(9999u64));
3442
3443 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3444 assert!(prover.verify().is_err());
3445 }
3446
3447 #[test]
3450 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3451 fn encryption_integrity_wrong_enc_c2_x_fails() {
3452 let (mut circuit, instance) = make_test_data();
3453
3454 circuit.enc_share_c2_x[0] = Value::known(pallas::Base::random(&mut OsRng));
3458
3459 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3460 assert!(prover.verify().is_err());
3461 }
3462
3463 #[test]
3465 fn elgamal_encrypt_deterministic() {
3466 let (_ea_sk, ea_pk) = generate_ea_keypair();
3467
3468 let v = pallas::Base::from(1000u64);
3469 let r = pallas::Base::from(42u64);
3470
3471 let (c1_a, c2_a, _, _) =
3472 elgamal_encrypt(v, r, ea_pk).expect("test encryption inputs should be valid");
3473 let (c1_b, c2_b, _, _) =
3474 elgamal_encrypt(v, r, ea_pk).expect("test encryption inputs should be valid");
3475 assert_eq!(c1_a, c1_b);
3476 assert_eq!(c2_a, c2_b);
3477
3478 let (c1_c, _, _, _) = elgamal_encrypt(v, pallas::Base::from(99u64), ea_pk)
3480 .expect("test encryption inputs should be valid");
3481 assert_ne!(c1_a, c1_c);
3482 }
3483
3484 #[test]
3487 fn base_to_scalar_accepts_elgamal_inputs() {
3488 assert!(base_to_scalar(pallas::Base::zero()).is_some());
3490 assert!(base_to_scalar(pallas::Base::from(1u64)).is_some());
3491 assert!(base_to_scalar(pallas::Base::from(1_000u64)).is_some());
3492 assert!(base_to_scalar(pallas::Base::from(404u64)).is_some()); for r in (1u64..=16).map(|i| i * 101) {
3496 assert!(
3497 base_to_scalar(pallas::Base::from(r)).is_some(),
3498 "r = {} must convert for El Gamal",
3499 r
3500 );
3501 }
3502 }
3503
3504 #[test]
3510 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3511 fn vote_commitment_integrity_valid_proof() {
3512 let (circuit, instance) = make_test_data();
3513
3514 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3515 assert_eq!(prover.verify(), Ok(()));
3516 }
3517
3518 #[test]
3521 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3522 fn vote_commitment_wrong_decision_fails() {
3523 let (mut circuit, instance) = make_test_data();
3524
3525 circuit.vote_decision = Value::known(pallas::Base::from(99u64));
3527
3528 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3529 assert!(prover.verify().is_err());
3530 }
3531
3532 #[test]
3536 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3537 fn vote_commitment_wrong_proposal_id_fails() {
3538 let (circuit, mut instance) = make_test_data();
3539
3540 instance.proposal_id = pallas::Base::from(999u64);
3542
3543 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3544 assert!(prover.verify().is_err());
3545 }
3546
3547 #[test]
3549 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3550 fn vote_commitment_wrong_instance_fails() {
3551 let (circuit, mut instance) = make_test_data();
3552
3553 instance.vote_commitment = pallas::Base::random(&mut OsRng);
3555
3556 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3557 assert!(prover.verify().is_err());
3558 }
3559
3560 #[test]
3562 fn vote_commitment_hash_deterministic() {
3563 let mut rng = OsRng;
3564
3565 let rid = pallas::Base::random(&mut rng);
3566 let sh = pallas::Base::random(&mut rng);
3567 let pid = pallas::Base::from(5u64);
3568 let dec = pallas::Base::from(1u64);
3569
3570 let h1 = vote_commitment_hash(rid, sh, pid, dec);
3571 let h2 = vote_commitment_hash(rid, sh, pid, dec);
3572 assert_eq!(h1, h2);
3573
3574 let h3 = vote_commitment_hash(rid, sh, pallas::Base::from(6u64), dec);
3576 assert_ne!(h1, h3);
3577
3578 let h4 = vote_commitment_hash(pallas::Base::from(999u64), sh, pid, dec);
3580 assert_ne!(h1, h4);
3581
3582 assert_ne!(h1, pallas::Base::zero());
3585 }
3586
3587 #[test]
3593 fn instance_has_eleven_public_inputs() {
3594 let (_, instance) = make_test_data();
3595 assert_eq!(instance.to_halo2_instance().len(), 11);
3596 }
3597
3598 #[test]
3600 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3601 fn default_circuit_with_valid_instance_fails() {
3602 let (_, instance) = make_test_data();
3603 let circuit = Circuit::default();
3604
3605 if let Ok(prover) = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]) {
3607 assert!(prover.verify().is_err());
3608 }
3609 }
3610
3611 #[test]
3620 #[ignore = "long-running row-budget diagnostic; run with `cargo test vote_proof::circuit::tests::row_budget -- --ignored --nocapture --test-threads=1`"]
3621 fn row_budget() {
3622 use halo2_proofs::dev::CircuitCost;
3623 use pasta_curves::vesta;
3624 use std::println;
3625
3626 let (circuit, _) = make_test_data();
3627
3628 let cost = CircuitCost::<vesta::Point, _>::measure(K, &circuit);
3631 let debug = format!("{cost:?}");
3632
3633 let extract = |field: &str| -> usize {
3635 let prefix = format!("{field}: ");
3636 debug
3637 .split(&prefix)
3638 .nth(1)
3639 .and_then(|s| s.split([',', ' ', '}']).next())
3640 .and_then(|n| n.parse().ok())
3641 .unwrap_or(0)
3642 };
3643
3644 let max_rows = extract("max_rows");
3645 let max_advice_rows = extract("max_advice_rows");
3646 let max_fixed_rows = extract("max_fixed_rows");
3647 let total_available = 1usize << K;
3648
3649 println!("=== vote-proof circuit row budget (K={K}) ===");
3650 println!(" max_rows (floor-planner high-water mark): {max_rows}");
3651 println!(" max_advice_rows: {max_advice_rows}");
3652 println!(" max_fixed_rows: {max_fixed_rows}");
3653 println!(" 2^K (total available rows): {total_available}");
3654 println!(
3655 " headroom: {}",
3656 total_available.saturating_sub(max_rows)
3657 );
3658 println!(
3659 " utilisation: {:.1}%",
3660 100.0 * max_rows as f64 / total_available as f64
3661 );
3662 println!();
3663 println!(" Full debug: {debug}");
3664
3665 let cost_default = CircuitCost::<vesta::Point, _>::measure(K, &Circuit::default());
3672 let debug_default = format!("{cost_default:?}");
3673 let max_rows_default = debug_default
3674 .split("max_rows: ")
3675 .nth(1)
3676 .and_then(|s| s.split([',', ' ', '}']).next())
3677 .and_then(|n| n.parse::<usize>().ok())
3678 .unwrap_or(0);
3679 if max_rows_default == max_rows {
3680 println!(
3681 " Witness-independence: PASS \
3682 (Circuit::default() max_rows={max_rows_default} == filled max_rows={max_rows})"
3683 );
3684 } else {
3685 println!(
3686 " Witness-independence: FAIL \
3687 (Circuit::default() max_rows={max_rows_default} != filled max_rows={max_rows}) \
3688 — row count depends on witness values!"
3689 );
3690 }
3691
3692 println!(" VOTE_COMM_TREE_DEPTH (circuit constant): {VOTE_COMM_TREE_DEPTH}");
3699
3700 for probe_k in 11u32..=K {
3705 let (c, inst) = make_test_data();
3706 match MockProver::run(probe_k, &c, vec![inst.to_halo2_instance()]) {
3707 Err(_) => {
3708 println!(" K={probe_k}: not enough rows (synthesizer rejected)");
3709 continue;
3710 }
3711 Ok(p) => match p.verify() {
3712 Ok(()) => {
3713 println!(" Minimum viable K: {probe_k} (2^{probe_k} = {} rows, {:.1}% headroom)",
3714 1usize << probe_k,
3715 100.0 * (1.0 - max_rows as f64 / (1usize << probe_k) as f64));
3716 break;
3717 }
3718 Err(_) => println!(" K={probe_k}: too small"),
3719 },
3720 }
3721 }
3722 }
3723}