1use std::vec::Vec;
54
55use halo2_proofs::{
56 circuit::{floor_planner, AssignedCell, Layouter, Value},
57 plonk::{self, Advice, Column, ConstraintSystem, Fixed, Instance as InstanceColumn},
58};
59use pasta_curves::{pallas, vesta};
60
61use super::authority_decrement::{AuthorityDecrementChip, AuthorityDecrementConfig};
62use crate::circuit::address_ownership::{prove_address_ownership, spend_auth_g_mul};
63use crate::circuit::elgamal::{prove_elgamal_encryptions, EaPkInstanceLoc};
64use crate::circuit::nonzero::NonZeroConfig;
65use crate::circuit::poseidon_merkle::{synthesize_poseidon_merkle_path, MerkleSwapGate};
66use crate::circuit::van_integrity;
67use crate::circuit::vote_commitment;
68use crate::domain_tags;
69pub use crate::protocol_hash::poseidon_hash_2;
70use crate::shares_hash::compute_shares_hash_in_circuit;
71#[cfg(test)]
72use crate::shares_hash::{hash_share_commitment_in_circuit, share_commitment, shares_hash};
73use halo2_gadgets::{
74 ecc::{
75 chip::{EccChip, EccConfig},
76 NonIdentityPoint, ScalarFixed,
77 },
78 poseidon::{
79 primitives::{self as poseidon, ConstantLength},
80 Hash as PoseidonHash, Pow5Chip as PoseidonChip, Pow5Config as PoseidonConfig,
81 },
82 sinsemilla::chip::{SinsemillaChip, SinsemillaConfig},
83 utilities::lookup_range_check::{LookupRangeCheck, LookupRangeCheckConfig},
84};
85use orchard::circuit::commit_ivk::{CommitIvkChip, CommitIvkConfig};
86use orchard::circuit::gadget::{
87 add_chip::{AddChip, AddConfig},
88 assign_free_advice, AddInstruction,
89};
90use orchard::constants::{OrchardCommitDomains, OrchardFixedBases, OrchardHashDomains};
91
92pub const VOTE_COMM_TREE_DEPTH: usize = 24;
105
106pub const K: u32 = 13;
124
125pub(crate) use van_integrity::DOMAIN_VAN;
126pub(crate) use vote_commitment::DOMAIN_VC;
127
128pub(super) const MAX_PROPOSAL_ID: usize = 16;
149
150pub const VAN_NULLIFIER_PUBLIC_OFFSET: usize = 0;
156pub const R_VPK_X_PUBLIC_OFFSET: usize = 1;
159pub const R_VPK_Y_PUBLIC_OFFSET: usize = 2;
161pub const VOTE_AUTHORITY_NOTE_NEW_PUBLIC_OFFSET: usize = 3;
163pub const VOTE_COMMITMENT_PUBLIC_OFFSET: usize = 4;
165pub const VOTE_COMM_TREE_ROOT_PUBLIC_OFFSET: usize = 5;
167pub const VOTE_COMM_TREE_ANCHOR_HEIGHT_PUBLIC_OFFSET: usize = 6;
175pub const PROPOSAL_ID_PUBLIC_OFFSET: usize = 7;
181pub const VOTING_ROUND_ID_PUBLIC_OFFSET: usize = 8;
187pub const EA_PK_X_PUBLIC_OFFSET: usize = 9;
189pub const EA_PK_Y_PUBLIC_OFFSET: usize = 10;
191
192pub(crate) use van_integrity::van_integrity_hash;
197pub(crate) use vote_commitment::vote_commitment_hash;
198
199pub fn domain_van_nullifier() -> pallas::Base {
204 domain_tags::vote_authority_spend()
205}
206
207pub(super) fn van_nullifier_hash(
216 vsk_nk: pallas::Base,
217 voting_round_id: pallas::Base,
218 vote_authority_note_old: pallas::Base,
219) -> pallas::Base {
220 poseidon::Hash::<_, poseidon::P128Pow5T3, ConstantLength<4>, 3, 2>::init().hash([
221 vsk_nk,
222 domain_van_nullifier(),
223 voting_round_id,
224 vote_authority_note_old,
225 ])
226}
227
228#[derive(Clone, Debug)]
240pub struct Config {
241 primary: Column<InstanceColumn>,
243 advices: [Column<Advice>; 10],
251 poseidon_config: PoseidonConfig<pallas::Base, 3, 2>,
258 add_config: AddConfig,
266 ecc_config: EccConfig<OrchardFixedBases>,
272 sinsemilla_config:
278 SinsemillaConfig<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>,
279 commit_ivk_config: CommitIvkConfig,
284 range_check: LookupRangeCheckConfig<pallas::Base, 10>,
291 merkle_swap: MerkleSwapGate,
297 authority_decrement: AuthorityDecrementConfig,
299 nonzero: NonZeroConfig,
301}
302
303impl Config {
304 pub(crate) fn poseidon_chip(&self) -> PoseidonChip<pallas::Base, 3, 2> {
309 PoseidonChip::construct(self.poseidon_config.clone())
310 }
311
312 fn add_chip(&self) -> AddChip {
314 AddChip::construct(self.add_config.clone())
315 }
316
317 fn ecc_chip(&self) -> EccChip<OrchardFixedBases> {
319 EccChip::construct(self.ecc_config.clone())
320 }
321
322 fn sinsemilla_chip(
324 &self,
325 ) -> SinsemillaChip<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases> {
326 SinsemillaChip::construct(self.sinsemilla_config.clone())
327 }
328
329 fn commit_ivk_chip(&self) -> CommitIvkChip {
331 CommitIvkChip::construct(self.commit_ivk_config.clone())
332 }
333
334 fn range_check_config(&self) -> LookupRangeCheckConfig<pallas::Base, 10> {
336 self.range_check
337 }
338}
339
340#[derive(Clone, Debug, Default)]
353pub struct Circuit {
354 pub(crate) vote_comm_tree_path: Value<[pallas::Base; VOTE_COMM_TREE_DEPTH]>,
360 pub(crate) vote_comm_tree_position: Value<u32>,
362
363 pub(crate) vpk_g_d: Value<pallas::Affine>,
376 pub(crate) vpk_pk_d: Value<pallas::Affine>,
380 pub(crate) total_note_value: Value<pallas::Base>,
383 pub(crate) proposal_authority_old: Value<pallas::Base>,
386 pub(crate) van_comm_rand: Value<pallas::Base>,
388 pub(crate) vote_authority_note_old: Value<pallas::Base>,
391
392 pub(crate) vsk: Value<pallas::Scalar>,
398 pub(crate) rivk_v: Value<pallas::Scalar>,
401 pub(crate) alpha_v: Value<pallas::Scalar>,
403
404 pub(crate) vsk_nk: Value<pallas::Base>,
408
409 pub(crate) one_shifted: Value<pallas::Base>,
418
419 pub(crate) shares: [Value<pallas::Base>; 16],
427
428 pub(crate) enc_share_c1_x: [Value<pallas::Base>; 16],
435 pub(crate) enc_share_c2_x: [Value<pallas::Base>; 16],
437 pub(crate) enc_share_c1_y: [Value<pallas::Base>; 16],
439 pub(crate) enc_share_c2_y: [Value<pallas::Base>; 16],
441
442 pub(crate) share_blinds: [Value<pallas::Base>; 16],
445
446 pub(crate) share_randomness: [Value<pallas::Base>; 16],
450 pub(crate) ea_pk: Value<pallas::Affine>,
456
457 pub(crate) vote_decision: Value<pallas::Base>,
460}
461
462impl Circuit {
463 pub fn with_van_witnesses(
480 vote_comm_tree_path: Value<[pallas::Base; VOTE_COMM_TREE_DEPTH]>,
481 vote_comm_tree_position: Value<u32>,
482 vpk_g_d: Value<pallas::Affine>,
483 vpk_pk_d: Value<pallas::Affine>,
484 total_note_value: Value<pallas::Base>,
485 proposal_authority_old: Value<pallas::Base>,
486 van_comm_rand: Value<pallas::Base>,
487 vote_authority_note_old: Value<pallas::Base>,
488 vsk: Value<pallas::Scalar>,
489 rivk_v: Value<pallas::Scalar>,
490 vsk_nk: Value<pallas::Base>,
491 alpha_v: Value<pallas::Scalar>,
492 ) -> Self {
493 Circuit {
494 vote_comm_tree_path,
495 vote_comm_tree_position,
496 vpk_g_d,
497 vpk_pk_d,
498 total_note_value,
499 proposal_authority_old,
500 van_comm_rand,
501 vote_authority_note_old,
502 vsk,
503 rivk_v,
504 alpha_v,
505 vsk_nk,
506 ..Default::default()
507 }
508 }
509}
510
511impl plonk::Circuit<pallas::Base> for Circuit {
518 type Config = Config;
519 type FloorPlanner = floor_planner::V1;
520
521 fn without_witnesses(&self) -> Self {
522 Self::default()
523 }
524
525 fn configure(meta: &mut ConstraintSystem<pallas::Base>) -> Self::Config {
526 let advices: [Column<Advice>; 10] = core::array::from_fn(|_| meta.advice_column());
542 for col in &advices {
543 meta.enable_equality(*col);
544 }
545
546 let primary = meta.instance_column();
548 meta.enable_equality(primary);
549
550 let lagrange_coeffs: [Column<Fixed>; 8] = core::array::from_fn(|_| meta.fixed_column());
555 let rc_a = lagrange_coeffs[2..5].try_into().unwrap();
556 let rc_b = lagrange_coeffs[5..8].try_into().unwrap();
557
558 let constants = meta.fixed_column();
563 meta.enable_constant(constants);
564
565 let add_config = AddChip::configure(meta, advices[7], advices[8], advices[6]);
569
570 let table_idx = meta.lookup_table_column();
575 let lookup = (
576 table_idx,
577 meta.lookup_table_column(),
578 meta.lookup_table_column(),
579 );
580
581 let range_check = LookupRangeCheckConfig::configure(meta, advices[9], table_idx);
583
584 let ecc_config =
589 EccChip::<OrchardFixedBases>::configure(meta, advices, lagrange_coeffs, range_check);
590
591 let sinsemilla_config = SinsemillaChip::configure(
596 meta,
597 advices[..5].try_into().unwrap(),
598 advices[6],
599 lagrange_coeffs[0],
600 lookup,
601 range_check,
602 false,
603 );
604
605 let commit_ivk_config = CommitIvkChip::configure(meta, advices);
608
609 let poseidon_config = PoseidonChip::configure::<poseidon::P128Pow5T3>(
614 meta,
615 advices[6..9].try_into().unwrap(),
616 advices[5],
617 rc_a,
618 rc_b,
619 );
620
621 let merkle_swap = MerkleSwapGate::configure(
623 meta,
624 [advices[0], advices[1], advices[2], advices[3], advices[4]],
625 );
626
627 let authority_decrement = AuthorityDecrementChip::configure(meta, advices);
629 let nonzero = NonZeroConfig::configure(meta, [advices[0], advices[1]]);
630
631 Config {
632 primary,
633 advices,
634 poseidon_config,
635 add_config,
636 ecc_config,
637 sinsemilla_config,
638 commit_ivk_config,
639 range_check,
640 merkle_swap,
641 authority_decrement,
642 nonzero,
643 }
644 }
645
646 #[allow(non_snake_case)]
647 fn synthesize(
648 &self,
649 config: Self::Config,
650 mut layouter: impl Layouter<pallas::Base>,
651 ) -> Result<(), plonk::Error> {
652 SinsemillaChip::load(config.sinsemilla_config.clone(), &mut layouter)?;
660
661 AuthorityDecrementChip::load_table(&config.authority_decrement, &mut layouter)?;
663
664 let ecc_chip = config.ecc_chip();
666
667 let voting_round_id = layouter.assign_region(
676 || "copy voting_round_id from instance",
677 |mut region| {
678 region.assign_advice_from_instance(
679 || "voting_round_id",
680 config.primary,
681 VOTING_ROUND_ID_PUBLIC_OFFSET,
682 config.advices[0],
683 0,
684 )
685 },
686 )?;
687 let voting_round_id_cond12 = voting_round_id.clone();
690
691 let vpk_g_d_point = NonIdentityPoint::new(
695 ecc_chip.clone(),
696 layouter.namespace(|| "witness vpk_g_d"),
697 self.vpk_g_d.map(|p| p),
698 )?;
699 let vpk_g_d = vpk_g_d_point.extract_p().inner().clone();
700
701 let vpk_pk_d_point = NonIdentityPoint::new(
704 ecc_chip.clone(),
705 layouter.namespace(|| "witness vpk_pk_d"),
706 self.vpk_pk_d.map(|p| p),
707 )?;
708 let vpk_pk_d = vpk_pk_d_point.extract_p().inner().clone();
709
710 let total_note_value = assign_free_advice(
711 layouter.namespace(|| "witness total_note_value"),
712 config.advices[0],
713 self.total_note_value,
714 )?;
715
716 let proposal_authority_old = assign_free_advice(
717 layouter.namespace(|| "witness proposal_authority_old"),
718 config.advices[0],
719 self.proposal_authority_old,
720 )?;
721
722 let van_comm_rand = assign_free_advice(
723 layouter.namespace(|| "witness van_comm_rand"),
724 config.advices[0],
725 self.van_comm_rand,
726 )?;
727
728 let vote_authority_note_old = assign_free_advice(
729 layouter.namespace(|| "witness vote_authority_note_old"),
730 config.advices[0],
731 self.vote_authority_note_old,
732 )?;
733
734 let domain_van = layouter.assign_region(
737 || "DOMAIN_VAN constant",
738 |mut region| {
739 region.assign_advice_from_constant(
740 || "domain_van",
741 config.advices[0],
742 0,
743 pallas::Base::from(DOMAIN_VAN),
744 )
745 },
746 )?;
747
748 let vsk_nk = assign_free_advice(
758 layouter.namespace(|| "witness vsk_nk"),
759 config.advices[0],
760 self.vsk_nk,
761 )?;
762
763 let vote_authority_note_old_cond1 = vote_authority_note_old.clone();
772 let voting_round_id_cond4 = voting_round_id.clone();
773 let domain_van_cond6 = domain_van.clone();
774 let vpk_g_d_cond6 = vpk_g_d.clone();
775 let vpk_pk_d_cond6 = vpk_pk_d.clone();
776 let total_note_value_cond6 = total_note_value.clone();
777 let total_note_value_cond8 = total_note_value.clone();
778 let voting_round_id_cond6 = voting_round_id.clone();
779 let van_comm_rand_cond6 = van_comm_rand.clone();
780 let vsk_nk_cond4 = vsk_nk.clone();
781
782 let derived_van = van_integrity::van_integrity_poseidon(
790 &config.poseidon_config,
791 &mut layouter,
792 "Old VAN integrity",
793 domain_van,
794 vpk_g_d,
795 vpk_pk_d,
796 total_note_value,
797 voting_round_id,
798 proposal_authority_old.clone(),
799 van_comm_rand,
800 )?;
801
802 layouter.assign_region(
804 || "VAN integrity check",
805 |mut region| region.constrain_equal(derived_van.cell(), vote_authority_note_old.cell()),
806 )?;
807
808 let vsk_scalar = ScalarFixed::new(
814 ecc_chip.clone(),
815 layouter.namespace(|| "cond3 vsk"),
816 self.vsk,
817 )?;
818 let vsk_ak_point = spend_auth_g_mul(
819 ecc_chip.clone(),
820 layouter.namespace(|| "cond3 [vsk]G"),
821 "cond3: [vsk] SpendAuthG",
822 vsk_scalar,
823 )?;
824 let ak = vsk_ak_point.extract_p().inner().clone();
825 let rivk_v_scalar = ScalarFixed::new(
826 ecc_chip.clone(),
827 layouter.namespace(|| "cond3 rivk_v"),
828 self.rivk_v,
829 )?;
830 prove_address_ownership(
831 config.sinsemilla_chip(),
832 ecc_chip.clone(),
833 config.commit_ivk_chip(),
834 layouter.namespace(|| "cond3 address"),
835 "cond3",
836 ak,
837 vsk_nk.clone(),
838 rivk_v_scalar,
839 &vpk_g_d_point,
840 &vpk_pk_d_point,
841 )?;
842
843 crate::circuit::spend_authority::prove_spend_authority(
855 ecc_chip.clone(),
856 layouter.namespace(|| "cond4 spend authority"),
857 self.alpha_v,
858 &vsk_ak_point,
859 config.primary,
860 R_VPK_X_PUBLIC_OFFSET,
861 R_VPK_Y_PUBLIC_OFFSET,
862 )?;
863
864 {
882 let root = synthesize_poseidon_merkle_path::<VOTE_COMM_TREE_DEPTH>(
883 &config.merkle_swap,
884 &config.poseidon_config,
885 &mut layouter,
886 config.advices[0],
887 vote_authority_note_old_cond1,
888 self.vote_comm_tree_position,
889 self.vote_comm_tree_path,
890 "cond1: merkle",
891 )?;
892
893 layouter.constrain_instance(
897 root.cell(),
898 config.primary,
899 VOTE_COMM_TREE_ROOT_PUBLIC_OFFSET,
900 )?;
901 }
902
903 let domain_van_nf = layouter.assign_region(
913 || "DOMAIN_VAN_NULLIFIER constant",
914 |mut region| {
915 region.assign_advice_from_constant(
916 || "domain_van_nullifier",
917 config.advices[0],
918 0,
919 domain_van_nullifier(),
920 )
921 },
922 )?;
923
924 let van_nullifier = {
936 let hasher = PoseidonHash::<
937 pallas::Base,
938 _,
939 poseidon::P128Pow5T3,
940 ConstantLength<4>,
941 3, 2, >::init(
944 config.poseidon_chip(),
945 layouter.namespace(|| "VAN nullifier Poseidon init"),
946 )?;
947 hasher.hash(
948 layouter.namespace(|| "Poseidon(vsk_nk, domain, round_id, van_old)"),
949 [
950 vsk_nk_cond4,
951 domain_van_nf,
952 voting_round_id_cond4,
953 vote_authority_note_old,
954 ],
955 )?
956 };
957
958 layouter.constrain_instance(
962 van_nullifier.cell(),
963 config.primary,
964 VAN_NULLIFIER_PUBLIC_OFFSET,
965 )?;
966
967 let proposal_id = layouter.assign_region(
979 || "copy proposal_id from instance",
980 |mut region| {
981 region.assign_advice_from_instance(
982 || "proposal_id",
983 config.primary,
984 PROPOSAL_ID_PUBLIC_OFFSET,
985 config.advices[0],
986 0,
987 )
988 },
989 )?;
990
991 let proposal_authority_new = AuthorityDecrementChip::assign(
992 &config.authority_decrement,
993 &mut layouter,
994 proposal_id.clone(),
995 proposal_authority_old,
996 self.one_shifted,
997 )?;
998
999 let derived_van_new = van_integrity::van_integrity_poseidon(
1008 &config.poseidon_config,
1009 &mut layouter,
1010 "New VAN integrity",
1011 domain_van_cond6,
1012 vpk_g_d_cond6,
1013 vpk_pk_d_cond6,
1014 total_note_value_cond6,
1015 voting_round_id_cond6,
1016 proposal_authority_new,
1017 van_comm_rand_cond6,
1018 )?;
1019
1020 layouter.constrain_instance(
1024 derived_van_new.cell(),
1025 config.primary,
1026 VOTE_AUTHORITY_NOTE_NEW_PUBLIC_OFFSET,
1027 )?;
1028
1029 let share_cells: [_; 16] = (0..16usize)
1047 .map(|i| {
1048 assign_free_advice(
1049 layouter.namespace(|| format!("witness share_{i}")),
1050 config.advices[0],
1051 self.shares[i],
1052 )
1053 })
1054 .collect::<Result<Vec<_>, _>>()?
1055 .try_into()
1056 .expect("always 16 elements");
1057
1058 let shares_sum = share_cells[1..].iter().enumerate().try_fold(
1060 share_cells[0].clone(),
1061 |acc, (i, share)| {
1062 config.add_chip().add(
1063 layouter.namespace(|| format!("shares sum step {}", i + 1)),
1064 &acc,
1065 share,
1066 )
1067 },
1068 )?;
1069
1070 layouter.assign_region(
1074 || "shares sum == total_note_value",
1075 |mut region| region.constrain_equal(shares_sum.cell(), total_note_value_cond8.cell()),
1076 )?;
1077
1078 for (i, cell) in share_cells.iter().enumerate() {
1114 config.range_check_config().copy_check(
1115 layouter.namespace(|| format!("share_{i} < 2^30")),
1116 cell.clone(),
1117 3, true, )?;
1120 }
1121
1122 let blinds: [AssignedCell<pallas::Base, pallas::Base>; 16] = (0..16)
1140 .map(|i| {
1141 assign_free_advice(
1142 layouter.namespace(|| format!("witness share_blind[{i}]")),
1143 config.advices[0],
1144 self.share_blinds[i],
1145 )
1146 })
1147 .collect::<Result<Vec<_>, _>>()?
1148 .try_into()
1149 .expect("always 16 elements");
1150
1151 let enc_c1: [AssignedCell<pallas::Base, pallas::Base>; 16] = (0..16)
1152 .map(|i| {
1153 assign_free_advice(
1154 layouter.namespace(|| format!("witness enc_c1_x[{i}]")),
1155 config.advices[0],
1156 self.enc_share_c1_x[i],
1157 )
1158 })
1159 .collect::<Result<Vec<_>, _>>()?
1160 .try_into()
1161 .expect("always 16 elements");
1162
1163 let enc_c2: [AssignedCell<pallas::Base, pallas::Base>; 16] = (0..16)
1164 .map(|i| {
1165 assign_free_advice(
1166 layouter.namespace(|| format!("witness enc_c2_x[{i}]")),
1167 config.advices[0],
1168 self.enc_share_c2_x[i],
1169 )
1170 })
1171 .collect::<Result<Vec<_>, _>>()?
1172 .try_into()
1173 .expect("always 16 elements");
1174
1175 let enc_c1_y: [AssignedCell<pallas::Base, pallas::Base>; 16] = (0..16)
1176 .map(|i| {
1177 assign_free_advice(
1178 layouter.namespace(|| format!("witness enc_c1_y[{i}]")),
1179 config.advices[0],
1180 self.enc_share_c1_y[i],
1181 )
1182 })
1183 .collect::<Result<Vec<_>, _>>()?
1184 .try_into()
1185 .expect("always 16 elements");
1186
1187 let enc_c2_y: [AssignedCell<pallas::Base, pallas::Base>; 16] = (0..16)
1188 .map(|i| {
1189 assign_free_advice(
1190 layouter.namespace(|| format!("witness enc_c2_y[{i}]")),
1191 config.advices[0],
1192 self.enc_share_c2_y[i],
1193 )
1194 })
1195 .collect::<Result<Vec<_>, _>>()?
1196 .try_into()
1197 .expect("always 16 elements");
1198
1199 let enc_c1_cond11: [AssignedCell<pallas::Base, pallas::Base>; 16] =
1201 core::array::from_fn(|i| enc_c1[i].clone());
1202 let enc_c2_cond11: [AssignedCell<pallas::Base, pallas::Base>; 16] =
1203 core::array::from_fn(|i| enc_c2[i].clone());
1204 let enc_c1_y_cond11: [AssignedCell<pallas::Base, pallas::Base>; 16] =
1205 core::array::from_fn(|i| enc_c1_y[i].clone());
1206 let enc_c2_y_cond11: [AssignedCell<pallas::Base, pallas::Base>; 16] =
1207 core::array::from_fn(|i| enc_c2_y[i].clone());
1208
1209 let shares_hash = compute_shares_hash_in_circuit(
1210 || config.poseidon_chip(),
1211 layouter.namespace(|| "cond10: shares hash"),
1212 blinds,
1213 enc_c1,
1214 enc_c2,
1215 enc_c1_y,
1216 enc_c2_y,
1217 )?;
1218
1219 {
1228 let r_cells: [_; 16] = (0..16usize)
1229 .map(|i| {
1230 assign_free_advice(
1231 layouter.namespace(|| format!("witness r[{i}]")),
1232 config.advices[0],
1233 self.share_randomness[i],
1234 )
1235 })
1236 .collect::<Result<Vec<_>, _>>()?
1237 .try_into()
1238 .expect("always 16 elements");
1239
1240 prove_elgamal_encryptions(
1241 ecc_chip.clone(),
1242 config.nonzero,
1243 layouter.namespace(|| "cond11 El Gamal"),
1244 "cond11",
1245 self.ea_pk,
1246 EaPkInstanceLoc {
1247 instance: config.primary,
1248 x_row: EA_PK_X_PUBLIC_OFFSET,
1249 y_row: EA_PK_Y_PUBLIC_OFFSET,
1250 },
1251 config.advices[0],
1252 share_cells,
1253 r_cells,
1254 enc_c1_cond11,
1255 enc_c2_cond11,
1256 enc_c1_y_cond11,
1257 enc_c2_y_cond11,
1258 )?;
1259 }
1260
1261 let domain_vc = layouter.assign_region(
1279 || "DOMAIN_VC constant",
1280 |mut region| {
1281 region.assign_advice_from_constant(
1282 || "domain_vc",
1283 config.advices[0],
1284 0,
1285 pallas::Base::from(DOMAIN_VC),
1286 )
1287 },
1288 )?;
1289
1290 let vote_decision = assign_free_advice(
1294 layouter.namespace(|| "witness vote_decision"),
1295 config.advices[0],
1296 self.vote_decision,
1297 )?;
1298
1299 let vote_commitment = vote_commitment::vote_commitment_poseidon(
1302 &config.poseidon_config,
1303 &mut layouter,
1304 "cond12",
1305 domain_vc,
1306 voting_round_id_cond12,
1307 shares_hash,
1308 proposal_id,
1309 vote_decision,
1310 )?;
1311
1312 layouter.constrain_instance(
1314 vote_commitment.cell(),
1315 config.primary,
1316 VOTE_COMMITMENT_PUBLIC_OFFSET,
1317 )?;
1318
1319 Ok(())
1320 }
1321}
1322
1323#[derive(Clone, Debug)]
1343pub struct Instance {
1344 pub van_nullifier: pallas::Base,
1346 pub r_vpk_x: pallas::Base,
1348 pub r_vpk_y: pallas::Base,
1350 pub vote_authority_note_new: pallas::Base,
1352 pub vote_commitment: pallas::Base,
1354 pub vote_comm_tree_root: pallas::Base,
1356 pub vote_comm_tree_anchor_height: pallas::Base,
1362 pub proposal_id: pallas::Base,
1368 pub voting_round_id: pallas::Base,
1373 pub ea_pk_x: pallas::Base,
1379 pub ea_pk_y: pallas::Base,
1384}
1385
1386impl Instance {
1387 pub const NUM_PUBLIC_INPUTS: usize = 11;
1389
1390 pub fn from_parts(
1404 van_nullifier: pallas::Base,
1405 r_vpk_x: pallas::Base,
1406 r_vpk_y: pallas::Base,
1407 vote_authority_note_new: pallas::Base,
1408 vote_commitment: pallas::Base,
1409 vote_comm_tree_root: pallas::Base,
1410 vote_comm_tree_anchor_height: pallas::Base,
1411 proposal_id: pallas::Base,
1412 voting_round_id: pallas::Base,
1413 ea_pk_x: pallas::Base,
1414 ea_pk_y: pallas::Base,
1415 ) -> Self {
1416 Instance {
1417 van_nullifier,
1418 r_vpk_x,
1419 r_vpk_y,
1420 vote_authority_note_new,
1421 vote_commitment,
1422 vote_comm_tree_root,
1423 vote_comm_tree_anchor_height,
1424 proposal_id,
1425 voting_round_id,
1426 ea_pk_x,
1427 ea_pk_y,
1428 }
1429 }
1430
1431 pub fn to_halo2_instance(&self) -> Vec<vesta::Scalar> {
1437 vec![
1438 self.van_nullifier,
1439 self.r_vpk_x,
1440 self.r_vpk_y,
1441 self.vote_authority_note_new,
1442 self.vote_commitment,
1443 self.vote_comm_tree_root,
1444 self.vote_comm_tree_anchor_height,
1445 self.proposal_id,
1446 self.voting_round_id,
1447 self.ea_pk_x,
1448 self.ea_pk_y,
1449 ]
1450 }
1451}
1452
1453#[cfg(test)]
1458mod tests {
1459 use super::*;
1460 use crate::circuit::elgamal::{base_to_scalar, elgamal_encrypt, spend_auth_g_affine};
1461 use core::iter;
1462 use ff::{Field, PrimeField};
1463 use group::ff::PrimeFieldBits;
1464 use group::{Curve, Group};
1465 use halo2_gadgets::sinsemilla::primitives::CommitDomain;
1466 use halo2_proofs::dev::MockProver;
1467 use pasta_curves::arithmetic::CurveAffine;
1468 use pasta_curves::pallas;
1469 use rand::rngs::OsRng;
1470
1471 use orchard::constants::{fixed_bases::COMMIT_IVK_PERSONALIZATION, L_ORCHARD_BASE};
1472
1473 fn generate_ea_keypair() -> (pallas::Scalar, pallas::Point, pallas::Affine) {
1476 let ea_sk = pallas::Scalar::from(42u64);
1477 let g = pallas::Point::from(spend_auth_g_affine());
1478 let ea_pk = g * ea_sk;
1479 let ea_pk_affine = ea_pk.to_affine();
1480 (ea_sk, ea_pk, ea_pk_affine)
1481 }
1482
1483 fn encrypt_shares(
1492 shares: [u64; 16],
1493 ea_pk: pallas::Point,
1494 ) -> (
1495 [pallas::Base; 16],
1496 [pallas::Base; 16],
1497 [pallas::Base; 16],
1498 [pallas::Base; 16],
1499 [pallas::Base; 16],
1500 [pallas::Base; 16],
1501 pallas::Base,
1502 ) {
1503 let mut c1_x = [pallas::Base::zero(); 16];
1504 let mut c2_x = [pallas::Base::zero(); 16];
1505 let mut c1_y = [pallas::Base::zero(); 16];
1506 let mut c2_y = [pallas::Base::zero(); 16];
1507 let randomness: [pallas::Base; 16] =
1509 core::array::from_fn(|i| pallas::Base::from((i as u64 + 1) * 101));
1510 let share_blinds: [pallas::Base; 16] =
1512 core::array::from_fn(|i| pallas::Base::from(1001u64 + i as u64));
1513 for i in 0..16 {
1514 let (cx1, cx2, cy1, cy2) =
1515 elgamal_encrypt(pallas::Base::from(shares[i]), randomness[i], ea_pk)
1516 .expect("test encryption inputs should be valid");
1517 c1_x[i] = cx1;
1518 c2_x[i] = cx2;
1519 c1_y[i] = cy1;
1520 c2_y[i] = cy2;
1521 }
1522 let hash = shares_hash(share_blinds, c1_x, c2_x, c1_y, c2_y);
1523 (c1_x, c2_x, c1_y, c2_y, randomness, share_blinds, hash)
1524 }
1525
1526 fn derive_voting_address(
1539 vsk: pallas::Scalar,
1540 nk: pallas::Base,
1541 rivk_v: pallas::Scalar,
1542 ) -> (pallas::Affine, pallas::Affine) {
1543 let g = pallas::Point::from(spend_auth_g_affine());
1545 let ak_point = g * vsk;
1546 let ak_x = *ak_point.to_affine().coordinates().unwrap().x();
1547
1548 let domain = CommitDomain::new(COMMIT_IVK_PERSONALIZATION);
1550 let ivk_v = domain
1551 .short_commit(
1552 iter::empty()
1553 .chain(ak_x.to_le_bits().iter().by_vals().take(L_ORCHARD_BASE))
1554 .chain(nk.to_le_bits().iter().by_vals().take(L_ORCHARD_BASE)),
1555 &rivk_v,
1556 )
1557 .expect("CommitIvk should not produce ⊥ for random inputs");
1558
1559 let g_d = pallas::Point::generator() * pallas::Scalar::from(12345u64);
1563 let g_d_affine = g_d.to_affine();
1564
1565 let ivk_v_scalar = base_to_scalar(ivk_v).expect("ivk_v must be < scalar field modulus");
1567 let pk_d = g_d * ivk_v_scalar;
1568 let pk_d_affine = pk_d.to_affine();
1569
1570 (g_d_affine, pk_d_affine)
1571 }
1572
1573 const TEST_PROPOSAL_ID: u64 = 3;
1575 const TEST_VOTE_DECISION: u64 = 1;
1576
1577 fn set_condition_11(
1585 circuit: &mut Circuit,
1586 shares_hash_val: pallas::Base,
1587 proposal_id: u64,
1588 voting_round_id: pallas::Base,
1589 ) -> pallas::Base {
1590 let proposal_id_base = pallas::Base::from(proposal_id);
1591 let vote_decision = pallas::Base::from(TEST_VOTE_DECISION);
1592 circuit.vote_decision = Value::known(vote_decision);
1593 vote_commitment_hash(
1594 voting_round_id,
1595 shares_hash_val,
1596 proposal_id_base,
1597 vote_decision,
1598 )
1599 }
1600
1601 fn build_single_leaf_merkle_path(
1606 leaf: pallas::Base,
1607 ) -> ([pallas::Base; VOTE_COMM_TREE_DEPTH], u32, pallas::Base) {
1608 let auth_path = empty_vote_comm_tree_path();
1609 let mut current = leaf;
1610 for i in 0..VOTE_COMM_TREE_DEPTH {
1611 current = poseidon_hash_2(current, auth_path[i]);
1612 }
1613 (auth_path, 0, current)
1614 }
1615
1616 fn empty_vote_comm_tree_path() -> [pallas::Base; VOTE_COMM_TREE_DEPTH] {
1617 let mut empty_roots = [pallas::Base::zero(); VOTE_COMM_TREE_DEPTH];
1618 empty_roots[0] = poseidon_hash_2(pallas::Base::zero(), pallas::Base::zero());
1619 for i in 1..VOTE_COMM_TREE_DEPTH {
1620 empty_roots[i] = poseidon_hash_2(empty_roots[i - 1], empty_roots[i - 1]);
1621 }
1622 empty_roots
1623 }
1624
1625 fn build_left_leaf_merkle_path_with_sibling(
1626 left_leaf: pallas::Base,
1627 right_leaf: pallas::Base,
1628 ) -> ([pallas::Base; VOTE_COMM_TREE_DEPTH], u32, pallas::Base) {
1629 let mut auth_path = empty_vote_comm_tree_path();
1630 auth_path[0] = right_leaf;
1631
1632 let mut current = left_leaf;
1633 for i in 0..VOTE_COMM_TREE_DEPTH {
1634 current = poseidon_hash_2(current, auth_path[i]);
1635 }
1636 (auth_path, 0, current)
1637 }
1638
1639 struct VoteReuseFixture {
1640 vsk: pallas::Scalar,
1641 vsk_nk: pallas::Base,
1642 rivk_v: pallas::Scalar,
1643 alpha_v: pallas::Scalar,
1644 vpk_g_d_affine: pallas::Affine,
1645 vpk_pk_d_affine: pallas::Affine,
1646 total_note_value: pallas::Base,
1647 proposal_authority_old: pallas::Base,
1648 proposal_id: u64,
1649 van_comm_rand: pallas::Base,
1650 shares_u64: [u64; 16],
1651 ea_pk_point: pallas::Point,
1652 ea_pk_affine: pallas::Affine,
1653 }
1654
1655 impl VoteReuseFixture {
1656 fn new() -> Self {
1657 let mut rng = OsRng;
1658 let vsk = pallas::Scalar::random(&mut rng);
1659 let vsk_nk = pallas::Base::random(&mut rng);
1660 let rivk_v = pallas::Scalar::random(&mut rng);
1661 let alpha_v = pallas::Scalar::random(&mut rng);
1662 let (vpk_g_d_affine, vpk_pk_d_affine) = derive_voting_address(vsk, vsk_nk, rivk_v);
1663 let (_ea_sk, ea_pk_point, ea_pk_affine) = generate_ea_keypair();
1664
1665 Self {
1666 vsk,
1667 vsk_nk,
1668 rivk_v,
1669 alpha_v,
1670 vpk_g_d_affine,
1671 vpk_pk_d_affine,
1672 total_note_value: pallas::Base::from(10_000u64),
1673 proposal_authority_old: pallas::Base::from(13u64),
1674 proposal_id: TEST_PROPOSAL_ID,
1675 van_comm_rand: pallas::Base::random(&mut rng),
1676 shares_u64: [625; 16],
1677 ea_pk_point,
1678 ea_pk_affine,
1679 }
1680 }
1681
1682 fn vpk_x_coordinates(&self) -> (pallas::Base, pallas::Base) {
1683 (
1684 *self.vpk_g_d_affine.coordinates().unwrap().x(),
1685 *self.vpk_pk_d_affine.coordinates().unwrap().x(),
1686 )
1687 }
1688
1689 fn vote_authority_note_old(&self, voting_round_id: pallas::Base) -> pallas::Base {
1690 let (vpk_g_d_x, vpk_pk_d_x) = self.vpk_x_coordinates();
1691 van_integrity_hash(
1692 vpk_g_d_x,
1693 vpk_pk_d_x,
1694 self.total_note_value,
1695 voting_round_id,
1696 self.proposal_authority_old,
1697 self.van_comm_rand,
1698 )
1699 }
1700
1701 fn vote_authority_note_new(&self, voting_round_id: pallas::Base) -> pallas::Base {
1702 let (vpk_g_d_x, vpk_pk_d_x) = self.vpk_x_coordinates();
1703 let proposal_authority_new =
1704 self.proposal_authority_old - pallas::Base::from(1u64 << self.proposal_id);
1705 van_integrity_hash(
1706 vpk_g_d_x,
1707 vpk_pk_d_x,
1708 self.total_note_value,
1709 voting_round_id,
1710 proposal_authority_new,
1711 self.van_comm_rand,
1712 )
1713 }
1714
1715 fn build_vote_data(
1716 &self,
1717 voting_round_id: pallas::Base,
1718 auth_path: [pallas::Base; VOTE_COMM_TREE_DEPTH],
1719 position: u32,
1720 vote_comm_tree_root: pallas::Base,
1721 anchor_height: u64,
1722 ) -> (Circuit, Instance) {
1723 let vote_authority_note_old = self.vote_authority_note_old(voting_round_id);
1724 let vote_authority_note_new = self.vote_authority_note_new(voting_round_id);
1725 let van_nullifier =
1726 van_nullifier_hash(self.vsk_nk, voting_round_id, vote_authority_note_old);
1727
1728 let g = pallas::Point::from(spend_auth_g_affine());
1729 let r_vpk = (g * self.vsk + g * self.alpha_v).to_affine();
1730 let r_vpk_x = *r_vpk.coordinates().unwrap().x();
1731 let r_vpk_y = *r_vpk.coordinates().unwrap().y();
1732
1733 let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
1734 encrypt_shares(self.shares_u64, self.ea_pk_point);
1735
1736 let mut circuit = Circuit::with_van_witnesses(
1737 Value::known(auth_path),
1738 Value::known(position),
1739 Value::known(self.vpk_g_d_affine),
1740 Value::known(self.vpk_pk_d_affine),
1741 Value::known(self.total_note_value),
1742 Value::known(self.proposal_authority_old),
1743 Value::known(self.van_comm_rand),
1744 Value::known(vote_authority_note_old),
1745 Value::known(self.vsk),
1746 Value::known(self.rivk_v),
1747 Value::known(self.vsk_nk),
1748 Value::known(self.alpha_v),
1749 );
1750 circuit.one_shifted = Value::known(pallas::Base::from(1u64 << self.proposal_id));
1751 circuit.shares = self.shares_u64.map(|s| Value::known(pallas::Base::from(s)));
1752 circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
1753 circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
1754 circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
1755 circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
1756 circuit.share_blinds = share_blinds.map(Value::known);
1757 circuit.share_randomness = randomness.map(Value::known);
1758 circuit.ea_pk = Value::known(self.ea_pk_affine);
1759 let vote_commitment = set_condition_11(
1760 &mut circuit,
1761 shares_hash_val,
1762 self.proposal_id,
1763 voting_round_id,
1764 );
1765
1766 let instance = Instance::from_parts(
1767 van_nullifier,
1768 r_vpk_x,
1769 r_vpk_y,
1770 vote_authority_note_new,
1771 vote_commitment,
1772 vote_comm_tree_root,
1773 pallas::Base::from(anchor_height),
1774 pallas::Base::from(self.proposal_id),
1775 voting_round_id,
1776 *self.ea_pk_affine.coordinates().unwrap().x(),
1777 *self.ea_pk_affine.coordinates().unwrap().y(),
1778 );
1779
1780 (circuit, instance)
1781 }
1782 }
1783
1784 fn make_test_data_with_authority_proposal_and_alpha(
1788 proposal_authority_old: pallas::Base,
1789 proposal_id: u64,
1790 alpha_v_override: Option<pallas::Scalar>,
1791 ) -> (Circuit, Instance) {
1792 let mut rng = OsRng;
1793
1794 let vsk = pallas::Scalar::random(&mut rng);
1797 let vsk_nk = pallas::Base::random(&mut rng);
1798 let rivk_v = pallas::Scalar::random(&mut rng);
1799 let alpha_v = alpha_v_override.unwrap_or_else(|| pallas::Scalar::random(&mut rng));
1800
1801 let (vpk_g_d_affine, vpk_pk_d_affine) = derive_voting_address(vsk, vsk_nk, rivk_v);
1802
1803 let g = pallas::Point::from(spend_auth_g_affine());
1805 let ak_point = g * vsk;
1806 let r_vpk = (ak_point + g * alpha_v).to_affine();
1807 let r_vpk_x = *r_vpk.coordinates().unwrap().x();
1808 let r_vpk_y = *r_vpk.coordinates().unwrap().y();
1809
1810 let vpk_g_d_x = *vpk_g_d_affine.coordinates().unwrap().x();
1812 let vpk_pk_d_x = *vpk_pk_d_affine.coordinates().unwrap().x();
1813
1814 let total_note_value = pallas::Base::from(10_000u64);
1817 let voting_round_id = pallas::Base::random(&mut rng);
1818 let van_comm_rand = pallas::Base::random(&mut rng);
1819
1820 let vote_authority_note_old = van_integrity_hash(
1821 vpk_g_d_x,
1822 vpk_pk_d_x,
1823 total_note_value,
1824 voting_round_id,
1825 proposal_authority_old,
1826 van_comm_rand,
1827 );
1828 let (auth_path, position, vote_comm_tree_root) =
1829 build_single_leaf_merkle_path(vote_authority_note_old);
1830 let van_nullifier = van_nullifier_hash(vsk_nk, voting_round_id, vote_authority_note_old);
1831 let one_shifted = pallas::Base::from(1u64 << proposal_id);
1833 let proposal_authority_new = proposal_authority_old - one_shifted;
1834 let vote_authority_note_new = van_integrity_hash(
1835 vpk_g_d_x,
1836 vpk_pk_d_x,
1837 total_note_value,
1838 voting_round_id,
1839 proposal_authority_new,
1840 van_comm_rand,
1841 );
1842
1843 let shares_u64: [u64; 16] = [625; 16]; let (_ea_sk, ea_pk_point, ea_pk_affine) = generate_ea_keypair();
1849 let ea_pk_x = *ea_pk_affine.coordinates().unwrap().x();
1850 let ea_pk_y = *ea_pk_affine.coordinates().unwrap().y();
1851 let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
1852 encrypt_shares(shares_u64, ea_pk_point);
1853
1854 let mut circuit = Circuit::with_van_witnesses(
1855 Value::known(auth_path),
1856 Value::known(position),
1857 Value::known(vpk_g_d_affine),
1858 Value::known(vpk_pk_d_affine),
1859 Value::known(total_note_value),
1860 Value::known(proposal_authority_old),
1861 Value::known(van_comm_rand),
1862 Value::known(vote_authority_note_old),
1863 Value::known(vsk),
1864 Value::known(rivk_v),
1865 Value::known(vsk_nk),
1866 Value::known(alpha_v),
1867 );
1868 circuit.one_shifted = Value::known(one_shifted);
1869 circuit.shares = shares_u64.map(|s| Value::known(pallas::Base::from(s)));
1870 circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
1871 circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
1872 circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
1873 circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
1874 circuit.share_blinds = share_blinds.map(Value::known);
1875 circuit.share_randomness = randomness.map(Value::known);
1876 circuit.ea_pk = Value::known(ea_pk_affine);
1877
1878 let vote_commitment =
1880 set_condition_11(&mut circuit, shares_hash_val, proposal_id, voting_round_id);
1881
1882 let instance = Instance::from_parts(
1883 van_nullifier,
1884 r_vpk_x,
1885 r_vpk_y,
1886 vote_authority_note_new,
1887 vote_commitment,
1888 vote_comm_tree_root,
1889 pallas::Base::zero(),
1890 pallas::Base::from(proposal_id),
1891 voting_round_id,
1892 ea_pk_x,
1893 ea_pk_y,
1894 );
1895
1896 (circuit, instance)
1897 }
1898
1899 fn make_test_data_with_authority_and_proposal(
1900 proposal_authority_old: pallas::Base,
1901 proposal_id: u64,
1902 ) -> (Circuit, Instance) {
1903 make_test_data_with_authority_proposal_and_alpha(proposal_authority_old, proposal_id, None)
1904 }
1905
1906 fn make_test_data_with_authority(proposal_authority_old: pallas::Base) -> (Circuit, Instance) {
1907 make_test_data_with_authority_and_proposal(proposal_authority_old, TEST_PROPOSAL_ID)
1908 }
1909
1910 fn make_test_data() -> (Circuit, Instance) {
1911 make_test_data_with_authority(pallas::Base::from(13u64))
1914 }
1915
1916 #[test]
1921 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
1922 fn van_integrity_valid_proof() {
1923 let (circuit, instance) = make_test_data();
1924
1925 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
1926
1927 assert_eq!(prover.verify(), Ok(()));
1928 }
1929
1930 #[test]
1931 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
1932 fn van_integrity_wrong_hash_fails() {
1933 let mut rng = OsRng;
1934 let (_, mut instance) = make_test_data();
1935
1936 let wrong_van = pallas::Base::random(&mut rng);
1938 let (auth_path, position, root) = build_single_leaf_merkle_path(wrong_van);
1939 instance.vote_comm_tree_root = root;
1940
1941 let vsk = pallas::Scalar::random(&mut rng);
1944 let vsk_nk = pallas::Base::random(&mut rng);
1945 let rivk_v = pallas::Scalar::random(&mut rng);
1946 let alpha_v = pallas::Scalar::random(&mut rng);
1947 let (vpk_g_d_affine, vpk_pk_d_affine) = derive_voting_address(vsk, vsk_nk, rivk_v);
1948 let g = pallas::Point::from(spend_auth_g_affine());
1949 let r_vpk = (g * vsk + g * alpha_v).to_affine();
1950 instance.r_vpk_x = *r_vpk.coordinates().unwrap().x();
1951 instance.r_vpk_y = *r_vpk.coordinates().unwrap().y();
1952
1953 let shares_u64: [u64; 16] = [625; 16];
1954 let (_ea_sk, ea_pk_point, ea_pk_affine) = generate_ea_keypair();
1955 let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
1956 encrypt_shares(shares_u64, ea_pk_point);
1957
1958 let proposal_authority_old = pallas::Base::from(13u64);
1961 let van_comm_rand = pallas::Base::random(&mut rng);
1962 let mut circuit = Circuit::with_van_witnesses(
1963 Value::known(auth_path),
1964 Value::known(position),
1965 Value::known(vpk_g_d_affine),
1966 Value::known(vpk_pk_d_affine),
1967 Value::known(pallas::Base::from(10_000u64)),
1968 Value::known(proposal_authority_old),
1969 Value::known(van_comm_rand),
1970 Value::known(wrong_van),
1971 Value::known(vsk),
1972 Value::known(rivk_v),
1973 Value::known(vsk_nk),
1974 Value::known(alpha_v),
1975 );
1976 circuit.one_shifted = Value::known(pallas::Base::from(1u64 << TEST_PROPOSAL_ID));
1977 circuit.shares = shares_u64.map(|s| Value::known(pallas::Base::from(s)));
1978 circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
1979 circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
1980 circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
1981 circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
1982 circuit.share_blinds = share_blinds.map(Value::known);
1983 circuit.share_randomness = randomness.map(Value::known);
1984 circuit.ea_pk = Value::known(ea_pk_affine);
1985 let vc = set_condition_11(
1986 &mut circuit,
1987 shares_hash_val,
1988 TEST_PROPOSAL_ID,
1989 instance.voting_round_id,
1990 );
1991 instance.vote_commitment = vc;
1992 instance.proposal_id = pallas::Base::from(TEST_PROPOSAL_ID);
1993 instance.ea_pk_x = *ea_pk_affine.coordinates().unwrap().x();
1994 instance.ea_pk_y = *ea_pk_affine.coordinates().unwrap().y();
1995
1996 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
1997 assert!(prover.verify().is_err());
1999 }
2000
2001 #[test]
2002 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2003 fn van_integrity_wrong_round_id_fails() {
2004 let (circuit, mut instance) = make_test_data();
2005
2006 instance.voting_round_id = pallas::Base::random(&mut OsRng);
2008
2009 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2010 assert!(prover.verify().is_err());
2013 }
2014
2015 #[test]
2016 fn round_scoped_van_redelegation_changes_nullifier() {
2017 let fixture = VoteReuseFixture::new();
2018 let round_1 = pallas::Base::from(0xCAFEu64);
2019 let round_2 = pallas::Base::from(0xCAFFu64);
2020
2021 let van_round_1 = fixture.vote_authority_note_old(round_1);
2022 let van_round_2 = fixture.vote_authority_note_old(round_2);
2023 assert_ne!(
2024 van_round_1, van_round_2,
2025 "voting_round_id is part of the VAN preimage"
2026 );
2027
2028 let nullifier_round_1 = van_nullifier_hash(fixture.vsk_nk, round_1, van_round_1);
2029 let nullifier_round_2 = van_nullifier_hash(fixture.vsk_nk, round_2, van_round_2);
2030 assert_ne!(
2031 nullifier_round_1, nullifier_round_2,
2032 "honest redelegation in a new round must not collide with the old round"
2033 );
2034 }
2035
2036 #[test]
2037 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2038 fn round_scoped_van_redelegation_verifies_with_distinct_nullifiers() {
2039 let fixture = VoteReuseFixture::new();
2040 let round_1 = pallas::Base::from(0xCAFEu64);
2041 let round_2 = pallas::Base::from(0xCAFFu64);
2042
2043 let van_round_1 = fixture.vote_authority_note_old(round_1);
2044 let (path_round_1, position_round_1, root_round_1) =
2045 build_single_leaf_merkle_path(van_round_1);
2046 let (circuit_round_1, instance_round_1) =
2047 fixture.build_vote_data(round_1, path_round_1, position_round_1, root_round_1, 10);
2048
2049 let van_round_2 = fixture.vote_authority_note_old(round_2);
2050 let (path_round_2, position_round_2, root_round_2) =
2051 build_single_leaf_merkle_path(van_round_2);
2052 let (circuit_round_2, instance_round_2) =
2053 fixture.build_vote_data(round_2, path_round_2, position_round_2, root_round_2, 20);
2054
2055 assert_ne!(van_round_1, van_round_2);
2056 assert_ne!(
2057 instance_round_1.van_nullifier,
2058 instance_round_2.van_nullifier
2059 );
2060
2061 let prover_round_1 = MockProver::run(
2062 K,
2063 &circuit_round_1,
2064 vec![instance_round_1.to_halo2_instance()],
2065 )
2066 .unwrap();
2067 assert_eq!(prover_round_1.verify(), Ok(()));
2068
2069 let prover_round_2 = MockProver::run(
2070 K,
2071 &circuit_round_2,
2072 vec![instance_round_2.to_halo2_instance()],
2073 )
2074 .unwrap();
2075 assert_eq!(prover_round_2.verify(), Ok(()));
2076 }
2077
2078 #[test]
2080 fn van_integrity_hash_deterministic() {
2081 let mut rng = OsRng;
2082
2083 let vpk_g_d = pallas::Base::random(&mut rng);
2084 let vpk_pk_d = pallas::Base::random(&mut rng);
2085 let val = pallas::Base::random(&mut rng);
2086 let round = pallas::Base::random(&mut rng);
2087 let auth = pallas::Base::random(&mut rng);
2088 let rand = pallas::Base::random(&mut rng);
2089
2090 let h1 = van_integrity_hash(vpk_g_d, vpk_pk_d, val, round, auth, rand);
2091 let h2 = van_integrity_hash(vpk_g_d, vpk_pk_d, val, round, auth, rand);
2092 assert_eq!(h1, h2);
2093
2094 let h3 = van_integrity_hash(
2096 pallas::Base::random(&mut rng),
2097 vpk_pk_d,
2098 val,
2099 round,
2100 auth,
2101 rand,
2102 );
2103 assert_ne!(h1, h3);
2104 }
2105
2106 #[test]
2118 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2119 fn condition_3_wrong_vsk_fails() {
2120 let mut rng = OsRng;
2121
2122 let vsk = pallas::Scalar::random(&mut rng);
2123 let vsk_nk = pallas::Base::random(&mut rng);
2124 let rivk_v = pallas::Scalar::random(&mut rng);
2125 let (vpk_g_d_affine, vpk_pk_d_affine) = derive_voting_address(vsk, vsk_nk, rivk_v);
2126 let vpk_g_d_x = *vpk_g_d_affine.coordinates().unwrap().x();
2127 let vpk_pk_d_x = *vpk_pk_d_affine.coordinates().unwrap().x();
2128
2129 let total_note_value = pallas::Base::from(10_000u64);
2130 let voting_round_id = pallas::Base::random(&mut rng);
2131 let proposal_authority_old = pallas::Base::from(13u64);
2132 let proposal_id = 3u64;
2133 let van_comm_rand = pallas::Base::random(&mut rng);
2134
2135 let vote_authority_note_old = van_integrity_hash(
2136 vpk_g_d_x,
2137 vpk_pk_d_x,
2138 total_note_value,
2139 voting_round_id,
2140 proposal_authority_old,
2141 van_comm_rand,
2142 );
2143 let (auth_path, position, vote_comm_tree_root) =
2144 build_single_leaf_merkle_path(vote_authority_note_old);
2145 let van_nullifier = van_nullifier_hash(vsk_nk, voting_round_id, vote_authority_note_old);
2146 let one_shifted = pallas::Base::from(1u64 << proposal_id);
2147 let proposal_authority_new = proposal_authority_old - one_shifted;
2148 let vote_authority_note_new = van_integrity_hash(
2149 vpk_g_d_x,
2150 vpk_pk_d_x,
2151 total_note_value,
2152 voting_round_id,
2153 proposal_authority_new,
2154 van_comm_rand,
2155 );
2156
2157 let shares_u64: [u64; 16] = [625; 16];
2158 let (_ea_sk, ea_pk_point, ea_pk_affine) = generate_ea_keypair();
2159 let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
2160 encrypt_shares(shares_u64, ea_pk_point);
2161
2162 let wrong_vsk = pallas::Scalar::random(&mut rng);
2163 assert_ne!(
2164 wrong_vsk, vsk,
2165 "test assumes distinct vsk with high probability"
2166 );
2167 let alpha_v = pallas::Scalar::random(&mut rng);
2168 let g = pallas::Point::from(spend_auth_g_affine());
2169 let r_vpk = (g * vsk + g * alpha_v).to_affine();
2170 let r_vpk_x = *r_vpk.coordinates().unwrap().x();
2171 let r_vpk_y = *r_vpk.coordinates().unwrap().y();
2172
2173 let mut circuit = Circuit::with_van_witnesses(
2174 Value::known(auth_path),
2175 Value::known(position),
2176 Value::known(vpk_g_d_affine),
2177 Value::known(vpk_pk_d_affine),
2178 Value::known(total_note_value),
2179 Value::known(proposal_authority_old),
2180 Value::known(van_comm_rand),
2181 Value::known(vote_authority_note_old),
2182 Value::known(wrong_vsk),
2183 Value::known(rivk_v),
2184 Value::known(vsk_nk),
2185 Value::known(alpha_v),
2186 );
2187 circuit.one_shifted = Value::known(one_shifted);
2188 circuit.shares = shares_u64.map(|s| Value::known(pallas::Base::from(s)));
2189 circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
2190 circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
2191 circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
2192 circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
2193 circuit.share_blinds = share_blinds.map(Value::known);
2194 circuit.share_randomness = randomness.map(Value::known);
2195 circuit.ea_pk = Value::known(ea_pk_affine);
2196 let vc = set_condition_11(&mut circuit, shares_hash_val, proposal_id, voting_round_id);
2197
2198 let instance = Instance::from_parts(
2199 van_nullifier,
2200 r_vpk_x,
2201 r_vpk_y,
2202 vote_authority_note_new,
2203 vc,
2204 vote_comm_tree_root,
2205 pallas::Base::zero(),
2206 pallas::Base::from(proposal_id),
2207 voting_round_id,
2208 *ea_pk_affine.coordinates().unwrap().x(),
2209 *ea_pk_affine.coordinates().unwrap().y(),
2210 );
2211
2212 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2213 assert!(
2214 prover.verify().is_err(),
2215 "condition 3 must reject wrong vsk"
2216 );
2217 }
2218
2219 #[test]
2223 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2224 fn condition_3_wrong_vpk_pk_d_fails() {
2225 let mut rng = OsRng;
2226
2227 let vsk = pallas::Scalar::random(&mut rng);
2228 let vsk_nk = pallas::Base::random(&mut rng);
2229 let rivk_v = pallas::Scalar::random(&mut rng);
2230 let (vpk_g_d_affine, _vpk_pk_d_correct) = derive_voting_address(vsk, vsk_nk, rivk_v);
2231 let vpk_g_d_x = *vpk_g_d_affine.coordinates().unwrap().x();
2232
2233 let wrong_vpk_pk_d_affine =
2234 (pallas::Point::generator() * pallas::Scalar::from(99999u64)).to_affine();
2235 let wrong_vpk_pk_d_x = *wrong_vpk_pk_d_affine.coordinates().unwrap().x();
2236
2237 let total_note_value = pallas::Base::from(10_000u64);
2238 let voting_round_id = pallas::Base::random(&mut rng);
2239 let proposal_authority_old = pallas::Base::from(13u64);
2240 let proposal_id = 3u64;
2241 let van_comm_rand = pallas::Base::random(&mut rng);
2242
2243 let vote_authority_note_old = van_integrity_hash(
2244 vpk_g_d_x,
2245 wrong_vpk_pk_d_x,
2246 total_note_value,
2247 voting_round_id,
2248 proposal_authority_old,
2249 van_comm_rand,
2250 );
2251 let (auth_path, position, vote_comm_tree_root) =
2252 build_single_leaf_merkle_path(vote_authority_note_old);
2253 let van_nullifier = van_nullifier_hash(vsk_nk, voting_round_id, vote_authority_note_old);
2254 let one_shifted = pallas::Base::from(1u64 << proposal_id);
2255 let proposal_authority_new = proposal_authority_old - one_shifted;
2256 let vote_authority_note_new = van_integrity_hash(
2257 vpk_g_d_x,
2258 wrong_vpk_pk_d_x,
2259 total_note_value,
2260 voting_round_id,
2261 proposal_authority_new,
2262 van_comm_rand,
2263 );
2264
2265 let shares_u64: [u64; 16] = [625; 16];
2266 let (_ea_sk, ea_pk_point, ea_pk_affine) = generate_ea_keypair();
2267 let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
2268 encrypt_shares(shares_u64, ea_pk_point);
2269
2270 let alpha_v = pallas::Scalar::random(&mut rng);
2271 let g = pallas::Point::from(spend_auth_g_affine());
2272 let r_vpk = (g * vsk + g * alpha_v).to_affine();
2273 let r_vpk_x = *r_vpk.coordinates().unwrap().x();
2274 let r_vpk_y = *r_vpk.coordinates().unwrap().y();
2275
2276 let mut circuit = Circuit::with_van_witnesses(
2277 Value::known(auth_path),
2278 Value::known(position),
2279 Value::known(vpk_g_d_affine),
2280 Value::known(wrong_vpk_pk_d_affine),
2281 Value::known(total_note_value),
2282 Value::known(proposal_authority_old),
2283 Value::known(van_comm_rand),
2284 Value::known(vote_authority_note_old),
2285 Value::known(vsk),
2286 Value::known(rivk_v),
2287 Value::known(vsk_nk),
2288 Value::known(alpha_v),
2289 );
2290 circuit.one_shifted = Value::known(one_shifted);
2291 circuit.shares = shares_u64.map(|s| Value::known(pallas::Base::from(s)));
2292 circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
2293 circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
2294 circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
2295 circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
2296 circuit.share_blinds = share_blinds.map(Value::known);
2297 circuit.share_randomness = randomness.map(Value::known);
2298 circuit.ea_pk = Value::known(ea_pk_affine);
2299 let vc = set_condition_11(&mut circuit, shares_hash_val, proposal_id, voting_round_id);
2300
2301 let instance = Instance::from_parts(
2302 van_nullifier,
2303 r_vpk_x,
2304 r_vpk_y,
2305 vote_authority_note_new,
2306 vc,
2307 vote_comm_tree_root,
2308 pallas::Base::zero(),
2309 pallas::Base::from(proposal_id),
2310 voting_round_id,
2311 *ea_pk_affine.coordinates().unwrap().x(),
2312 *ea_pk_affine.coordinates().unwrap().y(),
2313 );
2314
2315 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2316 assert!(
2317 prover.verify().is_err(),
2318 "condition 3 must reject wrong vpk_pk_d"
2319 );
2320 }
2321
2322 #[test]
2328 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2329 fn condition_4_wrong_r_vpk_fails() {
2330 let (circuit, mut instance) = make_test_data();
2331
2332 instance.r_vpk_x = pallas::Base::random(&mut OsRng);
2333
2334 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2335 assert!(
2336 prover.verify().is_err(),
2337 "condition 4 must reject wrong r_vpk"
2338 );
2339 }
2340
2341 #[test]
2346 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2347 fn condition_4_alpha_zero_is_accepted_by_relation() {
2348 let (circuit, instance) = make_test_data_with_authority_proposal_and_alpha(
2349 pallas::Base::from(13u64),
2350 TEST_PROPOSAL_ID,
2351 Some(pallas::Scalar::zero()),
2352 );
2353
2354 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2355 assert_eq!(prover.verify(), Ok(()));
2356 }
2357
2358 #[test]
2364 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2365 fn van_nullifier_wrong_public_input_fails() {
2366 let (circuit, mut instance) = make_test_data();
2367
2368 instance.van_nullifier = pallas::Base::random(&mut OsRng);
2370
2371 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2372
2373 assert!(prover.verify().is_err());
2375 }
2376
2377 #[test]
2383 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2384 fn van_nullifier_wrong_vsk_nk_fails() {
2385 let mut rng = OsRng;
2386
2387 let vsk = pallas::Scalar::random(&mut rng);
2389 let vsk_nk = pallas::Base::random(&mut rng);
2390 let rivk_v = pallas::Scalar::random(&mut rng);
2391 let (vpk_g_d_affine, vpk_pk_d_affine) = derive_voting_address(vsk, vsk_nk, rivk_v);
2392 let vpk_g_d_x = *vpk_g_d_affine.coordinates().unwrap().x();
2393 let vpk_pk_d_x = *vpk_pk_d_affine.coordinates().unwrap().x();
2394
2395 let total_note_value = pallas::Base::from(10_000u64);
2396 let voting_round_id = pallas::Base::random(&mut rng);
2397 let proposal_authority_old = pallas::Base::from(5u64); let van_comm_rand = pallas::Base::random(&mut rng);
2399 let proposal_id = 0u64; let vote_authority_note_old = van_integrity_hash(
2402 vpk_g_d_x,
2403 vpk_pk_d_x,
2404 total_note_value,
2405 voting_round_id,
2406 proposal_authority_old,
2407 van_comm_rand,
2408 );
2409 let (auth_path, position, vote_comm_tree_root) =
2410 build_single_leaf_merkle_path(vote_authority_note_old);
2411 let van_nullifier = van_nullifier_hash(vsk_nk, voting_round_id, vote_authority_note_old);
2412 let one_shifted = pallas::Base::from(1u64 << proposal_id);
2413 let proposal_authority_new = proposal_authority_old - one_shifted;
2414 let vote_authority_note_new = van_integrity_hash(
2415 vpk_g_d_x,
2416 vpk_pk_d_x,
2417 total_note_value,
2418 voting_round_id,
2419 proposal_authority_new,
2420 van_comm_rand,
2421 );
2422
2423 let wrong_vsk_nk = pallas::Base::random(&mut rng);
2425 let alpha_v = pallas::Scalar::random(&mut rng);
2426 let g = pallas::Point::from(spend_auth_g_affine());
2427 let r_vpk = (g * vsk + g * alpha_v).to_affine();
2428 let r_vpk_x = *r_vpk.coordinates().unwrap().x();
2429 let r_vpk_y = *r_vpk.coordinates().unwrap().y();
2430
2431 let shares_u64: [u64; 16] = [625; 16];
2433
2434 let (_ea_sk, ea_pk_point, ea_pk_affine) = generate_ea_keypair();
2436 let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
2437 encrypt_shares(shares_u64, ea_pk_point);
2438
2439 let mut circuit = Circuit::with_van_witnesses(
2440 Value::known(auth_path),
2441 Value::known(position),
2442 Value::known(vpk_g_d_affine),
2443 Value::known(vpk_pk_d_affine),
2444 Value::known(total_note_value),
2445 Value::known(proposal_authority_old),
2446 Value::known(van_comm_rand),
2447 Value::known(vote_authority_note_old),
2448 Value::known(vsk),
2449 Value::known(rivk_v),
2450 Value::known(wrong_vsk_nk),
2451 Value::known(alpha_v),
2452 );
2453 circuit.one_shifted = Value::known(one_shifted);
2454 circuit.shares = shares_u64.map(|s| Value::known(pallas::Base::from(s)));
2455 circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
2456 circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
2457 circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
2458 circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
2459 circuit.share_blinds = share_blinds.map(Value::known);
2460 circuit.share_randomness = randomness.map(Value::known);
2461 circuit.ea_pk = Value::known(ea_pk_affine);
2462 let vc = set_condition_11(&mut circuit, shares_hash_val, proposal_id, voting_round_id);
2463
2464 let instance = Instance::from_parts(
2465 van_nullifier,
2466 r_vpk_x,
2467 r_vpk_y,
2468 vote_authority_note_new,
2469 vc,
2470 vote_comm_tree_root,
2471 pallas::Base::zero(),
2472 pallas::Base::from(proposal_id),
2473 voting_round_id,
2474 *ea_pk_affine.coordinates().unwrap().x(),
2475 *ea_pk_affine.coordinates().unwrap().y(),
2476 );
2477
2478 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2479 assert!(prover.verify().is_err());
2483 }
2484
2485 #[test]
2487 fn van_nullifier_hash_deterministic() {
2488 let mut rng = OsRng;
2489
2490 let nk = pallas::Base::random(&mut rng);
2491 let round = pallas::Base::random(&mut rng);
2492 let van = pallas::Base::random(&mut rng);
2493
2494 let h1 = van_nullifier_hash(nk, round, van);
2495 let h2 = van_nullifier_hash(nk, round, van);
2496 assert_eq!(h1, h2);
2497
2498 let h3 = van_nullifier_hash(pallas::Base::random(&mut rng), round, van);
2500 assert_ne!(h1, h3);
2501 }
2502
2503 #[test]
2504 fn van_nullifier_hash_frozen_vector() {
2505 assert_eq!(
2506 van_nullifier_hash(
2507 pallas::Base::from(1u64),
2508 pallas::Base::from(42u64),
2509 pallas::Base::from(100u64),
2510 ),
2511 pallas::Base::from_repr([
2512 114, 56, 62, 208, 155, 244, 76, 209, 125, 210, 149, 109, 176, 88, 34, 116, 123, 56,
2513 62, 216, 108, 204, 55, 120, 28, 155, 217, 186, 29, 159, 128, 2,
2514 ])
2515 .expect("frozen vector must be canonical")
2516 );
2517 }
2518
2519 #[test]
2520 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2521 fn stale_and_current_anchor_proofs_for_same_van_share_nullifier() {
2522 let fixture = VoteReuseFixture::new();
2523 let voting_round_id = pallas::Base::from(0xCAFEu64);
2524 let stale_van = fixture.vote_authority_note_old(voting_round_id);
2525 let successor_van = fixture.vote_authority_note_new(voting_round_id);
2526
2527 let (stale_path, stale_position, stale_root) = build_single_leaf_merkle_path(stale_van);
2528 let (stale_circuit, stale_instance) =
2529 fixture.build_vote_data(voting_round_id, stale_path, stale_position, stale_root, 10);
2530
2531 let (current_path, current_position, current_root) =
2532 build_left_leaf_merkle_path_with_sibling(stale_van, successor_van);
2533 let (current_circuit, current_instance) = fixture.build_vote_data(
2534 voting_round_id,
2535 current_path,
2536 current_position,
2537 current_root,
2538 11,
2539 );
2540
2541 assert_ne!(
2542 stale_root, current_root,
2543 "the successor VAN changes the supplied tree anchor"
2544 );
2545 assert_eq!(
2546 stale_instance.van_nullifier, current_instance.van_nullifier,
2547 "same (vsk_nk, voting_round_id, VAN) must collide for chain-side nullifier uniqueness"
2548 );
2549
2550 let stale_prover =
2553 MockProver::run(K, &stale_circuit, vec![stale_instance.to_halo2_instance()]).unwrap();
2554 assert_eq!(stale_prover.verify(), Ok(()));
2555
2556 let current_prover = MockProver::run(
2557 K,
2558 ¤t_circuit,
2559 vec![current_instance.to_halo2_instance()],
2560 )
2561 .unwrap();
2562 assert_eq!(current_prover.verify(), Ok(()));
2563 }
2564
2565 #[test]
2567 fn domain_van_nullifier_deterministic() {
2568 let d1 = domain_van_nullifier();
2569 let d2 = domain_van_nullifier();
2570 assert_eq!(d1, d2);
2571
2572 assert_ne!(d1, pallas::Base::zero());
2574 }
2575
2576 #[test]
2582 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2583 fn proposal_authority_decrement_minimum_valid() {
2584 let (circuit, instance) =
2588 make_test_data_with_authority_and_proposal(pallas::Base::from(2u64), 1);
2589
2590 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2591 assert_eq!(prover.verify(), Ok(()));
2592 }
2593
2594 #[test]
2597 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2598 fn proposal_authority_zero_fails() {
2599 let (circuit, instance) = make_test_data_with_authority(pallas::Base::zero());
2600
2601 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2602
2603 assert!(prover.verify().is_err());
2604 }
2605
2606 #[test]
2608 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2609 fn proposal_id_zero_fails() {
2610 let (circuit, instance) =
2613 make_test_data_with_authority_and_proposal(pallas::Base::one(), 0);
2614
2615 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2616 assert!(prover.verify().is_err(), "proposal_id = 0 must be rejected");
2617 }
2618
2619 #[test]
2621 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2622 fn proposal_authority_full_authority_proposal_1_passes() {
2623 const MAX_PROPOSAL_AUTHORITY: u64 = 65535;
2624 let (circuit, instance) = make_test_data_with_authority_and_proposal(
2625 pallas::Base::from(MAX_PROPOSAL_AUTHORITY),
2626 1,
2627 );
2628
2629 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2630 assert_eq!(prover.verify(), Ok(()));
2631 }
2632
2633 #[test]
2635 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2636 fn proposal_authority_wrong_new_fails() {
2637 let (circuit, mut instance) =
2638 make_test_data_with_authority_and_proposal(pallas::Base::from(65535u64), 1);
2639
2640 instance.vote_authority_note_new = pallas::Base::random(&mut OsRng);
2641
2642 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2643 assert!(prover.verify().is_err());
2644 }
2645
2646 #[test]
2651 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2652 fn proposal_authority_bit_not_set_fails() {
2653 let (circuit, instance) =
2654 make_test_data_with_authority_and_proposal(pallas::Base::from(4u64), 1);
2655
2656 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2657 assert!(prover.verify().is_err());
2658 }
2659
2660 #[test]
2664 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2665 fn proposal_authority_condition6_run_sel_constraint() {
2666 let (circuit, instance) =
2667 make_test_data_with_authority_and_proposal(pallas::Base::from(3u64), 1);
2668
2669 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2670 assert_eq!(prover.verify(), Ok(()));
2671 }
2672
2673 #[test]
2678 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2679 fn proposal_authority_exceeds_16_bits_fails() {
2680 let (circuit, instance) = make_test_data_with_authority(pallas::Base::from(65536u64));
2682 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2683 assert!(
2684 prover.verify().is_err(),
2685 "authority > 65535 must be rejected by the 16-bit bit decomposition"
2686 );
2687 }
2688
2689 #[test]
2695 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2696 fn new_van_integrity_wrong_public_input_fails() {
2697 let (circuit, mut instance) = make_test_data();
2698
2699 instance.vote_authority_note_new = pallas::Base::random(&mut OsRng);
2701
2702 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2703
2704 assert!(prover.verify().is_err());
2706 }
2707
2708 #[test]
2711 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2712 fn new_van_integrity_large_authority() {
2713 let (circuit, instance) = make_test_data_with_authority(pallas::Base::from(0xFFF8u64));
2714
2715 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2716 assert_eq!(prover.verify(), Ok(()));
2717 }
2718
2719 #[test]
2725 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2726 fn van_membership_wrong_root_fails() {
2727 let (circuit, mut instance) = make_test_data();
2728
2729 instance.vote_comm_tree_root = pallas::Base::random(&mut OsRng);
2731
2732 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2733 assert!(prover.verify().is_err());
2734 }
2735
2736 #[test]
2738 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2739 fn van_membership_nonzero_position() {
2740 let mut rng = OsRng;
2741
2742 let vsk = pallas::Scalar::random(&mut rng);
2744 let vsk_nk = pallas::Base::random(&mut rng);
2745 let rivk_v = pallas::Scalar::random(&mut rng);
2746 let (vpk_g_d_affine, vpk_pk_d_affine) = derive_voting_address(vsk, vsk_nk, rivk_v);
2747 let vpk_g_d_x = *vpk_g_d_affine.coordinates().unwrap().x();
2748 let vpk_pk_d_x = *vpk_pk_d_affine.coordinates().unwrap().x();
2749
2750 let total_note_value = pallas::Base::from(10_000u64);
2751 let voting_round_id = pallas::Base::random(&mut rng);
2752 let proposal_authority_old = pallas::Base::from(5u64); let proposal_id = 2u64;
2755 let van_comm_rand = pallas::Base::random(&mut rng);
2756
2757 let vote_authority_note_old = van_integrity_hash(
2758 vpk_g_d_x,
2759 vpk_pk_d_x,
2760 total_note_value,
2761 voting_round_id,
2762 proposal_authority_old,
2763 van_comm_rand,
2764 );
2765
2766 let position: u32 = 7;
2768 let mut empty_roots = [pallas::Base::zero(); VOTE_COMM_TREE_DEPTH];
2769 empty_roots[0] = poseidon_hash_2(pallas::Base::zero(), pallas::Base::zero());
2770 for i in 1..VOTE_COMM_TREE_DEPTH {
2771 empty_roots[i] = poseidon_hash_2(empty_roots[i - 1], empty_roots[i - 1]);
2772 }
2773 let auth_path = empty_roots;
2774 let mut current = vote_authority_note_old;
2775 for i in 0..VOTE_COMM_TREE_DEPTH {
2776 if (position >> i) & 1 == 0 {
2777 current = poseidon_hash_2(current, auth_path[i]);
2778 } else {
2779 current = poseidon_hash_2(auth_path[i], current);
2780 }
2781 }
2782 let vote_comm_tree_root = current;
2783
2784 let van_nullifier = van_nullifier_hash(vsk_nk, voting_round_id, vote_authority_note_old);
2785 let one_shifted = pallas::Base::from(1u64 << proposal_id);
2786 let proposal_authority_new = proposal_authority_old - one_shifted;
2787 let vote_authority_note_new = van_integrity_hash(
2788 vpk_g_d_x,
2789 vpk_pk_d_x,
2790 total_note_value,
2791 voting_round_id,
2792 proposal_authority_new,
2793 van_comm_rand,
2794 );
2795
2796 let alpha_v = pallas::Scalar::random(&mut rng);
2797 let g = pallas::Point::from(spend_auth_g_affine());
2798 let r_vpk = (g * vsk + g * alpha_v).to_affine();
2799 let r_vpk_x = *r_vpk.coordinates().unwrap().x();
2800 let r_vpk_y = *r_vpk.coordinates().unwrap().y();
2801
2802 let shares_u64: [u64; 16] = [625; 16];
2804
2805 let (_ea_sk, ea_pk_point, ea_pk_affine) = generate_ea_keypair();
2807 let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
2808 encrypt_shares(shares_u64, ea_pk_point);
2809
2810 let mut circuit = Circuit::with_van_witnesses(
2811 Value::known(auth_path),
2812 Value::known(position),
2813 Value::known(vpk_g_d_affine),
2814 Value::known(vpk_pk_d_affine),
2815 Value::known(total_note_value),
2816 Value::known(proposal_authority_old),
2817 Value::known(van_comm_rand),
2818 Value::known(vote_authority_note_old),
2819 Value::known(vsk),
2820 Value::known(rivk_v),
2821 Value::known(vsk_nk),
2822 Value::known(alpha_v),
2823 );
2824 circuit.one_shifted = Value::known(one_shifted);
2825 circuit.shares = shares_u64.map(|s| Value::known(pallas::Base::from(s)));
2826 circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
2827 circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
2828 circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
2829 circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
2830 circuit.share_blinds = share_blinds.map(Value::known);
2831 circuit.share_randomness = randomness.map(Value::known);
2832 circuit.ea_pk = Value::known(ea_pk_affine);
2833 let vc = set_condition_11(&mut circuit, shares_hash_val, proposal_id, voting_round_id);
2834
2835 let instance = Instance::from_parts(
2836 van_nullifier,
2837 r_vpk_x,
2838 r_vpk_y,
2839 vote_authority_note_new,
2840 vc,
2841 vote_comm_tree_root,
2842 pallas::Base::zero(),
2843 pallas::Base::from(proposal_id),
2844 voting_round_id,
2845 *ea_pk_affine.coordinates().unwrap().x(),
2846 *ea_pk_affine.coordinates().unwrap().y(),
2847 );
2848
2849 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2850 assert_eq!(prover.verify(), Ok(()));
2851 }
2852
2853 #[test]
2855 fn poseidon_hash_2_deterministic() {
2856 let mut rng = OsRng;
2857 let a = pallas::Base::random(&mut rng);
2858 let b = pallas::Base::random(&mut rng);
2859
2860 assert_eq!(poseidon_hash_2(a, b), poseidon_hash_2(a, b));
2861 assert_ne!(poseidon_hash_2(a, b), poseidon_hash_2(b, a));
2863 }
2864
2865 #[test]
2871 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2872 fn shares_sum_wrong_total_fails() {
2873 let (mut circuit, instance) = make_test_data();
2874
2875 circuit.shares[3] = Value::known(pallas::Base::from(999u64));
2879
2880 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2881 assert!(prover.verify().is_err());
2883 }
2884
2885 #[test]
2891 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2892 fn shares_range_max_valid() {
2893 let max_share = pallas::Base::from((1u64 << 30) - 1); let total = (0..16).fold(pallas::Base::zero(), |acc, _| acc + max_share);
2895
2896 let mut rng = OsRng;
2897 let vsk = pallas::Scalar::random(&mut rng);
2899 let vsk_nk = pallas::Base::random(&mut rng);
2900 let rivk_v = pallas::Scalar::random(&mut rng);
2901 let (vpk_g_d_affine, vpk_pk_d_affine) = derive_voting_address(vsk, vsk_nk, rivk_v);
2902 let vpk_g_d_x = *vpk_g_d_affine.coordinates().unwrap().x();
2903 let vpk_pk_d_x = *vpk_pk_d_affine.coordinates().unwrap().x();
2904
2905 let voting_round_id = pallas::Base::random(&mut rng);
2906 let proposal_authority_old = pallas::Base::from(5u64); let proposal_id = 2u64;
2909 let van_comm_rand = pallas::Base::random(&mut rng);
2910
2911 let vote_authority_note_old = van_integrity_hash(
2912 vpk_g_d_x,
2913 vpk_pk_d_x,
2914 total,
2915 voting_round_id,
2916 proposal_authority_old,
2917 van_comm_rand,
2918 );
2919 let (auth_path, position, vote_comm_tree_root) =
2920 build_single_leaf_merkle_path(vote_authority_note_old);
2921 let van_nullifier = van_nullifier_hash(vsk_nk, voting_round_id, vote_authority_note_old);
2922 let one_shifted = pallas::Base::from(1u64 << proposal_id);
2923 let proposal_authority_new = proposal_authority_old - one_shifted;
2924 let vote_authority_note_new = van_integrity_hash(
2925 vpk_g_d_x,
2926 vpk_pk_d_x,
2927 total,
2928 voting_round_id,
2929 proposal_authority_new,
2930 van_comm_rand,
2931 );
2932
2933 let max_share_u64 = (1u64 << 30) - 1;
2935 let shares_u64: [u64; 16] = [max_share_u64; 16];
2936 let (_ea_sk, ea_pk_point, ea_pk_affine) = generate_ea_keypair();
2937 let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
2938 encrypt_shares(shares_u64, ea_pk_point);
2939
2940 let alpha_v = pallas::Scalar::random(&mut rng);
2941 let g = pallas::Point::from(spend_auth_g_affine());
2942 let r_vpk = (g * vsk + g * alpha_v).to_affine();
2943 let r_vpk_x = *r_vpk.coordinates().unwrap().x();
2944 let r_vpk_y = *r_vpk.coordinates().unwrap().y();
2945
2946 let mut circuit = Circuit::with_van_witnesses(
2947 Value::known(auth_path),
2948 Value::known(position),
2949 Value::known(vpk_g_d_affine),
2950 Value::known(vpk_pk_d_affine),
2951 Value::known(total),
2952 Value::known(proposal_authority_old),
2953 Value::known(van_comm_rand),
2954 Value::known(vote_authority_note_old),
2955 Value::known(vsk),
2956 Value::known(rivk_v),
2957 Value::known(vsk_nk),
2958 Value::known(alpha_v),
2959 );
2960 circuit.one_shifted = Value::known(one_shifted);
2961 circuit.shares = [Value::known(max_share); 16];
2962 circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
2963 circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
2964 circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
2965 circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
2966 circuit.share_blinds = share_blinds.map(Value::known);
2967 circuit.share_randomness = randomness.map(Value::known);
2968 circuit.ea_pk = Value::known(ea_pk_affine);
2969 let vc = set_condition_11(&mut circuit, shares_hash_val, proposal_id, voting_round_id);
2970
2971 let instance = Instance::from_parts(
2972 van_nullifier,
2973 r_vpk_x,
2974 r_vpk_y,
2975 vote_authority_note_new,
2976 vc,
2977 vote_comm_tree_root,
2978 pallas::Base::zero(),
2979 pallas::Base::from(proposal_id),
2980 voting_round_id,
2981 *ea_pk_affine.coordinates().unwrap().x(),
2982 *ea_pk_affine.coordinates().unwrap().y(),
2983 );
2984
2985 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2986 assert_eq!(prover.verify(), Ok(()));
2987 }
2988
2989 #[test]
2991 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2992 fn shares_range_overflow_fails() {
2993 let (mut circuit, instance) = make_test_data();
2994
2995 circuit.shares[0] = Value::known(pallas::Base::from(1u64 << 30));
2999
3000 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3001 assert!(prover.verify().is_err());
3002 }
3003
3004 #[test]
3007 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3008 fn shares_range_field_wrap_fails() {
3009 let (mut circuit, instance) = make_test_data();
3010
3011 circuit.shares[0] = Value::known(-pallas::Base::one());
3014
3015 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3016 assert!(prover.verify().is_err());
3017 }
3018
3019 #[test]
3025 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3026 fn shares_range_single_overflow_correct_sum_fails() {
3027 let mut rng = OsRng;
3028
3029 let overflow_share = pallas::Base::from(1u64 << 30); let normal_share_u64 = 625u64;
3031 let total_note_value = overflow_share + pallas::Base::from(15u64 * normal_share_u64);
3033
3034 let vsk = pallas::Scalar::random(&mut rng);
3035 let vsk_nk = pallas::Base::random(&mut rng);
3036 let rivk_v = pallas::Scalar::random(&mut rng);
3037 let alpha_v = pallas::Scalar::random(&mut rng);
3038 let (vpk_g_d_affine, vpk_pk_d_affine) = derive_voting_address(vsk, vsk_nk, rivk_v);
3039 let vpk_g_d_x = *vpk_g_d_affine.coordinates().unwrap().x();
3040 let vpk_pk_d_x = *vpk_pk_d_affine.coordinates().unwrap().x();
3041
3042 let voting_round_id = pallas::Base::random(&mut rng);
3043 let proposal_authority_old = pallas::Base::from(13u64); let proposal_id = TEST_PROPOSAL_ID;
3045 let van_comm_rand = pallas::Base::random(&mut rng);
3046
3047 let vote_authority_note_old = van_integrity_hash(
3048 vpk_g_d_x,
3049 vpk_pk_d_x,
3050 total_note_value,
3051 voting_round_id,
3052 proposal_authority_old,
3053 van_comm_rand,
3054 );
3055 let (auth_path, position, vote_comm_tree_root) =
3056 build_single_leaf_merkle_path(vote_authority_note_old);
3057 let van_nullifier = van_nullifier_hash(vsk_nk, voting_round_id, vote_authority_note_old);
3058 let one_shifted = pallas::Base::from(1u64 << proposal_id);
3059 let proposal_authority_new = proposal_authority_old - one_shifted;
3060 let vote_authority_note_new = van_integrity_hash(
3061 vpk_g_d_x,
3062 vpk_pk_d_x,
3063 total_note_value,
3064 voting_round_id,
3065 proposal_authority_new,
3066 van_comm_rand,
3067 );
3068
3069 let shares_u64: [u64; 16] = {
3072 let mut arr = [normal_share_u64; 16];
3073 arr[0] = 1u64 << 30;
3074 arr
3075 };
3076 let (_ea_sk, ea_pk_point, ea_pk_affine) = generate_ea_keypair();
3077 let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
3078 encrypt_shares(shares_u64, ea_pk_point);
3079
3080 let g = pallas::Point::from(spend_auth_g_affine());
3081 let r_vpk = (g * vsk + g * alpha_v).to_affine();
3082
3083 let mut circuit = Circuit::with_van_witnesses(
3084 Value::known(auth_path),
3085 Value::known(position),
3086 Value::known(vpk_g_d_affine),
3087 Value::known(vpk_pk_d_affine),
3088 Value::known(total_note_value),
3089 Value::known(proposal_authority_old),
3090 Value::known(van_comm_rand),
3091 Value::known(vote_authority_note_old),
3092 Value::known(vsk),
3093 Value::known(rivk_v),
3094 Value::known(vsk_nk),
3095 Value::known(alpha_v),
3096 );
3097 circuit.one_shifted = Value::known(one_shifted);
3098 circuit.shares = shares_u64.map(|s| Value::known(pallas::Base::from(s)));
3099 circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
3100 circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
3101 circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
3102 circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
3103 circuit.share_blinds = share_blinds.map(Value::known);
3104 circuit.share_randomness = randomness.map(Value::known);
3105 circuit.ea_pk = Value::known(ea_pk_affine);
3106
3107 let vote_commitment =
3108 set_condition_11(&mut circuit, shares_hash_val, proposal_id, voting_round_id);
3109
3110 let instance = Instance::from_parts(
3111 van_nullifier,
3112 *r_vpk.coordinates().unwrap().x(),
3113 *r_vpk.coordinates().unwrap().y(),
3114 vote_authority_note_new,
3115 vote_commitment,
3116 vote_comm_tree_root,
3117 pallas::Base::zero(),
3118 pallas::Base::from(proposal_id),
3119 voting_round_id,
3120 *ea_pk_affine.coordinates().unwrap().x(),
3121 *ea_pk_affine.coordinates().unwrap().y(),
3122 );
3123
3124 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3125 assert!(
3128 prover.verify().is_err(),
3129 "range check must reject a share equal to 2^30 even when the total sum is correct"
3130 );
3131 }
3132
3133 #[test]
3139 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3140 fn shares_hash_valid_proof() {
3141 let (circuit, instance) = make_test_data();
3142
3143 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3144 assert_eq!(prover.verify(), Ok(()));
3145 }
3146
3147 #[test]
3150 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3151 fn shares_hash_wrong_enc_share_fails() {
3152 let (mut circuit, instance) = make_test_data();
3153
3154 circuit.enc_share_c1_x[0] = Value::known(pallas::Base::random(&mut OsRng));
3157
3158 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3159 assert!(prover.verify().is_err());
3160 }
3161
3162 #[test]
3165 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3166 fn shares_hash_wrong_instance_fails() {
3167 let (circuit, mut instance) = make_test_data();
3168
3169 instance.vote_commitment = pallas::Base::random(&mut OsRng);
3171
3172 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3173 assert!(prover.verify().is_err());
3174 }
3175
3176 #[test]
3178 fn shares_hash_deterministic() {
3179 let mut rng = OsRng;
3180
3181 let blinds: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
3182 let c1_x: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
3183 let c2_x: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
3184 let c1_y: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
3185 let c2_y: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
3186
3187 let h1 = shares_hash(blinds, c1_x, c2_x, c1_y, c2_y);
3188 let h2 = shares_hash(blinds, c1_x, c2_x, c1_y, c2_y);
3189 assert_eq!(h1, h2);
3190
3191 let mut c1_x_alt = c1_x;
3193 c1_x_alt[2] = pallas::Base::random(&mut rng);
3194 let h3 = shares_hash(blinds, c1_x_alt, c2_x, c1_y, c2_y);
3195 assert_ne!(h1, h3);
3196
3197 let h4 = shares_hash(blinds, c2_x, c1_x, c2_y, c1_y);
3199 assert_ne!(h1, h4);
3200
3201 let blinds_alt: [pallas::Base; 16] =
3203 core::array::from_fn(|_| pallas::Base::random(&mut rng));
3204 let h5 = shares_hash(blinds_alt, c1_x, c2_x, c1_y, c2_y);
3205 assert_ne!(h1, h5);
3206 }
3207
3208 #[test]
3212 fn share_commitment_deterministic() {
3213 let mut rng = OsRng;
3214 let blind = pallas::Base::random(&mut rng);
3215 let c1_x = pallas::Base::random(&mut rng);
3216 let c2_x = pallas::Base::random(&mut rng);
3217 let c1_y = pallas::Base::random(&mut rng);
3218 let c2_y = pallas::Base::random(&mut rng);
3219
3220 let h1 = share_commitment(blind, c1_x, c2_x, c1_y, c2_y);
3221 let h2 = share_commitment(blind, c1_x, c2_x, c1_y, c2_y);
3222 assert_eq!(h1, h2);
3223
3224 let h3 = share_commitment(blind, c2_x, c1_x, c2_y, c1_y);
3226 assert_ne!(h1, h3);
3227
3228 let blind_alt = pallas::Base::random(&mut rng);
3230 let h4 = share_commitment(blind_alt, c1_x, c2_x, c1_y, c2_y);
3231 assert_ne!(h1, h4);
3232 }
3233
3234 #[derive(Clone, Default)]
3238 struct ShareCommitmentTestCircuit {
3239 blind: pallas::Base,
3240 c1_x: pallas::Base,
3241 c2_x: pallas::Base,
3242 c1_y: pallas::Base,
3243 c2_y: pallas::Base,
3244 }
3245
3246 #[derive(Clone)]
3247 struct ShareCommitmentTestConfig {
3248 primary: Column<InstanceColumn>,
3249 advices: [Column<Advice>; 5],
3250 poseidon_config: PoseidonConfig<pallas::Base, 3, 2>,
3251 }
3252
3253 impl plonk::Circuit<pallas::Base> for ShareCommitmentTestCircuit {
3254 type Config = ShareCommitmentTestConfig;
3255 type FloorPlanner = floor_planner::V1;
3256
3257 fn without_witnesses(&self) -> Self {
3258 Self::default()
3259 }
3260
3261 fn configure(meta: &mut ConstraintSystem<pallas::Base>) -> Self::Config {
3262 let primary = meta.instance_column();
3263 meta.enable_equality(primary);
3264 let advices: [Column<Advice>; 5] = core::array::from_fn(|_| meta.advice_column());
3265 for col in &advices {
3266 meta.enable_equality(*col);
3267 }
3268 let fixed: [Column<Fixed>; 6] = core::array::from_fn(|_| meta.fixed_column());
3269 let constants = meta.fixed_column();
3270 meta.enable_constant(constants);
3271 let rc_a = fixed[0..3].try_into().unwrap();
3272 let rc_b = fixed[3..6].try_into().unwrap();
3273 let poseidon_config = PoseidonChip::configure::<poseidon::P128Pow5T3>(
3274 meta,
3275 advices[1..4].try_into().unwrap(),
3276 advices[4],
3277 rc_a,
3278 rc_b,
3279 );
3280 ShareCommitmentTestConfig {
3281 primary,
3282 advices,
3283 poseidon_config,
3284 }
3285 }
3286
3287 fn synthesize(
3288 &self,
3289 config: Self::Config,
3290 mut layouter: impl Layouter<pallas::Base>,
3291 ) -> Result<(), plonk::Error> {
3292 let blind_cell = assign_free_advice(
3293 layouter.namespace(|| "blind"),
3294 config.advices[0],
3295 Value::known(self.blind),
3296 )?;
3297 let c1_x_cell = assign_free_advice(
3298 layouter.namespace(|| "c1_x"),
3299 config.advices[0],
3300 Value::known(self.c1_x),
3301 )?;
3302 let c2_x_cell = assign_free_advice(
3303 layouter.namespace(|| "c2_x"),
3304 config.advices[0],
3305 Value::known(self.c2_x),
3306 )?;
3307 let c1_y_cell = assign_free_advice(
3308 layouter.namespace(|| "c1_y"),
3309 config.advices[0],
3310 Value::known(self.c1_y),
3311 )?;
3312 let c2_y_cell = assign_free_advice(
3313 layouter.namespace(|| "c2_y"),
3314 config.advices[0],
3315 Value::known(self.c2_y),
3316 )?;
3317 let chip = PoseidonChip::construct(config.poseidon_config.clone());
3318 let result = hash_share_commitment_in_circuit(
3319 chip,
3320 layouter.namespace(|| "share_comm"),
3321 blind_cell,
3322 c1_x_cell,
3323 c2_x_cell,
3324 c1_y_cell,
3325 c2_y_cell,
3326 0,
3327 )?;
3328 layouter.constrain_instance(result.cell(), config.primary, 0)?;
3329 Ok(())
3330 }
3331 }
3332
3333 #[test]
3338 fn hash_share_commitment_in_circuit_matches_native() {
3339 let mut rng = OsRng;
3340 let blind = pallas::Base::random(&mut rng);
3341 let c1_x = pallas::Base::random(&mut rng);
3342 let c2_x = pallas::Base::random(&mut rng);
3343 let c1_y = pallas::Base::random(&mut rng);
3344 let c2_y = pallas::Base::random(&mut rng);
3345
3346 let expected = share_commitment(blind, c1_x, c2_x, c1_y, c2_y);
3347 let circuit = ShareCommitmentTestCircuit {
3348 blind,
3349 c1_x,
3350 c2_x,
3351 c1_y,
3352 c2_y,
3353 };
3354 let instance = vec![vec![expected]];
3355 const TEST_K: u32 = 10;
3357 let prover = MockProver::run(TEST_K, &circuit, instance).expect("MockProver::run failed");
3358 assert_eq!(prover.verify(), Ok(()));
3359 }
3360
3361 #[test]
3367 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3368 fn encryption_integrity_valid_proof() {
3369 let (circuit, instance) = make_test_data();
3370
3371 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3372 assert_eq!(prover.verify(), Ok(()));
3373 }
3374
3375 #[test]
3378 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3379 fn encryption_integrity_randomness_zero_is_rejected() {
3380 let (mut circuit, mut instance) = make_test_data();
3381 let shares_u64 = [625u64; 16];
3382 let (_ea_sk, ea_pk_point, _ea_pk_affine) = generate_ea_keypair();
3383 let (mut c1_x, mut c2_x, mut c1_y, mut c2_y, mut randomness, blinds, _) =
3384 encrypt_shares(shares_u64, ea_pk_point);
3385 let c2 = pallas::Point::from(spend_auth_g_affine()) * pallas::Scalar::from(shares_u64[0]);
3386 let c2_coords = c2.to_affine().coordinates().unwrap();
3387
3388 randomness[0] = pallas::Base::zero();
3389 c1_x[0] = pallas::Base::zero();
3390 c1_y[0] = pallas::Base::zero();
3391 c2_x[0] = *c2_coords.x();
3392 c2_y[0] = *c2_coords.y();
3393
3394 circuit.share_randomness = randomness.map(Value::known);
3395 circuit.enc_share_c1_x = c1_x.map(Value::known);
3396 circuit.enc_share_c1_y = c1_y.map(Value::known);
3397 circuit.enc_share_c2_x = c2_x.map(Value::known);
3398 circuit.enc_share_c2_y = c2_y.map(Value::known);
3399 let shares_hash_val = shares_hash(blinds, c1_x, c2_x, c1_y, c2_y);
3400 instance.vote_commitment = set_condition_11(
3401 &mut circuit,
3402 shares_hash_val,
3403 TEST_PROPOSAL_ID,
3404 instance.voting_round_id,
3405 );
3406
3407 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3408 assert!(prover.verify().is_err());
3409 }
3410
3411 #[test]
3414 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3415 fn encryption_integrity_wrong_randomness_fails() {
3416 let (mut circuit, instance) = make_test_data();
3417
3418 circuit.share_randomness[0] = Value::known(pallas::Base::from(9999u64));
3420
3421 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3422 assert!(prover.verify().is_err());
3423 }
3424
3425 #[test]
3428 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3429 fn encryption_integrity_wrong_ea_pk_instance_fails() {
3430 let (circuit, mut instance) = make_test_data();
3431
3432 instance.ea_pk_x = pallas::Base::from(12345u64);
3435
3436 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3437 assert!(prover.verify().is_err());
3438 }
3439
3440 #[test]
3443 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3444 fn encryption_integrity_wrong_share_fails() {
3445 let (mut circuit, instance) = make_test_data();
3446
3447 circuit.shares[0] = Value::known(pallas::Base::from(9999u64));
3450
3451 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3452 assert!(prover.verify().is_err());
3453 }
3454
3455 #[test]
3458 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3459 fn encryption_integrity_wrong_enc_c2_x_fails() {
3460 let (mut circuit, instance) = make_test_data();
3461
3462 circuit.enc_share_c2_x[0] = Value::known(pallas::Base::random(&mut OsRng));
3466
3467 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3468 assert!(prover.verify().is_err());
3469 }
3470
3471 #[test]
3473 fn elgamal_encrypt_deterministic() {
3474 let (_ea_sk, ea_pk_point, _ea_pk_affine) = generate_ea_keypair();
3475
3476 let v = pallas::Base::from(1000u64);
3477 let r = pallas::Base::from(42u64);
3478
3479 let (c1_a, c2_a, _, _) =
3480 elgamal_encrypt(v, r, ea_pk_point).expect("test encryption inputs should be valid");
3481 let (c1_b, c2_b, _, _) =
3482 elgamal_encrypt(v, r, ea_pk_point).expect("test encryption inputs should be valid");
3483 assert_eq!(c1_a, c1_b);
3484 assert_eq!(c2_a, c2_b);
3485
3486 let (c1_c, _, _, _) = elgamal_encrypt(v, pallas::Base::from(99u64), ea_pk_point)
3488 .expect("test encryption inputs should be valid");
3489 assert_ne!(c1_a, c1_c);
3490 }
3491
3492 #[test]
3495 fn base_to_scalar_accepts_elgamal_inputs() {
3496 assert!(base_to_scalar(pallas::Base::zero()).is_some());
3498 assert!(base_to_scalar(pallas::Base::from(1u64)).is_some());
3499 assert!(base_to_scalar(pallas::Base::from(1_000u64)).is_some());
3500 assert!(base_to_scalar(pallas::Base::from(404u64)).is_some()); for r in (1u64..=16).map(|i| i * 101) {
3504 assert!(
3505 base_to_scalar(pallas::Base::from(r)).is_some(),
3506 "r = {} must convert for El Gamal",
3507 r
3508 );
3509 }
3510 }
3511
3512 #[test]
3518 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3519 fn vote_commitment_integrity_valid_proof() {
3520 let (circuit, instance) = make_test_data();
3521
3522 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3523 assert_eq!(prover.verify(), Ok(()));
3524 }
3525
3526 #[test]
3529 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3530 fn vote_commitment_wrong_decision_fails() {
3531 let (mut circuit, instance) = make_test_data();
3532
3533 circuit.vote_decision = Value::known(pallas::Base::from(99u64));
3535
3536 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3537 assert!(prover.verify().is_err());
3538 }
3539
3540 #[test]
3544 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3545 fn vote_commitment_wrong_proposal_id_fails() {
3546 let (circuit, mut instance) = make_test_data();
3547
3548 instance.proposal_id = pallas::Base::from(999u64);
3550
3551 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3552 assert!(prover.verify().is_err());
3553 }
3554
3555 #[test]
3557 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3558 fn vote_commitment_wrong_instance_fails() {
3559 let (circuit, mut instance) = make_test_data();
3560
3561 instance.vote_commitment = pallas::Base::random(&mut OsRng);
3563
3564 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3565 assert!(prover.verify().is_err());
3566 }
3567
3568 #[test]
3570 fn vote_commitment_hash_deterministic() {
3571 let mut rng = OsRng;
3572
3573 let rid = pallas::Base::random(&mut rng);
3574 let sh = pallas::Base::random(&mut rng);
3575 let pid = pallas::Base::from(5u64);
3576 let dec = pallas::Base::from(1u64);
3577
3578 let h1 = vote_commitment_hash(rid, sh, pid, dec);
3579 let h2 = vote_commitment_hash(rid, sh, pid, dec);
3580 assert_eq!(h1, h2);
3581
3582 let h3 = vote_commitment_hash(rid, sh, pallas::Base::from(6u64), dec);
3584 assert_ne!(h1, h3);
3585
3586 let h4 = vote_commitment_hash(pallas::Base::from(999u64), sh, pid, dec);
3588 assert_ne!(h1, h4);
3589
3590 assert_ne!(h1, pallas::Base::zero());
3593 }
3594
3595 #[test]
3601 fn instance_has_eleven_public_inputs() {
3602 let (_, instance) = make_test_data();
3603 assert_eq!(instance.to_halo2_instance().len(), 11);
3604 }
3605
3606 #[test]
3608 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3609 fn default_circuit_with_valid_instance_fails() {
3610 let (_, instance) = make_test_data();
3611 let circuit = Circuit::default();
3612
3613 match MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]) {
3614 Ok(prover) => assert!(prover.verify().is_err()),
3615 Err(_) => {} }
3617 }
3618
3619 #[test]
3628 #[ignore = "long-running row-budget diagnostic; run with `cargo test --manifest-path voting-circuits/Cargo.toml vote_proof::circuit::tests::row_budget -- --ignored --nocapture --test-threads=1`"]
3629 fn row_budget() {
3630 use halo2_proofs::dev::CircuitCost;
3631 use pasta_curves::vesta;
3632 use std::println;
3633
3634 let (circuit, _) = make_test_data();
3635
3636 let cost = CircuitCost::<vesta::Point, _>::measure(K, &circuit);
3639 let debug = format!("{cost:?}");
3640
3641 let extract = |field: &str| -> usize {
3643 let prefix = format!("{field}: ");
3644 debug
3645 .split(&prefix)
3646 .nth(1)
3647 .and_then(|s| s.split([',', ' ', '}']).next())
3648 .and_then(|n| n.parse().ok())
3649 .unwrap_or(0)
3650 };
3651
3652 let max_rows = extract("max_rows");
3653 let max_advice_rows = extract("max_advice_rows");
3654 let max_fixed_rows = extract("max_fixed_rows");
3655 let total_available = 1usize << K;
3656
3657 println!("=== vote-proof circuit row budget (K={K}) ===");
3658 println!(" max_rows (floor-planner high-water mark): {max_rows}");
3659 println!(" max_advice_rows: {max_advice_rows}");
3660 println!(" max_fixed_rows: {max_fixed_rows}");
3661 println!(" 2^K (total available rows): {total_available}");
3662 println!(
3663 " headroom: {}",
3664 total_available.saturating_sub(max_rows)
3665 );
3666 println!(
3667 " utilisation: {:.1}%",
3668 100.0 * max_rows as f64 / total_available as f64
3669 );
3670 println!();
3671 println!(" Full debug: {debug}");
3672
3673 let cost_default = CircuitCost::<vesta::Point, _>::measure(K, &Circuit::default());
3680 let debug_default = format!("{cost_default:?}");
3681 let max_rows_default = debug_default
3682 .split("max_rows: ")
3683 .nth(1)
3684 .and_then(|s| s.split([',', ' ', '}']).next())
3685 .and_then(|n| n.parse::<usize>().ok())
3686 .unwrap_or(0);
3687 if max_rows_default == max_rows {
3688 println!(
3689 " Witness-independence: PASS \
3690 (Circuit::default() max_rows={max_rows_default} == filled max_rows={max_rows})"
3691 );
3692 } else {
3693 println!(
3694 " Witness-independence: FAIL \
3695 (Circuit::default() max_rows={max_rows_default} != filled max_rows={max_rows}) \
3696 — row count depends on witness values!"
3697 );
3698 }
3699
3700 println!(" VOTE_COMM_TREE_DEPTH (circuit constant): {VOTE_COMM_TREE_DEPTH}");
3707
3708 for probe_k in 11u32..=K {
3713 let (c, inst) = make_test_data();
3714 match MockProver::run(probe_k, &c, vec![inst.to_halo2_instance()]) {
3715 Err(_) => {
3716 println!(" K={probe_k}: not enough rows (synthesizer rejected)");
3717 continue;
3718 }
3719 Ok(p) => match p.verify() {
3720 Ok(()) => {
3721 println!(" Minimum viable K: {probe_k} (2^{probe_k} = {} rows, {:.1}% headroom)",
3722 1usize << probe_k,
3723 100.0 * (1.0 - max_rows as f64 / (1usize << probe_k) as f64));
3724 break;
3725 }
3726 Err(_) => println!(" K={probe_k}: too small"),
3727 },
3728 }
3729 }
3730 }
3731}