1use std::vec::Vec;
56
57use halo2_proofs::{
58 circuit::{floor_planner, AssignedCell, Layouter, Value},
59 plonk::{self, Advice, Column, ConstraintSystem, Fixed, Instance as InstanceColumn},
60};
61use pasta_curves::{pallas, vesta};
62
63use super::authority_decrement::{AuthorityDecrementChip, AuthorityDecrementConfig};
64use crate::circuit::address_ownership::{prove_address_ownership, spend_auth_g_mul};
65use crate::circuit::elgamal::{prove_elgamal_encryptions, EaPkInstanceLoc};
66use crate::circuit::nonzero::NonZeroConfig;
67use crate::circuit::poseidon_merkle::{synthesize_poseidon_merkle_path, MerkleSwapGate};
68use crate::circuit::share_commitment;
69use crate::circuit::van_integrity;
70use crate::circuit::vote_commitment;
71use crate::domain_tags;
72pub use crate::protocol_hash::poseidon_hash_2;
73use crate::shares_hash::compute_shares_hash_in_circuit;
74#[cfg(test)]
75use crate::shares_hash::{hash_share_commitment_in_circuit, shares_hash};
76use halo2_gadgets::{
77 ecc::{
78 chip::{EccChip, EccConfig},
79 NonIdentityPoint, ScalarFixed,
80 },
81 poseidon::{
82 primitives::{self as poseidon, ConstantLength},
83 Hash as PoseidonHash, Pow5Chip as PoseidonChip, Pow5Config as PoseidonConfig,
84 },
85 sinsemilla::chip::{SinsemillaChip, SinsemillaConfig},
86 utilities::lookup_range_check::{LookupRangeCheck, LookupRangeCheckConfig},
87};
88use orchard::circuit::commit_ivk::{CommitIvkChip, CommitIvkConfig};
89use orchard::circuit::gadget::{
90 add_chip::{AddChip, AddConfig},
91 assign_free_advice, AddInstruction,
92};
93use orchard::constants::{OrchardCommitDomains, OrchardFixedBases, OrchardHashDomains};
94
95pub const VOTE_COMM_TREE_DEPTH: usize = 24;
108
109pub const K: u32 = 13;
127
128#[cfg(test)]
129use share_commitment::share_commitment;
130pub(crate) use van_integrity::DOMAIN_VAN;
131pub(crate) use vote_commitment::DOMAIN_VC;
132
133pub(super) const MAX_PROPOSAL_ID: usize = 16;
154
155pub const VAN_NULLIFIER_PUBLIC_OFFSET: usize = 0;
161pub const R_VPK_X_PUBLIC_OFFSET: usize = 1;
164pub const R_VPK_Y_PUBLIC_OFFSET: usize = 2;
166pub const VOTE_AUTHORITY_NOTE_NEW_PUBLIC_OFFSET: usize = 3;
168pub const VOTE_COMMITMENT_PUBLIC_OFFSET: usize = 4;
170pub const VOTE_COMM_TREE_ROOT_PUBLIC_OFFSET: usize = 5;
172pub const VOTE_COMM_TREE_ANCHOR_HEIGHT_PUBLIC_OFFSET: usize = 6;
180pub const PROPOSAL_ID_PUBLIC_OFFSET: usize = 7;
186pub const VOTING_ROUND_ID_PUBLIC_OFFSET: usize = 8;
192pub const EA_PK_X_PUBLIC_OFFSET: usize = 9;
194pub const EA_PK_Y_PUBLIC_OFFSET: usize = 10;
196
197pub(crate) use van_integrity::van_integrity_hash;
202pub(crate) use vote_commitment::vote_commitment_hash;
203
204pub fn domain_van_nullifier() -> pallas::Base {
209 domain_tags::vote_authority_spend()
210}
211
212pub(super) fn van_nullifier_hash(
221 vsk_nk: pallas::Base,
222 voting_round_id: pallas::Base,
223 vote_authority_note_old: pallas::Base,
224) -> pallas::Base {
225 poseidon::Hash::<_, poseidon::P128Pow5T3, ConstantLength<4>, 3, 2>::init().hash([
226 vsk_nk,
227 domain_van_nullifier(),
228 voting_round_id,
229 vote_authority_note_old,
230 ])
231}
232
233#[derive(Clone, Debug)]
245pub struct Config {
246 primary: Column<InstanceColumn>,
248 advices: [Column<Advice>; 10],
256 poseidon_config: PoseidonConfig<pallas::Base, 3, 2>,
263 add_config: AddConfig,
271 ecc_config: EccConfig<OrchardFixedBases>,
277 sinsemilla_config:
283 SinsemillaConfig<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>,
284 commit_ivk_config: CommitIvkConfig,
289 range_check: LookupRangeCheckConfig<pallas::Base, 10>,
296 merkle_swap: MerkleSwapGate,
302 authority_decrement: AuthorityDecrementConfig,
304 nonzero: NonZeroConfig,
306}
307
308impl Config {
309 pub(crate) fn poseidon_chip(&self) -> PoseidonChip<pallas::Base, 3, 2> {
314 PoseidonChip::construct(self.poseidon_config.clone())
315 }
316
317 fn add_chip(&self) -> AddChip {
319 AddChip::construct(self.add_config.clone())
320 }
321
322 fn ecc_chip(&self) -> EccChip<OrchardFixedBases> {
324 EccChip::construct(self.ecc_config.clone())
325 }
326
327 fn sinsemilla_chip(
329 &self,
330 ) -> SinsemillaChip<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases> {
331 SinsemillaChip::construct(self.sinsemilla_config.clone())
332 }
333
334 fn commit_ivk_chip(&self) -> CommitIvkChip {
336 CommitIvkChip::construct(self.commit_ivk_config.clone())
337 }
338
339 fn range_check_config(&self) -> LookupRangeCheckConfig<pallas::Base, 10> {
341 self.range_check
342 }
343}
344
345#[derive(Clone, Debug, Default)]
358pub struct Circuit {
359 pub(crate) vote_comm_tree_path: Value<[pallas::Base; VOTE_COMM_TREE_DEPTH]>,
365 pub(crate) vote_comm_tree_position: Value<u32>,
367
368 pub(crate) vpk_g_d: Value<pallas::Affine>,
381 pub(crate) vpk_pk_d: Value<pallas::Affine>,
385 pub(crate) total_note_value: Value<pallas::Base>,
388 pub(crate) proposal_authority_old: Value<pallas::Base>,
391 pub(crate) van_comm_rand: Value<pallas::Base>,
393 pub(crate) vote_authority_note_old: Value<pallas::Base>,
396
397 pub(crate) vsk: Value<pallas::Scalar>,
403 pub(crate) rivk_v: Value<pallas::Scalar>,
406 pub(crate) alpha_v: Value<pallas::Scalar>,
408
409 pub(crate) vsk_nk: Value<pallas::Base>,
413
414 pub(crate) one_shifted: Value<pallas::Base>,
423
424 pub(crate) shares: [Value<pallas::Base>; 16],
432
433 pub(crate) enc_share_c1_x: [Value<pallas::Base>; 16],
440 pub(crate) enc_share_c2_x: [Value<pallas::Base>; 16],
442 pub(crate) enc_share_c1_y: [Value<pallas::Base>; 16],
444 pub(crate) enc_share_c2_y: [Value<pallas::Base>; 16],
446
447 pub(crate) share_blinds: [Value<pallas::Base>; 16],
450
451 pub(crate) share_randomness: [Value<pallas::Base>; 16],
455 pub(crate) ea_pk: Value<pallas::Affine>,
461
462 pub(crate) vote_decision: Value<pallas::Base>,
465}
466
467impl Circuit {
468 pub fn with_van_witnesses(
485 vote_comm_tree_path: Value<[pallas::Base; VOTE_COMM_TREE_DEPTH]>,
486 vote_comm_tree_position: Value<u32>,
487 vpk_g_d: Value<pallas::Affine>,
488 vpk_pk_d: Value<pallas::Affine>,
489 total_note_value: Value<pallas::Base>,
490 proposal_authority_old: Value<pallas::Base>,
491 van_comm_rand: Value<pallas::Base>,
492 vote_authority_note_old: Value<pallas::Base>,
493 vsk: Value<pallas::Scalar>,
494 rivk_v: Value<pallas::Scalar>,
495 vsk_nk: Value<pallas::Base>,
496 alpha_v: Value<pallas::Scalar>,
497 ) -> Self {
498 Circuit {
499 vote_comm_tree_path,
500 vote_comm_tree_position,
501 vpk_g_d,
502 vpk_pk_d,
503 total_note_value,
504 proposal_authority_old,
505 van_comm_rand,
506 vote_authority_note_old,
507 vsk,
508 rivk_v,
509 alpha_v,
510 vsk_nk,
511 ..Default::default()
512 }
513 }
514}
515
516impl plonk::Circuit<pallas::Base> for Circuit {
517 type Config = Config;
518 type FloorPlanner = floor_planner::V1;
519
520 fn without_witnesses(&self) -> Self {
521 Self::default()
522 }
523
524 fn configure(meta: &mut ConstraintSystem<pallas::Base>) -> Self::Config {
525 let advices: [Column<Advice>; 10] = core::array::from_fn(|_| meta.advice_column());
541 for col in &advices {
542 meta.enable_equality(*col);
543 }
544
545 let primary = meta.instance_column();
547 meta.enable_equality(primary);
548
549 let lagrange_coeffs: [Column<Fixed>; 8] = core::array::from_fn(|_| meta.fixed_column());
554 let rc_a = lagrange_coeffs[2..5].try_into().unwrap();
555 let rc_b = lagrange_coeffs[5..8].try_into().unwrap();
556
557 let constants = meta.fixed_column();
562 meta.enable_constant(constants);
563
564 let add_config = AddChip::configure(meta, advices[7], advices[8], advices[6]);
568
569 let table_idx = meta.lookup_table_column();
574 let lookup = (
575 table_idx,
576 meta.lookup_table_column(),
577 meta.lookup_table_column(),
578 );
579
580 let range_check = LookupRangeCheckConfig::configure(meta, advices[9], table_idx);
582
583 let ecc_config =
588 EccChip::<OrchardFixedBases>::configure(meta, advices, lagrange_coeffs, range_check);
589
590 let sinsemilla_config = SinsemillaChip::configure(
595 meta,
596 advices[..5].try_into().unwrap(),
597 advices[6],
598 lagrange_coeffs[0],
599 lookup,
600 range_check,
601 false,
602 );
603
604 let commit_ivk_config = CommitIvkChip::configure(meta, advices);
607
608 let poseidon_config = PoseidonChip::configure::<poseidon::P128Pow5T3>(
613 meta,
614 advices[6..9].try_into().unwrap(),
615 advices[5],
616 rc_a,
617 rc_b,
618 );
619
620 let merkle_swap = MerkleSwapGate::configure(
622 meta,
623 [advices[0], advices[1], advices[2], advices[3], advices[4]],
624 );
625
626 let authority_decrement = AuthorityDecrementChip::configure(meta, advices);
628 let nonzero = NonZeroConfig::configure(meta, [advices[0], advices[1]]);
629
630 Config {
631 primary,
632 advices,
633 poseidon_config,
634 add_config,
635 ecc_config,
636 sinsemilla_config,
637 commit_ivk_config,
638 range_check,
639 merkle_swap,
640 authority_decrement,
641 nonzero,
642 }
643 }
644
645 #[allow(non_snake_case)]
646 fn synthesize(
647 &self,
648 config: Self::Config,
649 mut layouter: impl Layouter<pallas::Base>,
650 ) -> Result<(), plonk::Error> {
651 SinsemillaChip::load(config.sinsemilla_config.clone(), &mut layouter)?;
659
660 AuthorityDecrementChip::load_table(&config.authority_decrement, &mut layouter)?;
662
663 let ecc_chip = config.ecc_chip();
665
666 let voting_round_id = layouter.assign_region(
675 || "copy voting_round_id from instance",
676 |mut region| {
677 region.assign_advice_from_instance(
678 || "voting_round_id",
679 config.primary,
680 VOTING_ROUND_ID_PUBLIC_OFFSET,
681 config.advices[0],
682 0,
683 )
684 },
685 )?;
686 let voting_round_id_cond12 = voting_round_id.clone();
689
690 let vpk_g_d_point = NonIdentityPoint::new(
694 ecc_chip.clone(),
695 layouter.namespace(|| "witness vpk_g_d"),
696 self.vpk_g_d.map(|p| p),
697 )?;
698 let vpk_g_d = vpk_g_d_point.extract_p().inner().clone();
699
700 let vpk_pk_d_point = NonIdentityPoint::new(
703 ecc_chip.clone(),
704 layouter.namespace(|| "witness vpk_pk_d"),
705 self.vpk_pk_d.map(|p| p),
706 )?;
707 let vpk_pk_d = vpk_pk_d_point.extract_p().inner().clone();
708
709 let total_note_value = assign_free_advice(
710 layouter.namespace(|| "witness total_note_value"),
711 config.advices[0],
712 self.total_note_value,
713 )?;
714
715 let proposal_authority_old = assign_free_advice(
716 layouter.namespace(|| "witness proposal_authority_old"),
717 config.advices[0],
718 self.proposal_authority_old,
719 )?;
720
721 let van_comm_rand = assign_free_advice(
722 layouter.namespace(|| "witness van_comm_rand"),
723 config.advices[0],
724 self.van_comm_rand,
725 )?;
726
727 let vote_authority_note_old = assign_free_advice(
728 layouter.namespace(|| "witness vote_authority_note_old"),
729 config.advices[0],
730 self.vote_authority_note_old,
731 )?;
732
733 let domain_van = layouter.assign_region(
736 || "DOMAIN_VAN constant",
737 |mut region| {
738 region.assign_advice_from_constant(
739 || "domain_van",
740 config.advices[0],
741 0,
742 pallas::Base::from(DOMAIN_VAN),
743 )
744 },
745 )?;
746
747 let vsk_nk = assign_free_advice(
757 layouter.namespace(|| "witness vsk_nk"),
758 config.advices[0],
759 self.vsk_nk,
760 )?;
761
762 let vote_authority_note_old_cond1 = vote_authority_note_old.clone();
771 let voting_round_id_cond4 = voting_round_id.clone();
772 let domain_van_cond6 = domain_van.clone();
773 let vpk_g_d_cond6 = vpk_g_d.clone();
774 let vpk_pk_d_cond6 = vpk_pk_d.clone();
775 let total_note_value_cond6 = total_note_value.clone();
776 let total_note_value_cond8 = total_note_value.clone();
777 let voting_round_id_cond6 = voting_round_id.clone();
778 let van_comm_rand_cond6 = van_comm_rand.clone();
779 let vsk_nk_cond4 = vsk_nk.clone();
780
781 let derived_van = van_integrity::van_integrity_poseidon(
789 &config.poseidon_config,
790 &mut layouter,
791 "Old VAN integrity",
792 domain_van,
793 vpk_g_d,
794 vpk_pk_d,
795 total_note_value,
796 voting_round_id,
797 proposal_authority_old.clone(),
798 van_comm_rand,
799 )?;
800
801 layouter.assign_region(
803 || "VAN integrity check",
804 |mut region| region.constrain_equal(derived_van.cell(), vote_authority_note_old.cell()),
805 )?;
806
807 let vsk_scalar = ScalarFixed::new(
813 ecc_chip.clone(),
814 layouter.namespace(|| "cond3 vsk"),
815 self.vsk,
816 )?;
817 let vsk_ak_point = spend_auth_g_mul(
818 ecc_chip.clone(),
819 layouter.namespace(|| "cond3 [vsk]G"),
820 "cond3: [vsk] SpendAuthG",
821 vsk_scalar,
822 )?;
823 let ak = vsk_ak_point.extract_p().inner().clone();
824 let rivk_v_scalar = ScalarFixed::new(
825 ecc_chip.clone(),
826 layouter.namespace(|| "cond3 rivk_v"),
827 self.rivk_v,
828 )?;
829 prove_address_ownership(
830 config.sinsemilla_chip(),
831 ecc_chip.clone(),
832 config.commit_ivk_chip(),
833 layouter.namespace(|| "cond3 address"),
834 "cond3",
835 ak,
836 vsk_nk.clone(),
837 rivk_v_scalar,
838 &vpk_g_d_point,
839 &vpk_pk_d_point,
840 )?;
841
842 crate::circuit::spend_authority::prove_spend_authority(
854 ecc_chip.clone(),
855 layouter.namespace(|| "cond4 spend authority"),
856 self.alpha_v,
857 &vsk_ak_point,
858 config.primary,
859 R_VPK_X_PUBLIC_OFFSET,
860 R_VPK_Y_PUBLIC_OFFSET,
861 )?;
862
863 {
881 let root = synthesize_poseidon_merkle_path::<VOTE_COMM_TREE_DEPTH>(
882 &config.merkle_swap,
883 &config.poseidon_config,
884 &mut layouter,
885 config.advices[0],
886 vote_authority_note_old_cond1,
887 self.vote_comm_tree_position,
888 self.vote_comm_tree_path,
889 "cond1: merkle",
890 )?;
891
892 layouter.constrain_instance(
896 root.cell(),
897 config.primary,
898 VOTE_COMM_TREE_ROOT_PUBLIC_OFFSET,
899 )?;
900 }
901
902 let domain_van_nf = layouter.assign_region(
912 || "DOMAIN_VAN_NULLIFIER constant",
913 |mut region| {
914 region.assign_advice_from_constant(
915 || "domain_van_nullifier",
916 config.advices[0],
917 0,
918 domain_van_nullifier(),
919 )
920 },
921 )?;
922
923 let van_nullifier = {
935 let hasher = PoseidonHash::<
936 pallas::Base,
937 _,
938 poseidon::P128Pow5T3,
939 ConstantLength<4>,
940 3, 2, >::init(
943 config.poseidon_chip(),
944 layouter.namespace(|| "VAN nullifier Poseidon init"),
945 )?;
946 hasher.hash(
947 layouter.namespace(|| "Poseidon(vsk_nk, domain, round_id, van_old)"),
948 [
949 vsk_nk_cond4,
950 domain_van_nf,
951 voting_round_id_cond4,
952 vote_authority_note_old,
953 ],
954 )?
955 };
956
957 layouter.constrain_instance(
961 van_nullifier.cell(),
962 config.primary,
963 VAN_NULLIFIER_PUBLIC_OFFSET,
964 )?;
965
966 let proposal_id = layouter.assign_region(
978 || "copy proposal_id from instance",
979 |mut region| {
980 region.assign_advice_from_instance(
981 || "proposal_id",
982 config.primary,
983 PROPOSAL_ID_PUBLIC_OFFSET,
984 config.advices[0],
985 0,
986 )
987 },
988 )?;
989
990 let proposal_authority_new = AuthorityDecrementChip::assign(
991 &config.authority_decrement,
992 &mut layouter,
993 proposal_id.clone(),
994 proposal_authority_old,
995 self.one_shifted,
996 )?;
997
998 let derived_van_new = van_integrity::van_integrity_poseidon(
1007 &config.poseidon_config,
1008 &mut layouter,
1009 "New VAN integrity",
1010 domain_van_cond6,
1011 vpk_g_d_cond6,
1012 vpk_pk_d_cond6,
1013 total_note_value_cond6,
1014 voting_round_id_cond6,
1015 proposal_authority_new,
1016 van_comm_rand_cond6,
1017 )?;
1018
1019 layouter.constrain_instance(
1023 derived_van_new.cell(),
1024 config.primary,
1025 VOTE_AUTHORITY_NOTE_NEW_PUBLIC_OFFSET,
1026 )?;
1027
1028 let share_cells: [_; 16] = (0..16usize)
1046 .map(|i| {
1047 assign_free_advice(
1048 layouter.namespace(|| format!("witness share_{i}")),
1049 config.advices[0],
1050 self.shares[i],
1051 )
1052 })
1053 .collect::<Result<Vec<_>, _>>()?
1054 .try_into()
1055 .expect("always 16 elements");
1056
1057 let shares_sum = share_cells[1..].iter().enumerate().try_fold(
1059 share_cells[0].clone(),
1060 |acc, (i, share)| {
1061 config.add_chip().add(
1062 layouter.namespace(|| format!("shares sum step {}", i + 1)),
1063 &acc,
1064 share,
1065 )
1066 },
1067 )?;
1068
1069 layouter.assign_region(
1073 || "shares sum == total_note_value",
1074 |mut region| region.constrain_equal(shares_sum.cell(), total_note_value_cond8.cell()),
1075 )?;
1076
1077 for (i, cell) in share_cells.iter().enumerate() {
1113 config.range_check_config().copy_check(
1114 layouter.namespace(|| format!("share_{i} < 2^30")),
1115 cell.clone(),
1116 3, true, )?;
1119 }
1120
1121 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 domain_share_comm =
1152 share_commitment::assign_domain_share_comm(&mut layouter, config.advices[0])?;
1153
1154 let enc_c1: [AssignedCell<pallas::Base, pallas::Base>; 16] = (0..16)
1155 .map(|i| {
1156 assign_free_advice(
1157 layouter.namespace(|| format!("witness enc_c1_x[{i}]")),
1158 config.advices[0],
1159 self.enc_share_c1_x[i],
1160 )
1161 })
1162 .collect::<Result<Vec<_>, _>>()?
1163 .try_into()
1164 .expect("always 16 elements");
1165
1166 let enc_c2: [AssignedCell<pallas::Base, pallas::Base>; 16] = (0..16)
1167 .map(|i| {
1168 assign_free_advice(
1169 layouter.namespace(|| format!("witness enc_c2_x[{i}]")),
1170 config.advices[0],
1171 self.enc_share_c2_x[i],
1172 )
1173 })
1174 .collect::<Result<Vec<_>, _>>()?
1175 .try_into()
1176 .expect("always 16 elements");
1177
1178 let enc_c1_y: [AssignedCell<pallas::Base, pallas::Base>; 16] = (0..16)
1179 .map(|i| {
1180 assign_free_advice(
1181 layouter.namespace(|| format!("witness enc_c1_y[{i}]")),
1182 config.advices[0],
1183 self.enc_share_c1_y[i],
1184 )
1185 })
1186 .collect::<Result<Vec<_>, _>>()?
1187 .try_into()
1188 .expect("always 16 elements");
1189
1190 let enc_c2_y: [AssignedCell<pallas::Base, pallas::Base>; 16] = (0..16)
1191 .map(|i| {
1192 assign_free_advice(
1193 layouter.namespace(|| format!("witness enc_c2_y[{i}]")),
1194 config.advices[0],
1195 self.enc_share_c2_y[i],
1196 )
1197 })
1198 .collect::<Result<Vec<_>, _>>()?
1199 .try_into()
1200 .expect("always 16 elements");
1201
1202 let enc_c1_cond11: [AssignedCell<pallas::Base, pallas::Base>; 16] =
1204 core::array::from_fn(|i| enc_c1[i].clone());
1205 let enc_c2_cond11: [AssignedCell<pallas::Base, pallas::Base>; 16] =
1206 core::array::from_fn(|i| enc_c2[i].clone());
1207 let enc_c1_y_cond11: [AssignedCell<pallas::Base, pallas::Base>; 16] =
1208 core::array::from_fn(|i| enc_c1_y[i].clone());
1209 let enc_c2_y_cond11: [AssignedCell<pallas::Base, pallas::Base>; 16] =
1210 core::array::from_fn(|i| enc_c2_y[i].clone());
1211
1212 let shares_hash = compute_shares_hash_in_circuit(
1213 || config.poseidon_chip(),
1214 layouter.namespace(|| "cond10: shares hash"),
1215 domain_share_comm,
1216 blinds,
1217 enc_c1,
1218 enc_c2,
1219 enc_c1_y,
1220 enc_c2_y,
1221 )?;
1222
1223 {
1232 let r_cells: [_; 16] = (0..16usize)
1233 .map(|i| {
1234 assign_free_advice(
1235 layouter.namespace(|| format!("witness r[{i}]")),
1236 config.advices[0],
1237 self.share_randomness[i],
1238 )
1239 })
1240 .collect::<Result<Vec<_>, _>>()?
1241 .try_into()
1242 .expect("always 16 elements");
1243
1244 prove_elgamal_encryptions(
1245 ecc_chip.clone(),
1246 config.nonzero,
1247 layouter.namespace(|| "cond11 El Gamal"),
1248 "cond11",
1249 self.ea_pk,
1250 EaPkInstanceLoc {
1251 instance: config.primary,
1252 x_row: EA_PK_X_PUBLIC_OFFSET,
1253 y_row: EA_PK_Y_PUBLIC_OFFSET,
1254 },
1255 config.advices[0],
1256 share_cells,
1257 r_cells,
1258 enc_c1_cond11,
1259 enc_c2_cond11,
1260 enc_c1_y_cond11,
1261 enc_c2_y_cond11,
1262 )?;
1263 }
1264
1265 let domain_vc = layouter.assign_region(
1283 || "DOMAIN_VC constant",
1284 |mut region| {
1285 region.assign_advice_from_constant(
1286 || "domain_vc",
1287 config.advices[0],
1288 0,
1289 pallas::Base::from(DOMAIN_VC),
1290 )
1291 },
1292 )?;
1293
1294 let vote_decision = assign_free_advice(
1298 layouter.namespace(|| "witness vote_decision"),
1299 config.advices[0],
1300 self.vote_decision,
1301 )?;
1302
1303 let vote_commitment = vote_commitment::vote_commitment_poseidon(
1306 &config.poseidon_config,
1307 &mut layouter,
1308 "cond12",
1309 domain_vc,
1310 voting_round_id_cond12,
1311 shares_hash,
1312 proposal_id,
1313 vote_decision,
1314 )?;
1315
1316 layouter.constrain_instance(
1318 vote_commitment.cell(),
1319 config.primary,
1320 VOTE_COMMITMENT_PUBLIC_OFFSET,
1321 )?;
1322
1323 Ok(())
1324 }
1325}
1326
1327#[derive(Clone, Debug)]
1347pub struct Instance {
1348 pub van_nullifier: pallas::Base,
1350 pub r_vpk_x: pallas::Base,
1352 pub r_vpk_y: pallas::Base,
1354 pub vote_authority_note_new: pallas::Base,
1356 pub vote_commitment: pallas::Base,
1358 pub vote_comm_tree_root: pallas::Base,
1360 pub vote_comm_tree_anchor_height: pallas::Base,
1366 pub proposal_id: pallas::Base,
1372 pub voting_round_id: pallas::Base,
1377 pub ea_pk_x: pallas::Base,
1383 pub ea_pk_y: pallas::Base,
1388}
1389
1390impl Instance {
1391 pub const NUM_PUBLIC_INPUTS: usize = 11;
1393
1394 pub fn from_parts(
1408 van_nullifier: pallas::Base,
1409 r_vpk_x: pallas::Base,
1410 r_vpk_y: pallas::Base,
1411 vote_authority_note_new: pallas::Base,
1412 vote_commitment: pallas::Base,
1413 vote_comm_tree_root: pallas::Base,
1414 vote_comm_tree_anchor_height: pallas::Base,
1415 proposal_id: pallas::Base,
1416 voting_round_id: pallas::Base,
1417 ea_pk_x: pallas::Base,
1418 ea_pk_y: pallas::Base,
1419 ) -> Self {
1420 Instance {
1421 van_nullifier,
1422 r_vpk_x,
1423 r_vpk_y,
1424 vote_authority_note_new,
1425 vote_commitment,
1426 vote_comm_tree_root,
1427 vote_comm_tree_anchor_height,
1428 proposal_id,
1429 voting_round_id,
1430 ea_pk_x,
1431 ea_pk_y,
1432 }
1433 }
1434
1435 pub fn to_halo2_instance(&self) -> Vec<vesta::Scalar> {
1441 vec![
1442 self.van_nullifier,
1443 self.r_vpk_x,
1444 self.r_vpk_y,
1445 self.vote_authority_note_new,
1446 self.vote_commitment,
1447 self.vote_comm_tree_root,
1448 self.vote_comm_tree_anchor_height,
1449 self.proposal_id,
1450 self.voting_round_id,
1451 self.ea_pk_x,
1452 self.ea_pk_y,
1453 ]
1454 }
1455}
1456
1457#[cfg(test)]
1462mod tests {
1463 use super::*;
1464 use crate::circuit::elgamal::{base_to_scalar, elgamal_encrypt, spend_auth_g_affine};
1465 use core::iter;
1466 use ff::{Field, PrimeField};
1467 use group::ff::PrimeFieldBits;
1468 use group::{Curve, Group};
1469 use halo2_gadgets::sinsemilla::primitives::CommitDomain;
1470 use halo2_proofs::dev::MockProver;
1471 use pasta_curves::arithmetic::CurveAffine;
1472 use pasta_curves::pallas;
1473 use rand::rngs::OsRng;
1474
1475 use orchard::constants::{fixed_bases::COMMIT_IVK_PERSONALIZATION, L_ORCHARD_BASE};
1476
1477 fn generate_ea_keypair() -> (pallas::Scalar, pallas::Point, pallas::Affine) {
1480 let ea_sk = pallas::Scalar::from(42u64);
1481 let g = pallas::Point::from(spend_auth_g_affine());
1482 let ea_pk = g * ea_sk;
1483 let ea_pk_affine = ea_pk.to_affine();
1484 (ea_sk, ea_pk, ea_pk_affine)
1485 }
1486
1487 fn encrypt_shares(
1496 shares: [u64; 16],
1497 ea_pk: pallas::Point,
1498 ) -> (
1499 [pallas::Base; 16],
1500 [pallas::Base; 16],
1501 [pallas::Base; 16],
1502 [pallas::Base; 16],
1503 [pallas::Base; 16],
1504 [pallas::Base; 16],
1505 pallas::Base,
1506 ) {
1507 let mut c1_x = [pallas::Base::zero(); 16];
1508 let mut c2_x = [pallas::Base::zero(); 16];
1509 let mut c1_y = [pallas::Base::zero(); 16];
1510 let mut c2_y = [pallas::Base::zero(); 16];
1511 let randomness: [pallas::Base; 16] =
1513 core::array::from_fn(|i| pallas::Base::from((i as u64 + 1) * 101));
1514 let share_blinds: [pallas::Base; 16] =
1516 core::array::from_fn(|i| pallas::Base::from(1001u64 + i as u64));
1517 for i in 0..16 {
1518 let (cx1, cx2, cy1, cy2) =
1519 elgamal_encrypt(pallas::Base::from(shares[i]), randomness[i], ea_pk)
1520 .expect("test encryption inputs should be valid");
1521 c1_x[i] = cx1;
1522 c2_x[i] = cx2;
1523 c1_y[i] = cy1;
1524 c2_y[i] = cy2;
1525 }
1526 let hash = shares_hash(share_blinds, c1_x, c2_x, c1_y, c2_y);
1527 (c1_x, c2_x, c1_y, c2_y, randomness, share_blinds, hash)
1528 }
1529
1530 fn derive_voting_address(
1543 vsk: pallas::Scalar,
1544 nk: pallas::Base,
1545 rivk_v: pallas::Scalar,
1546 ) -> (pallas::Affine, pallas::Affine) {
1547 let g = pallas::Point::from(spend_auth_g_affine());
1549 let ak_point = g * vsk;
1550 let ak_x = *ak_point.to_affine().coordinates().unwrap().x();
1551
1552 let domain = CommitDomain::new(COMMIT_IVK_PERSONALIZATION);
1554 let ivk_v = domain
1555 .short_commit(
1556 iter::empty()
1557 .chain(ak_x.to_le_bits().iter().by_vals().take(L_ORCHARD_BASE))
1558 .chain(nk.to_le_bits().iter().by_vals().take(L_ORCHARD_BASE)),
1559 &rivk_v,
1560 )
1561 .expect("CommitIvk should not produce ⊥ for random inputs");
1562
1563 let g_d = pallas::Point::generator() * pallas::Scalar::from(12345u64);
1567 let g_d_affine = g_d.to_affine();
1568
1569 let ivk_v_scalar = base_to_scalar(ivk_v).expect("ivk_v must be < scalar field modulus");
1571 let pk_d = g_d * ivk_v_scalar;
1572 let pk_d_affine = pk_d.to_affine();
1573
1574 (g_d_affine, pk_d_affine)
1575 }
1576
1577 const TEST_PROPOSAL_ID: u64 = 3;
1579 const TEST_VOTE_DECISION: u64 = 1;
1580
1581 fn set_condition_12(
1589 circuit: &mut Circuit,
1590 shares_hash_val: pallas::Base,
1591 proposal_id: u64,
1592 voting_round_id: pallas::Base,
1593 ) -> pallas::Base {
1594 let proposal_id_base = pallas::Base::from(proposal_id);
1595 let vote_decision = pallas::Base::from(TEST_VOTE_DECISION);
1596 circuit.vote_decision = Value::known(vote_decision);
1597 vote_commitment_hash(
1598 voting_round_id,
1599 shares_hash_val,
1600 proposal_id_base,
1601 vote_decision,
1602 )
1603 }
1604
1605 fn build_single_leaf_merkle_path(
1610 leaf: pallas::Base,
1611 ) -> ([pallas::Base; VOTE_COMM_TREE_DEPTH], u32, pallas::Base) {
1612 let auth_path = empty_vote_comm_tree_path();
1613 let mut current = leaf;
1614 for i in 0..VOTE_COMM_TREE_DEPTH {
1615 current = poseidon_hash_2(current, auth_path[i]);
1616 }
1617 (auth_path, 0, current)
1618 }
1619
1620 fn empty_vote_comm_tree_path() -> [pallas::Base; VOTE_COMM_TREE_DEPTH] {
1621 let mut empty_roots = [pallas::Base::zero(); VOTE_COMM_TREE_DEPTH];
1622 empty_roots[0] = poseidon_hash_2(pallas::Base::zero(), pallas::Base::zero());
1623 for i in 1..VOTE_COMM_TREE_DEPTH {
1624 empty_roots[i] = poseidon_hash_2(empty_roots[i - 1], empty_roots[i - 1]);
1625 }
1626 empty_roots
1627 }
1628
1629 fn build_left_leaf_merkle_path_with_sibling(
1630 left_leaf: pallas::Base,
1631 right_leaf: pallas::Base,
1632 ) -> ([pallas::Base; VOTE_COMM_TREE_DEPTH], u32, pallas::Base) {
1633 let mut auth_path = empty_vote_comm_tree_path();
1634 auth_path[0] = right_leaf;
1635
1636 let mut current = left_leaf;
1637 for i in 0..VOTE_COMM_TREE_DEPTH {
1638 current = poseidon_hash_2(current, auth_path[i]);
1639 }
1640 (auth_path, 0, current)
1641 }
1642
1643 struct VoteReuseFixture {
1644 vsk: pallas::Scalar,
1645 vsk_nk: pallas::Base,
1646 rivk_v: pallas::Scalar,
1647 alpha_v: pallas::Scalar,
1648 vpk_g_d_affine: pallas::Affine,
1649 vpk_pk_d_affine: pallas::Affine,
1650 total_note_value: pallas::Base,
1651 proposal_authority_old: pallas::Base,
1652 proposal_id: u64,
1653 van_comm_rand: pallas::Base,
1654 shares_u64: [u64; 16],
1655 ea_pk_point: pallas::Point,
1656 ea_pk_affine: pallas::Affine,
1657 }
1658
1659 impl VoteReuseFixture {
1660 fn new() -> Self {
1661 let mut rng = OsRng;
1662 let vsk = pallas::Scalar::random(&mut rng);
1663 let vsk_nk = pallas::Base::random(&mut rng);
1664 let rivk_v = pallas::Scalar::random(&mut rng);
1665 let alpha_v = pallas::Scalar::random(&mut rng);
1666 let (vpk_g_d_affine, vpk_pk_d_affine) = derive_voting_address(vsk, vsk_nk, rivk_v);
1667 let (_ea_sk, ea_pk_point, ea_pk_affine) = generate_ea_keypair();
1668
1669 Self {
1670 vsk,
1671 vsk_nk,
1672 rivk_v,
1673 alpha_v,
1674 vpk_g_d_affine,
1675 vpk_pk_d_affine,
1676 total_note_value: pallas::Base::from(10_000u64),
1677 proposal_authority_old: pallas::Base::from(13u64),
1678 proposal_id: TEST_PROPOSAL_ID,
1679 van_comm_rand: pallas::Base::random(&mut rng),
1680 shares_u64: [625; 16],
1681 ea_pk_point,
1682 ea_pk_affine,
1683 }
1684 }
1685
1686 fn vpk_x_coordinates(&self) -> (pallas::Base, pallas::Base) {
1687 (
1688 *self.vpk_g_d_affine.coordinates().unwrap().x(),
1689 *self.vpk_pk_d_affine.coordinates().unwrap().x(),
1690 )
1691 }
1692
1693 fn vote_authority_note_old(&self, voting_round_id: pallas::Base) -> pallas::Base {
1694 let (vpk_g_d_x, vpk_pk_d_x) = self.vpk_x_coordinates();
1695 van_integrity_hash(
1696 vpk_g_d_x,
1697 vpk_pk_d_x,
1698 self.total_note_value,
1699 voting_round_id,
1700 self.proposal_authority_old,
1701 self.van_comm_rand,
1702 )
1703 }
1704
1705 fn vote_authority_note_new(&self, voting_round_id: pallas::Base) -> pallas::Base {
1706 let (vpk_g_d_x, vpk_pk_d_x) = self.vpk_x_coordinates();
1707 let proposal_authority_new =
1708 self.proposal_authority_old - pallas::Base::from(1u64 << self.proposal_id);
1709 van_integrity_hash(
1710 vpk_g_d_x,
1711 vpk_pk_d_x,
1712 self.total_note_value,
1713 voting_round_id,
1714 proposal_authority_new,
1715 self.van_comm_rand,
1716 )
1717 }
1718
1719 fn build_vote_data(
1720 &self,
1721 voting_round_id: pallas::Base,
1722 auth_path: [pallas::Base; VOTE_COMM_TREE_DEPTH],
1723 position: u32,
1724 vote_comm_tree_root: pallas::Base,
1725 anchor_height: u64,
1726 ) -> (Circuit, Instance) {
1727 let vote_authority_note_old = self.vote_authority_note_old(voting_round_id);
1728 let vote_authority_note_new = self.vote_authority_note_new(voting_round_id);
1729 let van_nullifier =
1730 van_nullifier_hash(self.vsk_nk, voting_round_id, vote_authority_note_old);
1731
1732 let g = pallas::Point::from(spend_auth_g_affine());
1733 let r_vpk = (g * self.vsk + g * self.alpha_v).to_affine();
1734 let r_vpk_x = *r_vpk.coordinates().unwrap().x();
1735 let r_vpk_y = *r_vpk.coordinates().unwrap().y();
1736
1737 let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
1738 encrypt_shares(self.shares_u64, self.ea_pk_point);
1739
1740 let mut circuit = Circuit::with_van_witnesses(
1741 Value::known(auth_path),
1742 Value::known(position),
1743 Value::known(self.vpk_g_d_affine),
1744 Value::known(self.vpk_pk_d_affine),
1745 Value::known(self.total_note_value),
1746 Value::known(self.proposal_authority_old),
1747 Value::known(self.van_comm_rand),
1748 Value::known(vote_authority_note_old),
1749 Value::known(self.vsk),
1750 Value::known(self.rivk_v),
1751 Value::known(self.vsk_nk),
1752 Value::known(self.alpha_v),
1753 );
1754 circuit.one_shifted = Value::known(pallas::Base::from(1u64 << self.proposal_id));
1755 circuit.shares = self.shares_u64.map(|s| Value::known(pallas::Base::from(s)));
1756 circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
1757 circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
1758 circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
1759 circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
1760 circuit.share_blinds = share_blinds.map(Value::known);
1761 circuit.share_randomness = randomness.map(Value::known);
1762 circuit.ea_pk = Value::known(self.ea_pk_affine);
1763 let vote_commitment = set_condition_12(
1764 &mut circuit,
1765 shares_hash_val,
1766 self.proposal_id,
1767 voting_round_id,
1768 );
1769
1770 let instance = Instance::from_parts(
1771 van_nullifier,
1772 r_vpk_x,
1773 r_vpk_y,
1774 vote_authority_note_new,
1775 vote_commitment,
1776 vote_comm_tree_root,
1777 pallas::Base::from(anchor_height),
1778 pallas::Base::from(self.proposal_id),
1779 voting_round_id,
1780 *self.ea_pk_affine.coordinates().unwrap().x(),
1781 *self.ea_pk_affine.coordinates().unwrap().y(),
1782 );
1783
1784 (circuit, instance)
1785 }
1786 }
1787
1788 fn make_test_data_with_authority_proposal_and_alpha(
1792 proposal_authority_old: pallas::Base,
1793 proposal_id: u64,
1794 alpha_v_override: Option<pallas::Scalar>,
1795 ) -> (Circuit, Instance) {
1796 let mut rng = OsRng;
1797
1798 let vsk = pallas::Scalar::random(&mut rng);
1801 let vsk_nk = pallas::Base::random(&mut rng);
1802 let rivk_v = pallas::Scalar::random(&mut rng);
1803 let alpha_v = alpha_v_override.unwrap_or_else(|| pallas::Scalar::random(&mut rng));
1804
1805 let (vpk_g_d_affine, vpk_pk_d_affine) = derive_voting_address(vsk, vsk_nk, rivk_v);
1806
1807 let g = pallas::Point::from(spend_auth_g_affine());
1809 let ak_point = g * vsk;
1810 let r_vpk = (ak_point + g * alpha_v).to_affine();
1811 let r_vpk_x = *r_vpk.coordinates().unwrap().x();
1812 let r_vpk_y = *r_vpk.coordinates().unwrap().y();
1813
1814 let vpk_g_d_x = *vpk_g_d_affine.coordinates().unwrap().x();
1816 let vpk_pk_d_x = *vpk_pk_d_affine.coordinates().unwrap().x();
1817
1818 let total_note_value = pallas::Base::from(10_000u64);
1821 let voting_round_id = pallas::Base::random(&mut rng);
1822 let van_comm_rand = pallas::Base::random(&mut rng);
1823
1824 let vote_authority_note_old = van_integrity_hash(
1825 vpk_g_d_x,
1826 vpk_pk_d_x,
1827 total_note_value,
1828 voting_round_id,
1829 proposal_authority_old,
1830 van_comm_rand,
1831 );
1832 let (auth_path, position, vote_comm_tree_root) =
1833 build_single_leaf_merkle_path(vote_authority_note_old);
1834 let van_nullifier = van_nullifier_hash(vsk_nk, voting_round_id, vote_authority_note_old);
1835 let one_shifted = pallas::Base::from(1u64 << proposal_id);
1837 let proposal_authority_new = proposal_authority_old - one_shifted;
1838 let vote_authority_note_new = van_integrity_hash(
1839 vpk_g_d_x,
1840 vpk_pk_d_x,
1841 total_note_value,
1842 voting_round_id,
1843 proposal_authority_new,
1844 van_comm_rand,
1845 );
1846
1847 let shares_u64: [u64; 16] = [625; 16]; let (_ea_sk, ea_pk_point, ea_pk_affine) = generate_ea_keypair();
1853 let ea_pk_x = *ea_pk_affine.coordinates().unwrap().x();
1854 let ea_pk_y = *ea_pk_affine.coordinates().unwrap().y();
1855 let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
1856 encrypt_shares(shares_u64, ea_pk_point);
1857
1858 let mut circuit = Circuit::with_van_witnesses(
1859 Value::known(auth_path),
1860 Value::known(position),
1861 Value::known(vpk_g_d_affine),
1862 Value::known(vpk_pk_d_affine),
1863 Value::known(total_note_value),
1864 Value::known(proposal_authority_old),
1865 Value::known(van_comm_rand),
1866 Value::known(vote_authority_note_old),
1867 Value::known(vsk),
1868 Value::known(rivk_v),
1869 Value::known(vsk_nk),
1870 Value::known(alpha_v),
1871 );
1872 circuit.one_shifted = Value::known(one_shifted);
1873 circuit.shares = shares_u64.map(|s| Value::known(pallas::Base::from(s)));
1874 circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
1875 circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
1876 circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
1877 circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
1878 circuit.share_blinds = share_blinds.map(Value::known);
1879 circuit.share_randomness = randomness.map(Value::known);
1880 circuit.ea_pk = Value::known(ea_pk_affine);
1881
1882 let vote_commitment =
1884 set_condition_12(&mut circuit, shares_hash_val, proposal_id, voting_round_id);
1885
1886 let instance = Instance::from_parts(
1887 van_nullifier,
1888 r_vpk_x,
1889 r_vpk_y,
1890 vote_authority_note_new,
1891 vote_commitment,
1892 vote_comm_tree_root,
1893 pallas::Base::zero(),
1894 pallas::Base::from(proposal_id),
1895 voting_round_id,
1896 ea_pk_x,
1897 ea_pk_y,
1898 );
1899
1900 (circuit, instance)
1901 }
1902
1903 fn make_test_data_with_authority_and_proposal(
1904 proposal_authority_old: pallas::Base,
1905 proposal_id: u64,
1906 ) -> (Circuit, Instance) {
1907 make_test_data_with_authority_proposal_and_alpha(proposal_authority_old, proposal_id, None)
1908 }
1909
1910 fn make_test_data_with_authority(proposal_authority_old: pallas::Base) -> (Circuit, Instance) {
1911 make_test_data_with_authority_and_proposal(proposal_authority_old, TEST_PROPOSAL_ID)
1912 }
1913
1914 fn make_test_data() -> (Circuit, Instance) {
1915 make_test_data_with_authority(pallas::Base::from(13u64))
1918 }
1919
1920 #[test]
1925 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
1926 fn van_integrity_valid_proof() {
1927 let (circuit, instance) = make_test_data();
1928
1929 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
1930
1931 assert_eq!(prover.verify(), Ok(()));
1932 }
1933
1934 #[test]
1935 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
1936 fn van_integrity_wrong_hash_fails() {
1937 let mut rng = OsRng;
1938 let (_, mut instance) = make_test_data();
1939
1940 let wrong_van = pallas::Base::random(&mut rng);
1942 let (auth_path, position, root) = build_single_leaf_merkle_path(wrong_van);
1943 instance.vote_comm_tree_root = root;
1944
1945 let vsk = pallas::Scalar::random(&mut rng);
1948 let vsk_nk = pallas::Base::random(&mut rng);
1949 let rivk_v = pallas::Scalar::random(&mut rng);
1950 let alpha_v = pallas::Scalar::random(&mut rng);
1951 let (vpk_g_d_affine, vpk_pk_d_affine) = derive_voting_address(vsk, vsk_nk, rivk_v);
1952 let g = pallas::Point::from(spend_auth_g_affine());
1953 let r_vpk = (g * vsk + g * alpha_v).to_affine();
1954 instance.r_vpk_x = *r_vpk.coordinates().unwrap().x();
1955 instance.r_vpk_y = *r_vpk.coordinates().unwrap().y();
1956
1957 let shares_u64: [u64; 16] = [625; 16];
1958 let (_ea_sk, ea_pk_point, ea_pk_affine) = generate_ea_keypair();
1959 let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
1960 encrypt_shares(shares_u64, ea_pk_point);
1961
1962 let proposal_authority_old = pallas::Base::from(13u64);
1965 let van_comm_rand = pallas::Base::random(&mut rng);
1966 let mut circuit = Circuit::with_van_witnesses(
1967 Value::known(auth_path),
1968 Value::known(position),
1969 Value::known(vpk_g_d_affine),
1970 Value::known(vpk_pk_d_affine),
1971 Value::known(pallas::Base::from(10_000u64)),
1972 Value::known(proposal_authority_old),
1973 Value::known(van_comm_rand),
1974 Value::known(wrong_van),
1975 Value::known(vsk),
1976 Value::known(rivk_v),
1977 Value::known(vsk_nk),
1978 Value::known(alpha_v),
1979 );
1980 circuit.one_shifted = Value::known(pallas::Base::from(1u64 << TEST_PROPOSAL_ID));
1981 circuit.shares = shares_u64.map(|s| Value::known(pallas::Base::from(s)));
1982 circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
1983 circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
1984 circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
1985 circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
1986 circuit.share_blinds = share_blinds.map(Value::known);
1987 circuit.share_randomness = randomness.map(Value::known);
1988 circuit.ea_pk = Value::known(ea_pk_affine);
1989 let vc = set_condition_12(
1990 &mut circuit,
1991 shares_hash_val,
1992 TEST_PROPOSAL_ID,
1993 instance.voting_round_id,
1994 );
1995 instance.vote_commitment = vc;
1996 instance.proposal_id = pallas::Base::from(TEST_PROPOSAL_ID);
1997 instance.ea_pk_x = *ea_pk_affine.coordinates().unwrap().x();
1998 instance.ea_pk_y = *ea_pk_affine.coordinates().unwrap().y();
1999
2000 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2001 assert!(prover.verify().is_err());
2003 }
2004
2005 #[test]
2006 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2007 fn van_integrity_wrong_round_id_fails() {
2008 let (circuit, mut instance) = make_test_data();
2009
2010 instance.voting_round_id = pallas::Base::random(&mut OsRng);
2012
2013 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2014 assert!(prover.verify().is_err());
2017 }
2018
2019 #[test]
2020 fn round_scoped_van_redelegation_changes_nullifier() {
2021 let fixture = VoteReuseFixture::new();
2022 let round_1 = pallas::Base::from(0xCAFEu64);
2023 let round_2 = pallas::Base::from(0xCAFFu64);
2024
2025 let van_round_1 = fixture.vote_authority_note_old(round_1);
2026 let van_round_2 = fixture.vote_authority_note_old(round_2);
2027 assert_ne!(
2028 van_round_1, van_round_2,
2029 "voting_round_id is part of the VAN preimage"
2030 );
2031
2032 let nullifier_round_1 = van_nullifier_hash(fixture.vsk_nk, round_1, van_round_1);
2033 let nullifier_round_2 = van_nullifier_hash(fixture.vsk_nk, round_2, van_round_2);
2034 assert_ne!(
2035 nullifier_round_1, nullifier_round_2,
2036 "honest redelegation in a new round must not collide with the old round"
2037 );
2038 }
2039
2040 #[test]
2041 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2042 fn round_scoped_van_redelegation_verifies_with_distinct_nullifiers() {
2043 let fixture = VoteReuseFixture::new();
2044 let round_1 = pallas::Base::from(0xCAFEu64);
2045 let round_2 = pallas::Base::from(0xCAFFu64);
2046
2047 let van_round_1 = fixture.vote_authority_note_old(round_1);
2048 let (path_round_1, position_round_1, root_round_1) =
2049 build_single_leaf_merkle_path(van_round_1);
2050 let (circuit_round_1, instance_round_1) =
2051 fixture.build_vote_data(round_1, path_round_1, position_round_1, root_round_1, 10);
2052
2053 let van_round_2 = fixture.vote_authority_note_old(round_2);
2054 let (path_round_2, position_round_2, root_round_2) =
2055 build_single_leaf_merkle_path(van_round_2);
2056 let (circuit_round_2, instance_round_2) =
2057 fixture.build_vote_data(round_2, path_round_2, position_round_2, root_round_2, 20);
2058
2059 assert_ne!(van_round_1, van_round_2);
2060 assert_ne!(
2061 instance_round_1.van_nullifier,
2062 instance_round_2.van_nullifier
2063 );
2064
2065 let prover_round_1 = MockProver::run(
2066 K,
2067 &circuit_round_1,
2068 vec![instance_round_1.to_halo2_instance()],
2069 )
2070 .unwrap();
2071 assert_eq!(prover_round_1.verify(), Ok(()));
2072
2073 let prover_round_2 = MockProver::run(
2074 K,
2075 &circuit_round_2,
2076 vec![instance_round_2.to_halo2_instance()],
2077 )
2078 .unwrap();
2079 assert_eq!(prover_round_2.verify(), Ok(()));
2080 }
2081
2082 #[test]
2084 fn van_integrity_hash_deterministic() {
2085 let mut rng = OsRng;
2086
2087 let vpk_g_d = pallas::Base::random(&mut rng);
2088 let vpk_pk_d = pallas::Base::random(&mut rng);
2089 let val = pallas::Base::random(&mut rng);
2090 let round = pallas::Base::random(&mut rng);
2091 let auth = pallas::Base::random(&mut rng);
2092 let rand = pallas::Base::random(&mut rng);
2093
2094 let h1 = van_integrity_hash(vpk_g_d, vpk_pk_d, val, round, auth, rand);
2095 let h2 = van_integrity_hash(vpk_g_d, vpk_pk_d, val, round, auth, rand);
2096 assert_eq!(h1, h2);
2097
2098 let h3 = van_integrity_hash(
2100 pallas::Base::random(&mut rng),
2101 vpk_pk_d,
2102 val,
2103 round,
2104 auth,
2105 rand,
2106 );
2107 assert_ne!(h1, h3);
2108 }
2109
2110 #[test]
2122 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2123 fn condition_3_wrong_vsk_fails() {
2124 let mut rng = OsRng;
2125
2126 let vsk = pallas::Scalar::random(&mut rng);
2127 let vsk_nk = pallas::Base::random(&mut rng);
2128 let rivk_v = pallas::Scalar::random(&mut rng);
2129 let (vpk_g_d_affine, vpk_pk_d_affine) = derive_voting_address(vsk, vsk_nk, rivk_v);
2130 let vpk_g_d_x = *vpk_g_d_affine.coordinates().unwrap().x();
2131 let vpk_pk_d_x = *vpk_pk_d_affine.coordinates().unwrap().x();
2132
2133 let total_note_value = pallas::Base::from(10_000u64);
2134 let voting_round_id = pallas::Base::random(&mut rng);
2135 let proposal_authority_old = pallas::Base::from(13u64);
2136 let proposal_id = 3u64;
2137 let van_comm_rand = pallas::Base::random(&mut rng);
2138
2139 let vote_authority_note_old = van_integrity_hash(
2140 vpk_g_d_x,
2141 vpk_pk_d_x,
2142 total_note_value,
2143 voting_round_id,
2144 proposal_authority_old,
2145 van_comm_rand,
2146 );
2147 let (auth_path, position, vote_comm_tree_root) =
2148 build_single_leaf_merkle_path(vote_authority_note_old);
2149 let van_nullifier = van_nullifier_hash(vsk_nk, voting_round_id, vote_authority_note_old);
2150 let one_shifted = pallas::Base::from(1u64 << proposal_id);
2151 let proposal_authority_new = proposal_authority_old - one_shifted;
2152 let vote_authority_note_new = van_integrity_hash(
2153 vpk_g_d_x,
2154 vpk_pk_d_x,
2155 total_note_value,
2156 voting_round_id,
2157 proposal_authority_new,
2158 van_comm_rand,
2159 );
2160
2161 let shares_u64: [u64; 16] = [625; 16];
2162 let (_ea_sk, ea_pk_point, ea_pk_affine) = generate_ea_keypair();
2163 let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
2164 encrypt_shares(shares_u64, ea_pk_point);
2165
2166 let wrong_vsk = pallas::Scalar::random(&mut rng);
2167 assert_ne!(
2168 wrong_vsk, vsk,
2169 "test assumes distinct vsk with high probability"
2170 );
2171 let alpha_v = pallas::Scalar::random(&mut rng);
2172 let g = pallas::Point::from(spend_auth_g_affine());
2173 let r_vpk = (g * vsk + g * alpha_v).to_affine();
2174 let r_vpk_x = *r_vpk.coordinates().unwrap().x();
2175 let r_vpk_y = *r_vpk.coordinates().unwrap().y();
2176
2177 let mut circuit = Circuit::with_van_witnesses(
2178 Value::known(auth_path),
2179 Value::known(position),
2180 Value::known(vpk_g_d_affine),
2181 Value::known(vpk_pk_d_affine),
2182 Value::known(total_note_value),
2183 Value::known(proposal_authority_old),
2184 Value::known(van_comm_rand),
2185 Value::known(vote_authority_note_old),
2186 Value::known(wrong_vsk),
2187 Value::known(rivk_v),
2188 Value::known(vsk_nk),
2189 Value::known(alpha_v),
2190 );
2191 circuit.one_shifted = Value::known(one_shifted);
2192 circuit.shares = shares_u64.map(|s| Value::known(pallas::Base::from(s)));
2193 circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
2194 circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
2195 circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
2196 circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
2197 circuit.share_blinds = share_blinds.map(Value::known);
2198 circuit.share_randomness = randomness.map(Value::known);
2199 circuit.ea_pk = Value::known(ea_pk_affine);
2200 let vc = set_condition_12(&mut circuit, shares_hash_val, proposal_id, voting_round_id);
2201
2202 let instance = Instance::from_parts(
2203 van_nullifier,
2204 r_vpk_x,
2205 r_vpk_y,
2206 vote_authority_note_new,
2207 vc,
2208 vote_comm_tree_root,
2209 pallas::Base::zero(),
2210 pallas::Base::from(proposal_id),
2211 voting_round_id,
2212 *ea_pk_affine.coordinates().unwrap().x(),
2213 *ea_pk_affine.coordinates().unwrap().y(),
2214 );
2215
2216 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2217 assert!(
2218 prover.verify().is_err(),
2219 "condition 3 must reject wrong vsk"
2220 );
2221 }
2222
2223 #[test]
2227 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2228 fn condition_3_wrong_vpk_pk_d_fails() {
2229 let mut rng = OsRng;
2230
2231 let vsk = pallas::Scalar::random(&mut rng);
2232 let vsk_nk = pallas::Base::random(&mut rng);
2233 let rivk_v = pallas::Scalar::random(&mut rng);
2234 let (vpk_g_d_affine, _vpk_pk_d_correct) = derive_voting_address(vsk, vsk_nk, rivk_v);
2235 let vpk_g_d_x = *vpk_g_d_affine.coordinates().unwrap().x();
2236
2237 let wrong_vpk_pk_d_affine =
2238 (pallas::Point::generator() * pallas::Scalar::from(99999u64)).to_affine();
2239 let wrong_vpk_pk_d_x = *wrong_vpk_pk_d_affine.coordinates().unwrap().x();
2240
2241 let total_note_value = pallas::Base::from(10_000u64);
2242 let voting_round_id = pallas::Base::random(&mut rng);
2243 let proposal_authority_old = pallas::Base::from(13u64);
2244 let proposal_id = 3u64;
2245 let van_comm_rand = pallas::Base::random(&mut rng);
2246
2247 let vote_authority_note_old = van_integrity_hash(
2248 vpk_g_d_x,
2249 wrong_vpk_pk_d_x,
2250 total_note_value,
2251 voting_round_id,
2252 proposal_authority_old,
2253 van_comm_rand,
2254 );
2255 let (auth_path, position, vote_comm_tree_root) =
2256 build_single_leaf_merkle_path(vote_authority_note_old);
2257 let van_nullifier = van_nullifier_hash(vsk_nk, voting_round_id, vote_authority_note_old);
2258 let one_shifted = pallas::Base::from(1u64 << proposal_id);
2259 let proposal_authority_new = proposal_authority_old - one_shifted;
2260 let vote_authority_note_new = van_integrity_hash(
2261 vpk_g_d_x,
2262 wrong_vpk_pk_d_x,
2263 total_note_value,
2264 voting_round_id,
2265 proposal_authority_new,
2266 van_comm_rand,
2267 );
2268
2269 let shares_u64: [u64; 16] = [625; 16];
2270 let (_ea_sk, ea_pk_point, ea_pk_affine) = generate_ea_keypair();
2271 let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
2272 encrypt_shares(shares_u64, ea_pk_point);
2273
2274 let alpha_v = pallas::Scalar::random(&mut rng);
2275 let g = pallas::Point::from(spend_auth_g_affine());
2276 let r_vpk = (g * vsk + g * alpha_v).to_affine();
2277 let r_vpk_x = *r_vpk.coordinates().unwrap().x();
2278 let r_vpk_y = *r_vpk.coordinates().unwrap().y();
2279
2280 let mut circuit = Circuit::with_van_witnesses(
2281 Value::known(auth_path),
2282 Value::known(position),
2283 Value::known(vpk_g_d_affine),
2284 Value::known(wrong_vpk_pk_d_affine),
2285 Value::known(total_note_value),
2286 Value::known(proposal_authority_old),
2287 Value::known(van_comm_rand),
2288 Value::known(vote_authority_note_old),
2289 Value::known(vsk),
2290 Value::known(rivk_v),
2291 Value::known(vsk_nk),
2292 Value::known(alpha_v),
2293 );
2294 circuit.one_shifted = Value::known(one_shifted);
2295 circuit.shares = shares_u64.map(|s| Value::known(pallas::Base::from(s)));
2296 circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
2297 circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
2298 circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
2299 circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
2300 circuit.share_blinds = share_blinds.map(Value::known);
2301 circuit.share_randomness = randomness.map(Value::known);
2302 circuit.ea_pk = Value::known(ea_pk_affine);
2303 let vc = set_condition_12(&mut circuit, shares_hash_val, proposal_id, voting_round_id);
2304
2305 let instance = Instance::from_parts(
2306 van_nullifier,
2307 r_vpk_x,
2308 r_vpk_y,
2309 vote_authority_note_new,
2310 vc,
2311 vote_comm_tree_root,
2312 pallas::Base::zero(),
2313 pallas::Base::from(proposal_id),
2314 voting_round_id,
2315 *ea_pk_affine.coordinates().unwrap().x(),
2316 *ea_pk_affine.coordinates().unwrap().y(),
2317 );
2318
2319 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2320 assert!(
2321 prover.verify().is_err(),
2322 "condition 3 must reject wrong vpk_pk_d"
2323 );
2324 }
2325
2326 #[test]
2332 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2333 fn condition_4_wrong_r_vpk_fails() {
2334 let (circuit, mut instance) = make_test_data();
2335
2336 instance.r_vpk_x = pallas::Base::random(&mut OsRng);
2337
2338 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2339 assert!(
2340 prover.verify().is_err(),
2341 "condition 4 must reject wrong r_vpk"
2342 );
2343 }
2344
2345 #[test]
2350 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2351 fn condition_4_alpha_zero_is_accepted_by_relation() {
2352 let (circuit, instance) = make_test_data_with_authority_proposal_and_alpha(
2353 pallas::Base::from(13u64),
2354 TEST_PROPOSAL_ID,
2355 Some(pallas::Scalar::zero()),
2356 );
2357
2358 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2359 assert_eq!(prover.verify(), Ok(()));
2360 }
2361
2362 #[test]
2368 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2369 fn van_nullifier_wrong_public_input_fails() {
2370 let (circuit, mut instance) = make_test_data();
2371
2372 instance.van_nullifier = pallas::Base::random(&mut OsRng);
2374
2375 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2376
2377 assert!(prover.verify().is_err());
2379 }
2380
2381 #[test]
2387 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2388 fn van_nullifier_wrong_vsk_nk_fails() {
2389 let mut rng = OsRng;
2390
2391 let vsk = pallas::Scalar::random(&mut rng);
2393 let vsk_nk = pallas::Base::random(&mut rng);
2394 let rivk_v = pallas::Scalar::random(&mut rng);
2395 let (vpk_g_d_affine, vpk_pk_d_affine) = derive_voting_address(vsk, vsk_nk, rivk_v);
2396 let vpk_g_d_x = *vpk_g_d_affine.coordinates().unwrap().x();
2397 let vpk_pk_d_x = *vpk_pk_d_affine.coordinates().unwrap().x();
2398
2399 let total_note_value = pallas::Base::from(10_000u64);
2400 let voting_round_id = pallas::Base::random(&mut rng);
2401 let proposal_authority_old = pallas::Base::from(5u64); let van_comm_rand = pallas::Base::random(&mut rng);
2403 let proposal_id = 0u64; let vote_authority_note_old = van_integrity_hash(
2406 vpk_g_d_x,
2407 vpk_pk_d_x,
2408 total_note_value,
2409 voting_round_id,
2410 proposal_authority_old,
2411 van_comm_rand,
2412 );
2413 let (auth_path, position, vote_comm_tree_root) =
2414 build_single_leaf_merkle_path(vote_authority_note_old);
2415 let van_nullifier = van_nullifier_hash(vsk_nk, voting_round_id, vote_authority_note_old);
2416 let one_shifted = pallas::Base::from(1u64 << proposal_id);
2417 let proposal_authority_new = proposal_authority_old - one_shifted;
2418 let vote_authority_note_new = van_integrity_hash(
2419 vpk_g_d_x,
2420 vpk_pk_d_x,
2421 total_note_value,
2422 voting_round_id,
2423 proposal_authority_new,
2424 van_comm_rand,
2425 );
2426
2427 let wrong_vsk_nk = pallas::Base::random(&mut rng);
2429 let alpha_v = pallas::Scalar::random(&mut rng);
2430 let g = pallas::Point::from(spend_auth_g_affine());
2431 let r_vpk = (g * vsk + g * alpha_v).to_affine();
2432 let r_vpk_x = *r_vpk.coordinates().unwrap().x();
2433 let r_vpk_y = *r_vpk.coordinates().unwrap().y();
2434
2435 let shares_u64: [u64; 16] = [625; 16];
2437
2438 let (_ea_sk, ea_pk_point, ea_pk_affine) = generate_ea_keypair();
2440 let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
2441 encrypt_shares(shares_u64, ea_pk_point);
2442
2443 let mut circuit = Circuit::with_van_witnesses(
2444 Value::known(auth_path),
2445 Value::known(position),
2446 Value::known(vpk_g_d_affine),
2447 Value::known(vpk_pk_d_affine),
2448 Value::known(total_note_value),
2449 Value::known(proposal_authority_old),
2450 Value::known(van_comm_rand),
2451 Value::known(vote_authority_note_old),
2452 Value::known(vsk),
2453 Value::known(rivk_v),
2454 Value::known(wrong_vsk_nk),
2455 Value::known(alpha_v),
2456 );
2457 circuit.one_shifted = Value::known(one_shifted);
2458 circuit.shares = shares_u64.map(|s| Value::known(pallas::Base::from(s)));
2459 circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
2460 circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
2461 circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
2462 circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
2463 circuit.share_blinds = share_blinds.map(Value::known);
2464 circuit.share_randomness = randomness.map(Value::known);
2465 circuit.ea_pk = Value::known(ea_pk_affine);
2466 let vc = set_condition_12(&mut circuit, shares_hash_val, proposal_id, voting_round_id);
2467
2468 let instance = Instance::from_parts(
2469 van_nullifier,
2470 r_vpk_x,
2471 r_vpk_y,
2472 vote_authority_note_new,
2473 vc,
2474 vote_comm_tree_root,
2475 pallas::Base::zero(),
2476 pallas::Base::from(proposal_id),
2477 voting_round_id,
2478 *ea_pk_affine.coordinates().unwrap().x(),
2479 *ea_pk_affine.coordinates().unwrap().y(),
2480 );
2481
2482 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2483 assert!(prover.verify().is_err());
2487 }
2488
2489 #[test]
2491 fn van_nullifier_hash_deterministic() {
2492 let mut rng = OsRng;
2493
2494 let nk = pallas::Base::random(&mut rng);
2495 let round = pallas::Base::random(&mut rng);
2496 let van = pallas::Base::random(&mut rng);
2497
2498 let h1 = van_nullifier_hash(nk, round, van);
2499 let h2 = van_nullifier_hash(nk, round, van);
2500 assert_eq!(h1, h2);
2501
2502 let h3 = van_nullifier_hash(pallas::Base::random(&mut rng), round, van);
2504 assert_ne!(h1, h3);
2505 }
2506
2507 #[test]
2508 fn van_nullifier_hash_frozen_vector() {
2509 assert_eq!(
2510 van_nullifier_hash(
2511 pallas::Base::from(1u64),
2512 pallas::Base::from(42u64),
2513 pallas::Base::from(100u64),
2514 ),
2515 pallas::Base::from_repr([
2516 114, 56, 62, 208, 155, 244, 76, 209, 125, 210, 149, 109, 176, 88, 34, 116, 123, 56,
2517 62, 216, 108, 204, 55, 120, 28, 155, 217, 186, 29, 159, 128, 2,
2518 ])
2519 .expect("frozen vector must be canonical")
2520 );
2521 }
2522
2523 #[test]
2524 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2525 fn stale_and_current_anchor_proofs_for_same_van_share_nullifier() {
2526 let fixture = VoteReuseFixture::new();
2527 let voting_round_id = pallas::Base::from(0xCAFEu64);
2528 let stale_van = fixture.vote_authority_note_old(voting_round_id);
2529 let successor_van = fixture.vote_authority_note_new(voting_round_id);
2530
2531 let (stale_path, stale_position, stale_root) = build_single_leaf_merkle_path(stale_van);
2532 let (stale_circuit, stale_instance) =
2533 fixture.build_vote_data(voting_round_id, stale_path, stale_position, stale_root, 10);
2534
2535 let (current_path, current_position, current_root) =
2536 build_left_leaf_merkle_path_with_sibling(stale_van, successor_van);
2537 let (current_circuit, current_instance) = fixture.build_vote_data(
2538 voting_round_id,
2539 current_path,
2540 current_position,
2541 current_root,
2542 11,
2543 );
2544
2545 assert_ne!(
2546 stale_root, current_root,
2547 "the successor VAN changes the supplied tree anchor"
2548 );
2549 assert_eq!(
2550 stale_instance.van_nullifier, current_instance.van_nullifier,
2551 "same (vsk_nk, voting_round_id, VAN) must collide for chain-side nullifier uniqueness"
2552 );
2553
2554 let stale_prover =
2557 MockProver::run(K, &stale_circuit, vec![stale_instance.to_halo2_instance()]).unwrap();
2558 assert_eq!(stale_prover.verify(), Ok(()));
2559
2560 let current_prover = MockProver::run(
2561 K,
2562 ¤t_circuit,
2563 vec![current_instance.to_halo2_instance()],
2564 )
2565 .unwrap();
2566 assert_eq!(current_prover.verify(), Ok(()));
2567 }
2568
2569 #[test]
2571 fn domain_van_nullifier_deterministic() {
2572 let d1 = domain_van_nullifier();
2573 let d2 = domain_van_nullifier();
2574 assert_eq!(d1, d2);
2575
2576 assert_ne!(d1, pallas::Base::zero());
2578 }
2579
2580 #[test]
2586 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2587 fn proposal_authority_decrement_minimum_valid() {
2588 let (circuit, instance) =
2592 make_test_data_with_authority_and_proposal(pallas::Base::from(2u64), 1);
2593
2594 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2595 assert_eq!(prover.verify(), Ok(()));
2596 }
2597
2598 #[test]
2601 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2602 fn proposal_authority_zero_fails() {
2603 let (circuit, instance) = make_test_data_with_authority(pallas::Base::zero());
2604
2605 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2606
2607 assert!(prover.verify().is_err());
2608 }
2609
2610 #[test]
2612 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2613 fn proposal_id_zero_fails() {
2614 let (circuit, instance) =
2617 make_test_data_with_authority_and_proposal(pallas::Base::one(), 0);
2618
2619 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2620 assert!(prover.verify().is_err(), "proposal_id = 0 must be rejected");
2621 }
2622
2623 #[test]
2625 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2626 fn proposal_authority_full_authority_proposal_1_passes() {
2627 const MAX_PROPOSAL_AUTHORITY: u64 = 65535;
2628 let (circuit, instance) = make_test_data_with_authority_and_proposal(
2629 pallas::Base::from(MAX_PROPOSAL_AUTHORITY),
2630 1,
2631 );
2632
2633 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2634 assert_eq!(prover.verify(), Ok(()));
2635 }
2636
2637 #[test]
2639 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2640 fn proposal_authority_wrong_new_fails() {
2641 let (circuit, mut instance) =
2642 make_test_data_with_authority_and_proposal(pallas::Base::from(65535u64), 1);
2643
2644 instance.vote_authority_note_new = pallas::Base::random(&mut OsRng);
2645
2646 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2647 assert!(prover.verify().is_err());
2648 }
2649
2650 #[test]
2655 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2656 fn proposal_authority_bit_not_set_fails() {
2657 let (circuit, instance) =
2658 make_test_data_with_authority_and_proposal(pallas::Base::from(4u64), 1);
2659
2660 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2661 assert!(prover.verify().is_err());
2662 }
2663
2664 #[test]
2668 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2669 fn proposal_authority_condition6_run_sel_constraint() {
2670 let (circuit, instance) =
2671 make_test_data_with_authority_and_proposal(pallas::Base::from(3u64), 1);
2672
2673 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2674 assert_eq!(prover.verify(), Ok(()));
2675 }
2676
2677 #[test]
2682 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2683 fn proposal_authority_exceeds_16_bits_fails() {
2684 let (circuit, instance) = make_test_data_with_authority(pallas::Base::from(65536u64));
2686 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2687 assert!(
2688 prover.verify().is_err(),
2689 "authority > 65535 must be rejected by the 16-bit bit decomposition"
2690 );
2691 }
2692
2693 #[test]
2699 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2700 fn new_van_integrity_wrong_public_input_fails() {
2701 let (circuit, mut instance) = make_test_data();
2702
2703 instance.vote_authority_note_new = pallas::Base::random(&mut OsRng);
2705
2706 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2707
2708 assert!(prover.verify().is_err());
2710 }
2711
2712 #[test]
2715 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2716 fn new_van_integrity_large_authority() {
2717 let (circuit, instance) = make_test_data_with_authority(pallas::Base::from(0xFFF8u64));
2718
2719 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2720 assert_eq!(prover.verify(), Ok(()));
2721 }
2722
2723 #[test]
2729 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2730 fn van_membership_wrong_root_fails() {
2731 let (circuit, mut instance) = make_test_data();
2732
2733 instance.vote_comm_tree_root = pallas::Base::random(&mut OsRng);
2735
2736 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2737 assert!(prover.verify().is_err());
2738 }
2739
2740 #[test]
2742 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2743 fn van_membership_nonzero_position() {
2744 let mut rng = OsRng;
2745
2746 let vsk = pallas::Scalar::random(&mut rng);
2748 let vsk_nk = pallas::Base::random(&mut rng);
2749 let rivk_v = pallas::Scalar::random(&mut rng);
2750 let (vpk_g_d_affine, vpk_pk_d_affine) = derive_voting_address(vsk, vsk_nk, rivk_v);
2751 let vpk_g_d_x = *vpk_g_d_affine.coordinates().unwrap().x();
2752 let vpk_pk_d_x = *vpk_pk_d_affine.coordinates().unwrap().x();
2753
2754 let total_note_value = pallas::Base::from(10_000u64);
2755 let voting_round_id = pallas::Base::random(&mut rng);
2756 let proposal_authority_old = pallas::Base::from(5u64); let proposal_id = 2u64;
2759 let van_comm_rand = pallas::Base::random(&mut rng);
2760
2761 let vote_authority_note_old = van_integrity_hash(
2762 vpk_g_d_x,
2763 vpk_pk_d_x,
2764 total_note_value,
2765 voting_round_id,
2766 proposal_authority_old,
2767 van_comm_rand,
2768 );
2769
2770 let position: u32 = 7;
2772 let mut empty_roots = [pallas::Base::zero(); VOTE_COMM_TREE_DEPTH];
2773 empty_roots[0] = poseidon_hash_2(pallas::Base::zero(), pallas::Base::zero());
2774 for i in 1..VOTE_COMM_TREE_DEPTH {
2775 empty_roots[i] = poseidon_hash_2(empty_roots[i - 1], empty_roots[i - 1]);
2776 }
2777 let auth_path = empty_roots;
2778 let mut current = vote_authority_note_old;
2779 for i in 0..VOTE_COMM_TREE_DEPTH {
2780 if (position >> i) & 1 == 0 {
2781 current = poseidon_hash_2(current, auth_path[i]);
2782 } else {
2783 current = poseidon_hash_2(auth_path[i], current);
2784 }
2785 }
2786 let vote_comm_tree_root = current;
2787
2788 let van_nullifier = van_nullifier_hash(vsk_nk, voting_round_id, vote_authority_note_old);
2789 let one_shifted = pallas::Base::from(1u64 << proposal_id);
2790 let proposal_authority_new = proposal_authority_old - one_shifted;
2791 let vote_authority_note_new = van_integrity_hash(
2792 vpk_g_d_x,
2793 vpk_pk_d_x,
2794 total_note_value,
2795 voting_round_id,
2796 proposal_authority_new,
2797 van_comm_rand,
2798 );
2799
2800 let alpha_v = pallas::Scalar::random(&mut rng);
2801 let g = pallas::Point::from(spend_auth_g_affine());
2802 let r_vpk = (g * vsk + g * alpha_v).to_affine();
2803 let r_vpk_x = *r_vpk.coordinates().unwrap().x();
2804 let r_vpk_y = *r_vpk.coordinates().unwrap().y();
2805
2806 let shares_u64: [u64; 16] = [625; 16];
2808
2809 let (_ea_sk, ea_pk_point, ea_pk_affine) = generate_ea_keypair();
2811 let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
2812 encrypt_shares(shares_u64, ea_pk_point);
2813
2814 let mut circuit = Circuit::with_van_witnesses(
2815 Value::known(auth_path),
2816 Value::known(position),
2817 Value::known(vpk_g_d_affine),
2818 Value::known(vpk_pk_d_affine),
2819 Value::known(total_note_value),
2820 Value::known(proposal_authority_old),
2821 Value::known(van_comm_rand),
2822 Value::known(vote_authority_note_old),
2823 Value::known(vsk),
2824 Value::known(rivk_v),
2825 Value::known(vsk_nk),
2826 Value::known(alpha_v),
2827 );
2828 circuit.one_shifted = Value::known(one_shifted);
2829 circuit.shares = shares_u64.map(|s| Value::known(pallas::Base::from(s)));
2830 circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
2831 circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
2832 circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
2833 circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
2834 circuit.share_blinds = share_blinds.map(Value::known);
2835 circuit.share_randomness = randomness.map(Value::known);
2836 circuit.ea_pk = Value::known(ea_pk_affine);
2837 let vc = set_condition_12(&mut circuit, shares_hash_val, proposal_id, voting_round_id);
2838
2839 let instance = Instance::from_parts(
2840 van_nullifier,
2841 r_vpk_x,
2842 r_vpk_y,
2843 vote_authority_note_new,
2844 vc,
2845 vote_comm_tree_root,
2846 pallas::Base::zero(),
2847 pallas::Base::from(proposal_id),
2848 voting_round_id,
2849 *ea_pk_affine.coordinates().unwrap().x(),
2850 *ea_pk_affine.coordinates().unwrap().y(),
2851 );
2852
2853 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2854 assert_eq!(prover.verify(), Ok(()));
2855 }
2856
2857 #[test]
2859 fn poseidon_hash_2_deterministic() {
2860 let mut rng = OsRng;
2861 let a = pallas::Base::random(&mut rng);
2862 let b = pallas::Base::random(&mut rng);
2863
2864 assert_eq!(poseidon_hash_2(a, b), poseidon_hash_2(a, b));
2865 assert_ne!(poseidon_hash_2(a, b), poseidon_hash_2(b, a));
2867 }
2868
2869 #[test]
2875 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2876 fn shares_sum_wrong_total_fails() {
2877 let (mut circuit, instance) = make_test_data();
2878
2879 circuit.shares[3] = Value::known(pallas::Base::from(999u64));
2883
2884 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2885 assert!(prover.verify().is_err());
2887 }
2888
2889 #[test]
2895 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2896 fn shares_range_max_valid() {
2897 let max_share = pallas::Base::from((1u64 << 30) - 1); let total = (0..16).fold(pallas::Base::zero(), |acc, _| acc + max_share);
2899
2900 let mut rng = OsRng;
2901 let vsk = pallas::Scalar::random(&mut rng);
2903 let vsk_nk = pallas::Base::random(&mut rng);
2904 let rivk_v = pallas::Scalar::random(&mut rng);
2905 let (vpk_g_d_affine, vpk_pk_d_affine) = derive_voting_address(vsk, vsk_nk, rivk_v);
2906 let vpk_g_d_x = *vpk_g_d_affine.coordinates().unwrap().x();
2907 let vpk_pk_d_x = *vpk_pk_d_affine.coordinates().unwrap().x();
2908
2909 let voting_round_id = pallas::Base::random(&mut rng);
2910 let proposal_authority_old = pallas::Base::from(5u64); let proposal_id = 2u64;
2913 let van_comm_rand = pallas::Base::random(&mut rng);
2914
2915 let vote_authority_note_old = van_integrity_hash(
2916 vpk_g_d_x,
2917 vpk_pk_d_x,
2918 total,
2919 voting_round_id,
2920 proposal_authority_old,
2921 van_comm_rand,
2922 );
2923 let (auth_path, position, vote_comm_tree_root) =
2924 build_single_leaf_merkle_path(vote_authority_note_old);
2925 let van_nullifier = van_nullifier_hash(vsk_nk, voting_round_id, vote_authority_note_old);
2926 let one_shifted = pallas::Base::from(1u64 << proposal_id);
2927 let proposal_authority_new = proposal_authority_old - one_shifted;
2928 let vote_authority_note_new = van_integrity_hash(
2929 vpk_g_d_x,
2930 vpk_pk_d_x,
2931 total,
2932 voting_round_id,
2933 proposal_authority_new,
2934 van_comm_rand,
2935 );
2936
2937 let max_share_u64 = (1u64 << 30) - 1;
2939 let shares_u64: [u64; 16] = [max_share_u64; 16];
2940 let (_ea_sk, ea_pk_point, ea_pk_affine) = generate_ea_keypair();
2941 let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
2942 encrypt_shares(shares_u64, ea_pk_point);
2943
2944 let alpha_v = pallas::Scalar::random(&mut rng);
2945 let g = pallas::Point::from(spend_auth_g_affine());
2946 let r_vpk = (g * vsk + g * alpha_v).to_affine();
2947 let r_vpk_x = *r_vpk.coordinates().unwrap().x();
2948 let r_vpk_y = *r_vpk.coordinates().unwrap().y();
2949
2950 let mut circuit = Circuit::with_van_witnesses(
2951 Value::known(auth_path),
2952 Value::known(position),
2953 Value::known(vpk_g_d_affine),
2954 Value::known(vpk_pk_d_affine),
2955 Value::known(total),
2956 Value::known(proposal_authority_old),
2957 Value::known(van_comm_rand),
2958 Value::known(vote_authority_note_old),
2959 Value::known(vsk),
2960 Value::known(rivk_v),
2961 Value::known(vsk_nk),
2962 Value::known(alpha_v),
2963 );
2964 circuit.one_shifted = Value::known(one_shifted);
2965 circuit.shares = [Value::known(max_share); 16];
2966 circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
2967 circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
2968 circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
2969 circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
2970 circuit.share_blinds = share_blinds.map(Value::known);
2971 circuit.share_randomness = randomness.map(Value::known);
2972 circuit.ea_pk = Value::known(ea_pk_affine);
2973 let vc = set_condition_12(&mut circuit, shares_hash_val, proposal_id, voting_round_id);
2974
2975 let instance = Instance::from_parts(
2976 van_nullifier,
2977 r_vpk_x,
2978 r_vpk_y,
2979 vote_authority_note_new,
2980 vc,
2981 vote_comm_tree_root,
2982 pallas::Base::zero(),
2983 pallas::Base::from(proposal_id),
2984 voting_round_id,
2985 *ea_pk_affine.coordinates().unwrap().x(),
2986 *ea_pk_affine.coordinates().unwrap().y(),
2987 );
2988
2989 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
2990 assert_eq!(prover.verify(), Ok(()));
2991 }
2992
2993 #[test]
2995 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
2996 fn shares_range_overflow_fails() {
2997 let (mut circuit, instance) = make_test_data();
2998
2999 circuit.shares[0] = Value::known(pallas::Base::from(1u64 << 30));
3003
3004 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3005 assert!(prover.verify().is_err());
3006 }
3007
3008 #[test]
3011 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3012 fn shares_range_field_wrap_fails() {
3013 let (mut circuit, instance) = make_test_data();
3014
3015 circuit.shares[0] = Value::known(-pallas::Base::one());
3018
3019 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3020 assert!(prover.verify().is_err());
3021 }
3022
3023 #[test]
3029 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3030 fn shares_range_single_overflow_correct_sum_fails() {
3031 let mut rng = OsRng;
3032
3033 let overflow_share = pallas::Base::from(1u64 << 30); let normal_share_u64 = 625u64;
3035 let total_note_value = overflow_share + pallas::Base::from(15u64 * normal_share_u64);
3037
3038 let vsk = pallas::Scalar::random(&mut rng);
3039 let vsk_nk = pallas::Base::random(&mut rng);
3040 let rivk_v = pallas::Scalar::random(&mut rng);
3041 let alpha_v = pallas::Scalar::random(&mut rng);
3042 let (vpk_g_d_affine, vpk_pk_d_affine) = derive_voting_address(vsk, vsk_nk, rivk_v);
3043 let vpk_g_d_x = *vpk_g_d_affine.coordinates().unwrap().x();
3044 let vpk_pk_d_x = *vpk_pk_d_affine.coordinates().unwrap().x();
3045
3046 let voting_round_id = pallas::Base::random(&mut rng);
3047 let proposal_authority_old = pallas::Base::from(13u64); let proposal_id = TEST_PROPOSAL_ID;
3049 let van_comm_rand = pallas::Base::random(&mut rng);
3050
3051 let vote_authority_note_old = van_integrity_hash(
3052 vpk_g_d_x,
3053 vpk_pk_d_x,
3054 total_note_value,
3055 voting_round_id,
3056 proposal_authority_old,
3057 van_comm_rand,
3058 );
3059 let (auth_path, position, vote_comm_tree_root) =
3060 build_single_leaf_merkle_path(vote_authority_note_old);
3061 let van_nullifier = van_nullifier_hash(vsk_nk, voting_round_id, vote_authority_note_old);
3062 let one_shifted = pallas::Base::from(1u64 << proposal_id);
3063 let proposal_authority_new = proposal_authority_old - one_shifted;
3064 let vote_authority_note_new = van_integrity_hash(
3065 vpk_g_d_x,
3066 vpk_pk_d_x,
3067 total_note_value,
3068 voting_round_id,
3069 proposal_authority_new,
3070 van_comm_rand,
3071 );
3072
3073 let shares_u64: [u64; 16] = {
3076 let mut arr = [normal_share_u64; 16];
3077 arr[0] = 1u64 << 30;
3078 arr
3079 };
3080 let (_ea_sk, ea_pk_point, ea_pk_affine) = generate_ea_keypair();
3081 let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
3082 encrypt_shares(shares_u64, ea_pk_point);
3083
3084 let g = pallas::Point::from(spend_auth_g_affine());
3085 let r_vpk = (g * vsk + g * alpha_v).to_affine();
3086
3087 let mut circuit = Circuit::with_van_witnesses(
3088 Value::known(auth_path),
3089 Value::known(position),
3090 Value::known(vpk_g_d_affine),
3091 Value::known(vpk_pk_d_affine),
3092 Value::known(total_note_value),
3093 Value::known(proposal_authority_old),
3094 Value::known(van_comm_rand),
3095 Value::known(vote_authority_note_old),
3096 Value::known(vsk),
3097 Value::known(rivk_v),
3098 Value::known(vsk_nk),
3099 Value::known(alpha_v),
3100 );
3101 circuit.one_shifted = Value::known(one_shifted);
3102 circuit.shares = shares_u64.map(|s| Value::known(pallas::Base::from(s)));
3103 circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
3104 circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
3105 circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
3106 circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
3107 circuit.share_blinds = share_blinds.map(Value::known);
3108 circuit.share_randomness = randomness.map(Value::known);
3109 circuit.ea_pk = Value::known(ea_pk_affine);
3110
3111 let vote_commitment =
3112 set_condition_12(&mut circuit, shares_hash_val, proposal_id, voting_round_id);
3113
3114 let instance = Instance::from_parts(
3115 van_nullifier,
3116 *r_vpk.coordinates().unwrap().x(),
3117 *r_vpk.coordinates().unwrap().y(),
3118 vote_authority_note_new,
3119 vote_commitment,
3120 vote_comm_tree_root,
3121 pallas::Base::zero(),
3122 pallas::Base::from(proposal_id),
3123 voting_round_id,
3124 *ea_pk_affine.coordinates().unwrap().x(),
3125 *ea_pk_affine.coordinates().unwrap().y(),
3126 );
3127
3128 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3129 assert!(
3132 prover.verify().is_err(),
3133 "range check must reject a share equal to 2^30 even when the total sum is correct"
3134 );
3135 }
3136
3137 #[test]
3143 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3144 fn shares_hash_valid_proof() {
3145 let (circuit, instance) = make_test_data();
3146
3147 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3148 assert_eq!(prover.verify(), Ok(()));
3149 }
3150
3151 #[test]
3154 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3155 fn shares_hash_wrong_enc_share_fails() {
3156 let (mut circuit, instance) = make_test_data();
3157
3158 circuit.enc_share_c1_x[0] = Value::known(pallas::Base::random(&mut OsRng));
3161
3162 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3163 assert!(prover.verify().is_err());
3164 }
3165
3166 #[test]
3169 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3170 fn shares_hash_wrong_instance_fails() {
3171 let (circuit, mut instance) = make_test_data();
3172
3173 instance.vote_commitment = pallas::Base::random(&mut OsRng);
3175
3176 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3177 assert!(prover.verify().is_err());
3178 }
3179
3180 #[test]
3182 fn shares_hash_deterministic() {
3183 let mut rng = OsRng;
3184
3185 let blinds: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
3186 let c1_x: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
3187 let c2_x: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
3188 let c1_y: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
3189 let c2_y: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
3190
3191 let h1 = shares_hash(blinds, c1_x, c2_x, c1_y, c2_y);
3192 let h2 = shares_hash(blinds, c1_x, c2_x, c1_y, c2_y);
3193 assert_eq!(h1, h2);
3194
3195 let mut c1_x_alt = c1_x;
3197 c1_x_alt[2] = pallas::Base::random(&mut rng);
3198 let h3 = shares_hash(blinds, c1_x_alt, c2_x, c1_y, c2_y);
3199 assert_ne!(h1, h3);
3200
3201 let h4 = shares_hash(blinds, c2_x, c1_x, c2_y, c1_y);
3203 assert_ne!(h1, h4);
3204
3205 let blinds_alt: [pallas::Base; 16] =
3207 core::array::from_fn(|_| pallas::Base::random(&mut rng));
3208 let h5 = shares_hash(blinds_alt, c1_x, c2_x, c1_y, c2_y);
3209 assert_ne!(h1, h5);
3210 }
3211
3212 #[test]
3215 fn share_commitment_deterministic() {
3216 let mut rng = OsRng;
3217 let blind = pallas::Base::random(&mut rng);
3218 let c1_x = pallas::Base::random(&mut rng);
3219 let c2_x = pallas::Base::random(&mut rng);
3220 let c1_y = pallas::Base::random(&mut rng);
3221 let c2_y = pallas::Base::random(&mut rng);
3222
3223 let h1 = share_commitment(blind, c1_x, c2_x, c1_y, c2_y);
3224 let h2 = share_commitment(blind, c1_x, c2_x, c1_y, c2_y);
3225 assert_eq!(h1, h2);
3226
3227 let h3 = share_commitment(blind, c2_x, c1_x, c2_y, c1_y);
3229 assert_ne!(h1, h3);
3230
3231 let blind_alt = pallas::Base::random(&mut rng);
3233 let h4 = share_commitment(blind_alt, c1_x, c2_x, c1_y, c2_y);
3234 assert_ne!(h1, h4);
3235 }
3236
3237 #[test]
3241 fn share_commitment_domain_separates_vote_commitment_shape() {
3242 let voting_round_id = pallas::Base::from(11u64);
3243 let shares_hash = pallas::Base::from(22u64);
3244 let proposal_id = pallas::Base::from(3u64);
3245 let vote_decision = pallas::Base::from(1u64);
3246
3247 let vote_commitment =
3248 vote_commitment_hash(voting_round_id, shares_hash, proposal_id, vote_decision);
3249 let shape_overlap_share_commitment = share_commitment(
3250 pallas::Base::from(DOMAIN_VC),
3251 voting_round_id,
3252 shares_hash,
3253 proposal_id,
3254 vote_decision,
3255 );
3256
3257 assert_eq!(
3258 vote_commitment.to_repr(),
3259 [
3260 70, 114, 148, 71, 228, 55, 194, 154, 195, 194, 133, 222, 203, 11, 174, 163, 74, 20,
3261 85, 36, 103, 39, 22, 81, 245, 10, 23, 88, 115, 254, 254, 50,
3262 ]
3263 );
3264 assert_eq!(
3265 shape_overlap_share_commitment.to_repr(),
3266 [
3267 67, 67, 1, 91, 168, 243, 48, 129, 115, 80, 130, 0, 38, 45, 204, 82, 225, 208, 122,
3268 41, 143, 233, 124, 107, 249, 117, 167, 190, 235, 13, 157, 38,
3269 ]
3270 );
3271 assert_ne!(vote_commitment, shape_overlap_share_commitment);
3272 }
3273
3274 #[derive(Clone, Default)]
3278 struct ShareCommitmentTestCircuit {
3279 blind: pallas::Base,
3280 c1_x: pallas::Base,
3281 c2_x: pallas::Base,
3282 c1_y: pallas::Base,
3283 c2_y: pallas::Base,
3284 }
3285
3286 #[derive(Clone)]
3287 struct ShareCommitmentTestConfig {
3288 primary: Column<InstanceColumn>,
3289 advices: [Column<Advice>; 5],
3290 poseidon_config: PoseidonConfig<pallas::Base, 3, 2>,
3291 }
3292
3293 impl plonk::Circuit<pallas::Base> for ShareCommitmentTestCircuit {
3294 type Config = ShareCommitmentTestConfig;
3295 type FloorPlanner = floor_planner::V1;
3296
3297 fn without_witnesses(&self) -> Self {
3298 Self::default()
3299 }
3300
3301 fn configure(meta: &mut ConstraintSystem<pallas::Base>) -> Self::Config {
3302 let primary = meta.instance_column();
3303 meta.enable_equality(primary);
3304 let advices: [Column<Advice>; 5] = core::array::from_fn(|_| meta.advice_column());
3305 for col in &advices {
3306 meta.enable_equality(*col);
3307 }
3308 let fixed: [Column<Fixed>; 6] = core::array::from_fn(|_| meta.fixed_column());
3309 let constants = meta.fixed_column();
3310 meta.enable_constant(constants);
3311 let rc_a = fixed[0..3].try_into().unwrap();
3312 let rc_b = fixed[3..6].try_into().unwrap();
3313 let poseidon_config = PoseidonChip::configure::<poseidon::P128Pow5T3>(
3314 meta,
3315 advices[1..4].try_into().unwrap(),
3316 advices[4],
3317 rc_a,
3318 rc_b,
3319 );
3320 ShareCommitmentTestConfig {
3321 primary,
3322 advices,
3323 poseidon_config,
3324 }
3325 }
3326
3327 fn synthesize(
3328 &self,
3329 config: Self::Config,
3330 mut layouter: impl Layouter<pallas::Base>,
3331 ) -> Result<(), plonk::Error> {
3332 let blind_cell = assign_free_advice(
3333 layouter.namespace(|| "blind"),
3334 config.advices[0],
3335 Value::known(self.blind),
3336 )?;
3337 let c1_x_cell = assign_free_advice(
3338 layouter.namespace(|| "c1_x"),
3339 config.advices[0],
3340 Value::known(self.c1_x),
3341 )?;
3342 let c2_x_cell = assign_free_advice(
3343 layouter.namespace(|| "c2_x"),
3344 config.advices[0],
3345 Value::known(self.c2_x),
3346 )?;
3347 let c1_y_cell = assign_free_advice(
3348 layouter.namespace(|| "c1_y"),
3349 config.advices[0],
3350 Value::known(self.c1_y),
3351 )?;
3352 let c2_y_cell = assign_free_advice(
3353 layouter.namespace(|| "c2_y"),
3354 config.advices[0],
3355 Value::known(self.c2_y),
3356 )?;
3357 let chip = PoseidonChip::construct(config.poseidon_config.clone());
3358 let domain_share_comm =
3359 share_commitment::assign_domain_share_comm(&mut layouter, config.advices[0])?;
3360 let result = hash_share_commitment_in_circuit(
3361 chip,
3362 layouter.namespace(|| "share_comm"),
3363 domain_share_comm,
3364 blind_cell,
3365 c1_x_cell,
3366 c2_x_cell,
3367 c1_y_cell,
3368 c2_y_cell,
3369 0,
3370 )?;
3371 layouter.constrain_instance(result.cell(), config.primary, 0)?;
3372 Ok(())
3373 }
3374 }
3375
3376 #[test]
3381 fn hash_share_commitment_in_circuit_matches_native() {
3382 let mut rng = OsRng;
3383 let blind = pallas::Base::random(&mut rng);
3384 let c1_x = pallas::Base::random(&mut rng);
3385 let c2_x = pallas::Base::random(&mut rng);
3386 let c1_y = pallas::Base::random(&mut rng);
3387 let c2_y = pallas::Base::random(&mut rng);
3388
3389 let expected = share_commitment(blind, c1_x, c2_x, c1_y, c2_y);
3390 let circuit = ShareCommitmentTestCircuit {
3391 blind,
3392 c1_x,
3393 c2_x,
3394 c1_y,
3395 c2_y,
3396 };
3397 let instance = vec![vec![expected]];
3398 const TEST_K: u32 = 10;
3400 let prover = MockProver::run(TEST_K, &circuit, instance).expect("MockProver::run failed");
3401 assert_eq!(prover.verify(), Ok(()));
3402 }
3403
3404 #[test]
3410 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3411 fn encryption_integrity_valid_proof() {
3412 let (circuit, instance) = make_test_data();
3413
3414 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3415 assert_eq!(prover.verify(), Ok(()));
3416 }
3417
3418 #[test]
3421 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3422 fn encryption_integrity_randomness_zero_is_rejected() {
3423 let (mut circuit, mut instance) = make_test_data();
3424 let shares_u64 = [625u64; 16];
3425 let (_ea_sk, ea_pk_point, _ea_pk_affine) = generate_ea_keypair();
3426 let (mut c1_x, mut c2_x, mut c1_y, mut c2_y, mut randomness, blinds, _) =
3427 encrypt_shares(shares_u64, ea_pk_point);
3428 let c2 = pallas::Point::from(spend_auth_g_affine()) * pallas::Scalar::from(shares_u64[0]);
3429 let c2_coords = c2.to_affine().coordinates().unwrap();
3430
3431 randomness[0] = pallas::Base::zero();
3432 c1_x[0] = pallas::Base::zero();
3433 c1_y[0] = pallas::Base::zero();
3434 c2_x[0] = *c2_coords.x();
3435 c2_y[0] = *c2_coords.y();
3436
3437 circuit.share_randomness = randomness.map(Value::known);
3438 circuit.enc_share_c1_x = c1_x.map(Value::known);
3439 circuit.enc_share_c1_y = c1_y.map(Value::known);
3440 circuit.enc_share_c2_x = c2_x.map(Value::known);
3441 circuit.enc_share_c2_y = c2_y.map(Value::known);
3442 let shares_hash_val = shares_hash(blinds, c1_x, c2_x, c1_y, c2_y);
3443 instance.vote_commitment = set_condition_12(
3444 &mut circuit,
3445 shares_hash_val,
3446 TEST_PROPOSAL_ID,
3447 instance.voting_round_id,
3448 );
3449
3450 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3451 assert!(prover.verify().is_err());
3452 }
3453
3454 #[test]
3457 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3458 fn encryption_integrity_wrong_randomness_fails() {
3459 let (mut circuit, instance) = make_test_data();
3460
3461 circuit.share_randomness[0] = Value::known(pallas::Base::from(9999u64));
3463
3464 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3465 assert!(prover.verify().is_err());
3466 }
3467
3468 #[test]
3471 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3472 fn encryption_integrity_wrong_ea_pk_instance_fails() {
3473 let (circuit, mut instance) = make_test_data();
3474
3475 instance.ea_pk_x = pallas::Base::from(12345u64);
3478
3479 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3480 assert!(prover.verify().is_err());
3481 }
3482
3483 #[test]
3486 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3487 fn encryption_integrity_wrong_share_fails() {
3488 let (mut circuit, instance) = make_test_data();
3489
3490 circuit.shares[0] = Value::known(pallas::Base::from(9999u64));
3493
3494 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3495 assert!(prover.verify().is_err());
3496 }
3497
3498 #[test]
3501 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3502 fn encryption_integrity_wrong_enc_c2_x_fails() {
3503 let (mut circuit, instance) = make_test_data();
3504
3505 circuit.enc_share_c2_x[0] = Value::known(pallas::Base::random(&mut OsRng));
3509
3510 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3511 assert!(prover.verify().is_err());
3512 }
3513
3514 #[test]
3516 fn elgamal_encrypt_deterministic() {
3517 let (_ea_sk, ea_pk_point, _ea_pk_affine) = generate_ea_keypair();
3518
3519 let v = pallas::Base::from(1000u64);
3520 let r = pallas::Base::from(42u64);
3521
3522 let (c1_a, c2_a, _, _) =
3523 elgamal_encrypt(v, r, ea_pk_point).expect("test encryption inputs should be valid");
3524 let (c1_b, c2_b, _, _) =
3525 elgamal_encrypt(v, r, ea_pk_point).expect("test encryption inputs should be valid");
3526 assert_eq!(c1_a, c1_b);
3527 assert_eq!(c2_a, c2_b);
3528
3529 let (c1_c, _, _, _) = elgamal_encrypt(v, pallas::Base::from(99u64), ea_pk_point)
3531 .expect("test encryption inputs should be valid");
3532 assert_ne!(c1_a, c1_c);
3533 }
3534
3535 #[test]
3538 fn base_to_scalar_accepts_elgamal_inputs() {
3539 assert!(base_to_scalar(pallas::Base::zero()).is_some());
3541 assert!(base_to_scalar(pallas::Base::from(1u64)).is_some());
3542 assert!(base_to_scalar(pallas::Base::from(1_000u64)).is_some());
3543 assert!(base_to_scalar(pallas::Base::from(404u64)).is_some()); for r in (1u64..=16).map(|i| i * 101) {
3547 assert!(
3548 base_to_scalar(pallas::Base::from(r)).is_some(),
3549 "r = {} must convert for El Gamal",
3550 r
3551 );
3552 }
3553 }
3554
3555 #[test]
3561 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3562 fn vote_commitment_integrity_valid_proof() {
3563 let (circuit, instance) = make_test_data();
3564
3565 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3566 assert_eq!(prover.verify(), Ok(()));
3567 }
3568
3569 #[test]
3572 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3573 fn vote_commitment_wrong_decision_fails() {
3574 let (mut circuit, instance) = make_test_data();
3575
3576 circuit.vote_decision = Value::known(pallas::Base::from(99u64));
3578
3579 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3580 assert!(prover.verify().is_err());
3581 }
3582
3583 #[test]
3587 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3588 fn vote_commitment_wrong_proposal_id_fails() {
3589 let (circuit, mut instance) = make_test_data();
3590
3591 instance.proposal_id = pallas::Base::from(999u64);
3593
3594 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3595 assert!(prover.verify().is_err());
3596 }
3597
3598 #[test]
3600 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3601 fn vote_commitment_wrong_instance_fails() {
3602 let (circuit, mut instance) = make_test_data();
3603
3604 instance.vote_commitment = pallas::Base::random(&mut OsRng);
3606
3607 let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
3608 assert!(prover.verify().is_err());
3609 }
3610
3611 #[test]
3613 fn vote_commitment_hash_deterministic() {
3614 let mut rng = OsRng;
3615
3616 let rid = pallas::Base::random(&mut rng);
3617 let sh = pallas::Base::random(&mut rng);
3618 let pid = pallas::Base::from(5u64);
3619 let dec = pallas::Base::from(1u64);
3620
3621 let h1 = vote_commitment_hash(rid, sh, pid, dec);
3622 let h2 = vote_commitment_hash(rid, sh, pid, dec);
3623 assert_eq!(h1, h2);
3624
3625 let h3 = vote_commitment_hash(rid, sh, pallas::Base::from(6u64), dec);
3627 assert_ne!(h1, h3);
3628
3629 let h4 = vote_commitment_hash(pallas::Base::from(999u64), sh, pid, dec);
3631 assert_ne!(h1, h4);
3632
3633 assert_ne!(h1, pallas::Base::zero());
3636 }
3637
3638 #[test]
3644 fn instance_has_eleven_public_inputs() {
3645 let (_, instance) = make_test_data();
3646 assert_eq!(instance.to_halo2_instance().len(), 11);
3647 }
3648
3649 #[test]
3651 #[ignore = "long-running Halo2 circuit test; run with `cargo test -- --ignored`"]
3652 fn default_circuit_with_valid_instance_fails() {
3653 let (_, instance) = make_test_data();
3654 let circuit = Circuit::default();
3655
3656 match MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]) {
3657 Ok(prover) => assert!(prover.verify().is_err()),
3658 Err(_) => {} }
3660 }
3661
3662 #[test]
3671 #[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`"]
3672 fn row_budget() {
3673 use halo2_proofs::dev::CircuitCost;
3674 use pasta_curves::vesta;
3675 use std::println;
3676
3677 let (circuit, _) = make_test_data();
3678
3679 let cost = CircuitCost::<vesta::Point, _>::measure(K, &circuit);
3682 let debug = format!("{cost:?}");
3683
3684 let extract = |field: &str| -> usize {
3686 let prefix = format!("{field}: ");
3687 debug
3688 .split(&prefix)
3689 .nth(1)
3690 .and_then(|s| s.split([',', ' ', '}']).next())
3691 .and_then(|n| n.parse().ok())
3692 .unwrap_or(0)
3693 };
3694
3695 let max_rows = extract("max_rows");
3696 let max_advice_rows = extract("max_advice_rows");
3697 let max_fixed_rows = extract("max_fixed_rows");
3698 let total_available = 1usize << K;
3699
3700 println!("=== vote-proof circuit row budget (K={K}) ===");
3701 println!(" max_rows (floor-planner high-water mark): {max_rows}");
3702 println!(" max_advice_rows: {max_advice_rows}");
3703 println!(" max_fixed_rows: {max_fixed_rows}");
3704 println!(" 2^K (total available rows): {total_available}");
3705 println!(
3706 " headroom: {}",
3707 total_available.saturating_sub(max_rows)
3708 );
3709 println!(
3710 " utilisation: {:.1}%",
3711 100.0 * max_rows as f64 / total_available as f64
3712 );
3713 println!();
3714 println!(" Full debug: {debug}");
3715
3716 let cost_default = CircuitCost::<vesta::Point, _>::measure(K, &Circuit::default());
3723 let debug_default = format!("{cost_default:?}");
3724 let max_rows_default = debug_default
3725 .split("max_rows: ")
3726 .nth(1)
3727 .and_then(|s| s.split([',', ' ', '}']).next())
3728 .and_then(|n| n.parse::<usize>().ok())
3729 .unwrap_or(0);
3730 if max_rows_default == max_rows {
3731 println!(
3732 " Witness-independence: PASS \
3733 (Circuit::default() max_rows={max_rows_default} == filled max_rows={max_rows})"
3734 );
3735 } else {
3736 println!(
3737 " Witness-independence: FAIL \
3738 (Circuit::default() max_rows={max_rows_default} != filled max_rows={max_rows}) \
3739 — row count depends on witness values!"
3740 );
3741 }
3742
3743 println!(" VOTE_COMM_TREE_DEPTH (circuit constant): {VOTE_COMM_TREE_DEPTH}");
3750
3751 for probe_k in 11u32..=K {
3756 let (c, inst) = make_test_data();
3757 match MockProver::run(probe_k, &c, vec![inst.to_halo2_instance()]) {
3758 Err(_) => {
3759 println!(" K={probe_k}: not enough rows (synthesizer rejected)");
3760 continue;
3761 }
3762 Ok(p) => match p.verify() {
3763 Ok(()) => {
3764 println!(" Minimum viable K: {probe_k} (2^{probe_k} = {} rows, {:.1}% headroom)",
3765 1usize << probe_k,
3766 100.0 * (1.0 - max_rows as f64 / (1usize << probe_k) as f64));
3767 break;
3768 }
3769 Err(_) => println!(" K={probe_k}: too small"),
3770 },
3771 }
3772 }
3773 }
3774}