use alloc::vec::Vec;
use halo2_proofs::{
circuit::{AssignedCell, Layouter, Value, floor_planner},
plonk::{self, Advice, Column, ConstraintSystem, Fixed, Instance as InstanceColumn},
};
use pasta_curves::{pallas, vesta};
use halo2_gadgets::{
ecc::{
chip::{EccChip, EccConfig},
NonIdentityPoint, ScalarFixed,
},
poseidon::{
primitives::{self as poseidon, ConstantLength},
Hash as PoseidonHash, Pow5Chip as PoseidonChip, Pow5Config as PoseidonConfig,
},
sinsemilla::chip::{SinsemillaChip, SinsemillaConfig},
utilities::lookup_range_check::LookupRangeCheckConfig,
};
use crate::circuit::address_ownership::{prove_address_ownership, spend_auth_g_mul};
use crate::circuit::elgamal::{EaPkInstanceLoc, prove_elgamal_encryptions};
use crate::circuit::poseidon_merkle::{MerkleSwapGate, synthesize_poseidon_merkle_path};
use orchard::circuit::commit_ivk::{CommitIvkChip, CommitIvkConfig};
use orchard::circuit::gadget::{add_chip::{AddChip, AddConfig}, assign_free_advice, AddInstruction};
use orchard::constants::{
OrchardCommitDomains, OrchardFixedBases, OrchardHashDomains,
};
use crate::circuit::van_integrity;
use crate::circuit::vote_commitment;
use crate::shares_hash::compute_shares_hash_in_circuit;
#[cfg(test)]
use crate::shares_hash::hash_share_commitment_in_circuit;
use super::authority_decrement::{AuthorityDecrementChip, AuthorityDecrementConfig};
pub const VOTE_COMM_TREE_DEPTH: usize = 24;
pub const K: u32 = 13;
pub use van_integrity::DOMAIN_VAN;
pub use vote_commitment::DOMAIN_VC;
pub const MAX_PROPOSAL_ID: usize = 16;
const VAN_NULLIFIER: usize = 0;
const R_VPK_X: usize = 1;
const R_VPK_Y: usize = 2;
const VOTE_AUTHORITY_NOTE_NEW: usize = 3;
const VOTE_COMMITMENT: usize = 4;
const VOTE_COMM_TREE_ROOT: usize = 5;
const VOTE_COMM_TREE_ANCHOR_HEIGHT: usize = 6;
const PROPOSAL_ID: usize = 7;
const VOTING_ROUND_ID: usize = 8;
const EA_PK_X: usize = 9;
const EA_PK_Y: usize = 10;
const _: usize = VOTE_COMM_TREE_ANCHOR_HEIGHT;
pub use van_integrity::van_integrity_hash;
pub use vote_commitment::vote_commitment_hash;
pub fn domain_van_nullifier() -> pallas::Base {
pallas::Base::from_raw([
0x7475_6120_6574_6f76, 0x7320_7974_6972_6f68, 0x0000_0000_646e_6570, 0,
])
}
pub fn van_nullifier_hash(
vsk_nk: pallas::Base,
voting_round_id: pallas::Base,
vote_authority_note_old: pallas::Base,
) -> pallas::Base {
poseidon::Hash::<_, poseidon::P128Pow5T3, ConstantLength<4>, 3, 2>::init().hash([
vsk_nk,
domain_van_nullifier(),
voting_round_id,
vote_authority_note_old,
])
}
pub fn poseidon_hash_2(a: pallas::Base, b: pallas::Base) -> pallas::Base {
poseidon::Hash::<_, poseidon::P128Pow5T3, ConstantLength<2>, 3, 2>::init().hash([a, b])
}
pub fn share_commitment(
blind: pallas::Base,
c1_x: pallas::Base,
c2_x: pallas::Base,
c1_y: pallas::Base,
c2_y: pallas::Base,
) -> pallas::Base {
poseidon::Hash::<_, poseidon::P128Pow5T3, ConstantLength<5>, 3, 2>::init()
.hash([blind, c1_x, c2_x, c1_y, c2_y])
}
pub fn shares_hash(
share_blinds: [pallas::Base; 16],
enc_share_c1_x: [pallas::Base; 16],
enc_share_c2_x: [pallas::Base; 16],
enc_share_c1_y: [pallas::Base; 16],
enc_share_c2_y: [pallas::Base; 16],
) -> pallas::Base {
let comms: [pallas::Base; 16] = core::array::from_fn(|i| {
share_commitment(
share_blinds[i],
enc_share_c1_x[i],
enc_share_c2_x[i],
enc_share_c1_y[i],
enc_share_c2_y[i],
)
});
poseidon::Hash::<_, poseidon::P128Pow5T3, ConstantLength<16>, 3, 2>::init().hash(comms)
}
#[derive(Clone, Debug)]
pub struct Config {
primary: Column<InstanceColumn>,
advices: [Column<Advice>; 10],
poseidon_config: PoseidonConfig<pallas::Base, 3, 2>,
add_config: AddConfig,
ecc_config: EccConfig<OrchardFixedBases>,
sinsemilla_config:
SinsemillaConfig<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>,
commit_ivk_config: CommitIvkConfig,
range_check: LookupRangeCheckConfig<pallas::Base, 10>,
merkle_swap: MerkleSwapGate,
authority_decrement: AuthorityDecrementConfig,
}
impl Config {
pub(crate) fn poseidon_chip(&self) -> PoseidonChip<pallas::Base, 3, 2> {
PoseidonChip::construct(self.poseidon_config.clone())
}
fn add_chip(&self) -> AddChip {
AddChip::construct(self.add_config.clone())
}
fn ecc_chip(&self) -> EccChip<OrchardFixedBases> {
EccChip::construct(self.ecc_config.clone())
}
fn sinsemilla_chip(
&self,
) -> SinsemillaChip<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases> {
SinsemillaChip::construct(self.sinsemilla_config.clone())
}
fn commit_ivk_chip(&self) -> CommitIvkChip {
CommitIvkChip::construct(self.commit_ivk_config.clone())
}
fn range_check_config(&self) -> LookupRangeCheckConfig<pallas::Base, 10> {
self.range_check
}
}
#[derive(Clone, Debug, Default)]
pub struct Circuit {
pub(crate) vote_comm_tree_path: Value<[pallas::Base; VOTE_COMM_TREE_DEPTH]>,
pub(crate) vote_comm_tree_position: Value<u32>,
pub(crate) vpk_g_d: Value<pallas::Affine>,
pub(crate) vpk_pk_d: Value<pallas::Affine>,
pub(crate) total_note_value: Value<pallas::Base>,
pub(crate) proposal_authority_old: Value<pallas::Base>,
pub(crate) van_comm_rand: Value<pallas::Base>,
pub(crate) vote_authority_note_old: Value<pallas::Base>,
pub(crate) vsk: Value<pallas::Scalar>,
pub(crate) rivk_v: Value<pallas::Scalar>,
pub(crate) alpha_v: Value<pallas::Scalar>,
pub(crate) vsk_nk: Value<pallas::Base>,
pub(crate) one_shifted: Value<pallas::Base>,
pub(crate) shares: [Value<pallas::Base>; 16],
pub(crate) enc_share_c1_x: [Value<pallas::Base>; 16],
pub(crate) enc_share_c2_x: [Value<pallas::Base>; 16],
pub(crate) enc_share_c1_y: [Value<pallas::Base>; 16],
pub(crate) enc_share_c2_y: [Value<pallas::Base>; 16],
pub(crate) share_blinds: [Value<pallas::Base>; 16],
pub(crate) share_randomness: [Value<pallas::Base>; 16],
pub(crate) ea_pk: Value<pallas::Affine>,
pub(crate) vote_decision: Value<pallas::Base>,
}
impl Circuit {
pub fn with_van_witnesses(
vote_comm_tree_path: Value<[pallas::Base; VOTE_COMM_TREE_DEPTH]>,
vote_comm_tree_position: Value<u32>,
vpk_g_d: Value<pallas::Affine>,
vpk_pk_d: Value<pallas::Affine>,
total_note_value: Value<pallas::Base>,
proposal_authority_old: Value<pallas::Base>,
van_comm_rand: Value<pallas::Base>,
vote_authority_note_old: Value<pallas::Base>,
vsk: Value<pallas::Scalar>,
rivk_v: Value<pallas::Scalar>,
vsk_nk: Value<pallas::Base>,
alpha_v: Value<pallas::Scalar>,
) -> Self {
Circuit {
vote_comm_tree_path,
vote_comm_tree_position,
vpk_g_d,
vpk_pk_d,
total_note_value,
proposal_authority_old,
van_comm_rand,
vote_authority_note_old,
vsk,
rivk_v,
alpha_v,
vsk_nk,
..Default::default()
}
}
}
impl plonk::Circuit<pallas::Base> for Circuit {
type Config = Config;
type FloorPlanner = floor_planner::V1;
fn without_witnesses(&self) -> Self {
Self::default()
}
fn configure(meta: &mut ConstraintSystem<pallas::Base>) -> Self::Config {
let advices: [Column<Advice>; 10] = core::array::from_fn(|_| meta.advice_column());
for col in &advices {
meta.enable_equality(*col);
}
let primary = meta.instance_column();
meta.enable_equality(primary);
let lagrange_coeffs: [Column<Fixed>; 8] =
core::array::from_fn(|_| meta.fixed_column());
let rc_a = lagrange_coeffs[2..5].try_into().unwrap();
let rc_b = lagrange_coeffs[5..8].try_into().unwrap();
let constants = meta.fixed_column();
meta.enable_constant(constants);
let add_config = AddChip::configure(meta, advices[7], advices[8], advices[6]);
let table_idx = meta.lookup_table_column();
let lookup = (
table_idx,
meta.lookup_table_column(),
meta.lookup_table_column(),
);
let range_check = LookupRangeCheckConfig::configure(meta, advices[9], table_idx);
let ecc_config =
EccChip::<OrchardFixedBases>::configure(meta, advices, lagrange_coeffs, range_check);
let sinsemilla_config = SinsemillaChip::configure(
meta,
advices[..5].try_into().unwrap(),
advices[6],
lagrange_coeffs[0],
lookup,
range_check,
);
let commit_ivk_config = CommitIvkChip::configure(meta, advices);
let poseidon_config = PoseidonChip::configure::<poseidon::P128Pow5T3>(
meta,
advices[6..9].try_into().unwrap(),
advices[5],
rc_a,
rc_b,
);
let merkle_swap = MerkleSwapGate::configure(
meta,
[advices[0], advices[1], advices[2], advices[3], advices[4]],
);
let authority_decrement = AuthorityDecrementChip::configure(meta, advices);
Config {
primary,
advices,
poseidon_config,
add_config,
ecc_config,
sinsemilla_config,
commit_ivk_config,
range_check,
merkle_swap,
authority_decrement,
}
}
#[allow(non_snake_case)]
fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<pallas::Base>,
) -> Result<(), plonk::Error> {
SinsemillaChip::load(config.sinsemilla_config.clone(), &mut layouter)?;
AuthorityDecrementChip::load_table(&config.authority_decrement, &mut layouter)?;
let ecc_chip = config.ecc_chip();
let voting_round_id = layouter.assign_region(
|| "copy voting_round_id from instance",
|mut region| {
region.assign_advice_from_instance(
|| "voting_round_id",
config.primary,
VOTING_ROUND_ID,
config.advices[0],
0,
)
},
)?;
let voting_round_id_cond12 = voting_round_id.clone();
let vpk_g_d_point = NonIdentityPoint::new(
ecc_chip.clone(),
layouter.namespace(|| "witness vpk_g_d"),
self.vpk_g_d.map(|p| p),
)?;
let vpk_g_d = vpk_g_d_point.extract_p().inner().clone();
let vpk_pk_d_point = NonIdentityPoint::new(
ecc_chip.clone(),
layouter.namespace(|| "witness vpk_pk_d"),
self.vpk_pk_d.map(|p| p),
)?;
let vpk_pk_d = vpk_pk_d_point.extract_p().inner().clone();
let total_note_value = assign_free_advice(
layouter.namespace(|| "witness total_note_value"),
config.advices[0],
self.total_note_value,
)?;
let proposal_authority_old = assign_free_advice(
layouter.namespace(|| "witness proposal_authority_old"),
config.advices[0],
self.proposal_authority_old,
)?;
let van_comm_rand = assign_free_advice(
layouter.namespace(|| "witness van_comm_rand"),
config.advices[0],
self.van_comm_rand,
)?;
let vote_authority_note_old = assign_free_advice(
layouter.namespace(|| "witness vote_authority_note_old"),
config.advices[0],
self.vote_authority_note_old,
)?;
let domain_van = layouter.assign_region(
|| "DOMAIN_VAN constant",
|mut region| {
region.assign_advice_from_constant(
|| "domain_van",
config.advices[0],
0,
pallas::Base::from(DOMAIN_VAN),
)
},
)?;
let vsk_nk = assign_free_advice(
layouter.namespace(|| "witness vsk_nk"),
config.advices[0],
self.vsk_nk,
)?;
let vote_authority_note_old_cond1 = vote_authority_note_old.clone();
let voting_round_id_cond4 = voting_round_id.clone();
let domain_van_cond6 = domain_van.clone();
let vpk_g_d_cond6 = vpk_g_d.clone();
let vpk_pk_d_cond6 = vpk_pk_d.clone();
let total_note_value_cond6 = total_note_value.clone();
let total_note_value_cond8 = total_note_value.clone();
let voting_round_id_cond6 = voting_round_id.clone();
let van_comm_rand_cond6 = van_comm_rand.clone();
let vsk_nk_cond4 = vsk_nk.clone();
let derived_van = van_integrity::van_integrity_poseidon(
&config.poseidon_config,
&mut layouter,
"Old VAN integrity",
domain_van,
vpk_g_d,
vpk_pk_d,
total_note_value,
voting_round_id,
proposal_authority_old.clone(),
van_comm_rand,
)?;
layouter.assign_region(
|| "VAN integrity check",
|mut region| region.constrain_equal(derived_van.cell(), vote_authority_note_old.cell()),
)?;
let vsk_scalar = ScalarFixed::new(
ecc_chip.clone(),
layouter.namespace(|| "cond3 vsk"),
self.vsk,
)?;
let vsk_ak_point = spend_auth_g_mul(
ecc_chip.clone(),
layouter.namespace(|| "cond3 [vsk]G"),
"cond3: [vsk] SpendAuthG",
vsk_scalar,
)?;
let ak = vsk_ak_point.extract_p().inner().clone();
let rivk_v_scalar = ScalarFixed::new(
ecc_chip.clone(),
layouter.namespace(|| "cond3 rivk_v"),
self.rivk_v,
)?;
prove_address_ownership(
config.sinsemilla_chip(),
ecc_chip.clone(),
config.commit_ivk_chip(),
layouter.namespace(|| "cond3 address"),
"cond3",
ak,
vsk_nk.clone(),
rivk_v_scalar,
&vpk_g_d_point,
&vpk_pk_d_point,
)?;
crate::circuit::spend_authority::prove_spend_authority(
ecc_chip.clone(),
layouter.namespace(|| "cond4 spend authority"),
self.alpha_v,
&vsk_ak_point,
config.primary,
R_VPK_X,
R_VPK_Y,
)?;
{
let root = synthesize_poseidon_merkle_path::<VOTE_COMM_TREE_DEPTH>(
&config.merkle_swap,
&config.poseidon_config,
&mut layouter,
config.advices[0],
vote_authority_note_old_cond1,
self.vote_comm_tree_position,
self.vote_comm_tree_path,
"cond1: merkle",
)?;
layouter.constrain_instance(
root.cell(),
config.primary,
VOTE_COMM_TREE_ROOT,
)?;
}
let domain_van_nf = layouter.assign_region(
|| "DOMAIN_VAN_NULLIFIER constant",
|mut region| {
region.assign_advice_from_constant(
|| "domain_van_nullifier",
config.advices[0],
0,
domain_van_nullifier(),
)
},
)?;
let van_nullifier = {
let hasher = PoseidonHash::<
pallas::Base,
_,
poseidon::P128Pow5T3,
ConstantLength<4>,
3, 2, >::init(
config.poseidon_chip(),
layouter.namespace(|| "VAN nullifier Poseidon init"),
)?;
hasher.hash(
layouter.namespace(|| "Poseidon(vsk_nk, domain, round_id, van_old)"),
[vsk_nk_cond4, domain_van_nf, voting_round_id_cond4, vote_authority_note_old],
)?
};
layouter.constrain_instance(van_nullifier.cell(), config.primary, VAN_NULLIFIER)?;
let proposal_id = layouter.assign_region(
|| "copy proposal_id from instance",
|mut region| {
region.assign_advice_from_instance(
|| "proposal_id",
config.primary,
PROPOSAL_ID,
config.advices[0],
0,
)
},
)?;
let proposal_authority_new = AuthorityDecrementChip::assign(
&config.authority_decrement,
&mut layouter,
proposal_id.clone(),
proposal_authority_old,
self.one_shifted,
)?;
let derived_van_new = van_integrity::van_integrity_poseidon(
&config.poseidon_config,
&mut layouter,
"New VAN integrity",
domain_van_cond6,
vpk_g_d_cond6,
vpk_pk_d_cond6,
total_note_value_cond6,
voting_round_id_cond6,
proposal_authority_new,
van_comm_rand_cond6,
)?;
layouter.constrain_instance(
derived_van_new.cell(),
config.primary,
VOTE_AUTHORITY_NOTE_NEW,
)?;
let share_cells: [_; 16] = (0..16usize)
.map(|i| assign_free_advice(
layouter.namespace(|| alloc::format!("witness share_{i}")),
config.advices[0],
self.shares[i],
))
.collect::<Result<Vec<_>, _>>()?
.try_into()
.expect("always 16 elements");
let shares_sum = share_cells[1..].iter().enumerate().try_fold(
share_cells[0].clone(),
|acc, (i, share)| {
config.add_chip().add(
layouter.namespace(|| alloc::format!("shares sum step {}", i + 1)),
&acc,
share,
)
},
)?;
layouter.assign_region(
|| "shares sum == total_note_value",
|mut region| {
region.constrain_equal(shares_sum.cell(), total_note_value_cond8.cell())
},
)?;
for (i, cell) in share_cells.iter().enumerate() {
config.range_check_config().copy_check(
layouter.namespace(|| alloc::format!("share_{i} < 2^30")),
cell.clone(),
3, true, )?;
}
let blinds: [AssignedCell<pallas::Base, pallas::Base>; 16] = (0..16)
.map(|i| {
assign_free_advice(
layouter.namespace(|| alloc::format!("witness share_blind[{i}]")),
config.advices[0],
self.share_blinds[i],
)
})
.collect::<Result<Vec<_>, _>>()?
.try_into()
.expect("always 16 elements");
let enc_c1: [AssignedCell<pallas::Base, pallas::Base>; 16] = (0..16)
.map(|i| assign_free_advice(
layouter.namespace(|| alloc::format!("witness enc_c1_x[{i}]")),
config.advices[0],
self.enc_share_c1_x[i],
))
.collect::<Result<Vec<_>, _>>()?
.try_into()
.expect("always 16 elements");
let enc_c2: [AssignedCell<pallas::Base, pallas::Base>; 16] = (0..16)
.map(|i| assign_free_advice(
layouter.namespace(|| alloc::format!("witness enc_c2_x[{i}]")),
config.advices[0],
self.enc_share_c2_x[i],
))
.collect::<Result<Vec<_>, _>>()?
.try_into()
.expect("always 16 elements");
let enc_c1_y: [AssignedCell<pallas::Base, pallas::Base>; 16] = (0..16)
.map(|i| assign_free_advice(
layouter.namespace(|| alloc::format!("witness enc_c1_y[{i}]")),
config.advices[0],
self.enc_share_c1_y[i],
))
.collect::<Result<Vec<_>, _>>()?
.try_into()
.expect("always 16 elements");
let enc_c2_y: [AssignedCell<pallas::Base, pallas::Base>; 16] = (0..16)
.map(|i| assign_free_advice(
layouter.namespace(|| alloc::format!("witness enc_c2_y[{i}]")),
config.advices[0],
self.enc_share_c2_y[i],
))
.collect::<Result<Vec<_>, _>>()?
.try_into()
.expect("always 16 elements");
let enc_c1_cond11: [AssignedCell<pallas::Base, pallas::Base>; 16] =
core::array::from_fn(|i| enc_c1[i].clone());
let enc_c2_cond11: [AssignedCell<pallas::Base, pallas::Base>; 16] =
core::array::from_fn(|i| enc_c2[i].clone());
let enc_c1_y_cond11: [AssignedCell<pallas::Base, pallas::Base>; 16] =
core::array::from_fn(|i| enc_c1_y[i].clone());
let enc_c2_y_cond11: [AssignedCell<pallas::Base, pallas::Base>; 16] =
core::array::from_fn(|i| enc_c2_y[i].clone());
let shares_hash = compute_shares_hash_in_circuit(
|| config.poseidon_chip(),
layouter.namespace(|| "cond10: shares hash"),
blinds,
enc_c1,
enc_c2,
enc_c1_y,
enc_c2_y,
)?;
{
let r_cells: [_; 16] = (0..16usize)
.map(|i| assign_free_advice(
layouter.namespace(|| alloc::format!("witness r[{i}]")),
config.advices[0],
self.share_randomness[i],
))
.collect::<Result<Vec<_>, _>>()?
.try_into()
.expect("always 16 elements");
prove_elgamal_encryptions(
ecc_chip.clone(),
layouter.namespace(|| "cond11 El Gamal"),
"cond11",
self.ea_pk,
EaPkInstanceLoc {
instance: config.primary,
x_row: EA_PK_X,
y_row: EA_PK_Y,
},
config.advices[0],
share_cells,
r_cells,
enc_c1_cond11,
enc_c2_cond11,
enc_c1_y_cond11,
enc_c2_y_cond11,
)?;
}
let domain_vc = layouter.assign_region(
|| "DOMAIN_VC constant",
|mut region| {
region.assign_advice_from_constant(
|| "domain_vc",
config.advices[0],
0,
pallas::Base::from(DOMAIN_VC),
)
},
)?;
let vote_decision = assign_free_advice(
layouter.namespace(|| "witness vote_decision"),
config.advices[0],
self.vote_decision,
)?;
let vote_commitment = vote_commitment::vote_commitment_poseidon(
&config.poseidon_config,
&mut layouter,
"cond12",
domain_vc,
voting_round_id_cond12,
shares_hash,
proposal_id,
vote_decision,
)?;
layouter.constrain_instance(
vote_commitment.cell(),
config.primary,
VOTE_COMMITMENT,
)?;
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct Instance {
pub van_nullifier: pallas::Base,
pub r_vpk_x: pallas::Base,
pub r_vpk_y: pallas::Base,
pub vote_authority_note_new: pallas::Base,
pub vote_commitment: pallas::Base,
pub vote_comm_tree_root: pallas::Base,
pub vote_comm_tree_anchor_height: pallas::Base,
pub proposal_id: pallas::Base,
pub voting_round_id: pallas::Base,
pub ea_pk_x: pallas::Base,
pub ea_pk_y: pallas::Base,
}
impl Instance {
pub fn from_parts(
van_nullifier: pallas::Base,
r_vpk_x: pallas::Base,
r_vpk_y: pallas::Base,
vote_authority_note_new: pallas::Base,
vote_commitment: pallas::Base,
vote_comm_tree_root: pallas::Base,
vote_comm_tree_anchor_height: pallas::Base,
proposal_id: pallas::Base,
voting_round_id: pallas::Base,
ea_pk_x: pallas::Base,
ea_pk_y: pallas::Base,
) -> Self {
Instance {
van_nullifier,
r_vpk_x,
r_vpk_y,
vote_authority_note_new,
vote_commitment,
vote_comm_tree_root,
vote_comm_tree_anchor_height,
proposal_id,
voting_round_id,
ea_pk_x,
ea_pk_y,
}
}
pub fn to_halo2_instance(&self) -> Vec<vesta::Scalar> {
alloc::vec![
self.van_nullifier,
self.r_vpk_x,
self.r_vpk_y,
self.vote_authority_note_new,
self.vote_commitment,
self.vote_comm_tree_root,
self.vote_comm_tree_anchor_height,
self.proposal_id,
self.voting_round_id,
self.ea_pk_x,
self.ea_pk_y,
]
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::circuit::elgamal::{base_to_scalar, elgamal_encrypt, spend_auth_g_affine};
use core::iter;
use ff::Field;
use group::ff::PrimeFieldBits;
use group::{Curve, Group};
use halo2_gadgets::sinsemilla::primitives::CommitDomain;
use halo2_proofs::dev::MockProver;
use pasta_curves::arithmetic::CurveAffine;
use pasta_curves::pallas;
use rand::rngs::OsRng;
use orchard::constants::{
fixed_bases::COMMIT_IVK_PERSONALIZATION,
L_ORCHARD_BASE,
};
fn generate_ea_keypair() -> (pallas::Scalar, pallas::Point, pallas::Affine) {
let ea_sk = pallas::Scalar::from(42u64);
let g = pallas::Point::from(spend_auth_g_affine());
let ea_pk = g * ea_sk;
let ea_pk_affine = ea_pk.to_affine();
(ea_sk, ea_pk, ea_pk_affine)
}
fn encrypt_shares(
shares: [u64; 16],
ea_pk: pallas::Point,
) -> (
[pallas::Base; 16],
[pallas::Base; 16],
[pallas::Base; 16],
[pallas::Base; 16],
[pallas::Base; 16],
[pallas::Base; 16],
pallas::Base,
) {
let mut c1_x = [pallas::Base::zero(); 16];
let mut c2_x = [pallas::Base::zero(); 16];
let mut c1_y = [pallas::Base::zero(); 16];
let mut c2_y = [pallas::Base::zero(); 16];
let randomness: [pallas::Base; 16] = core::array::from_fn(|i| {
pallas::Base::from((i as u64 + 1) * 101)
});
let share_blinds: [pallas::Base; 16] = core::array::from_fn(|i| {
pallas::Base::from(1001u64 + i as u64)
});
for i in 0..16 {
let (cx1, cx2, cy1, cy2) = elgamal_encrypt(
pallas::Base::from(shares[i]),
randomness[i],
ea_pk,
);
c1_x[i] = cx1;
c2_x[i] = cx2;
c1_y[i] = cy1;
c2_y[i] = cy2;
}
let hash = shares_hash(share_blinds, c1_x, c2_x, c1_y, c2_y);
(c1_x, c2_x, c1_y, c2_y, randomness, share_blinds, hash)
}
fn derive_voting_address(
vsk: pallas::Scalar,
nk: pallas::Base,
rivk_v: pallas::Scalar,
) -> (pallas::Affine, pallas::Affine) {
let g = pallas::Point::from(spend_auth_g_affine());
let ak_point = g * vsk;
let ak_x = *ak_point.to_affine().coordinates().unwrap().x();
let domain = CommitDomain::new(COMMIT_IVK_PERSONALIZATION);
let ivk_v = domain
.short_commit(
iter::empty()
.chain(ak_x.to_le_bits().iter().by_vals().take(L_ORCHARD_BASE))
.chain(nk.to_le_bits().iter().by_vals().take(L_ORCHARD_BASE)),
&rivk_v,
)
.expect("CommitIvk should not produce ⊥ for random inputs");
let g_d = pallas::Point::generator() * pallas::Scalar::from(12345u64);
let g_d_affine = g_d.to_affine();
let ivk_v_scalar =
base_to_scalar(ivk_v).expect("ivk_v must be < scalar field modulus");
let pk_d = g_d * ivk_v_scalar;
let pk_d_affine = pk_d.to_affine();
(g_d_affine, pk_d_affine)
}
const TEST_PROPOSAL_ID: u64 = 3;
const TEST_VOTE_DECISION: u64 = 1;
fn set_condition_11(
circuit: &mut Circuit,
shares_hash_val: pallas::Base,
proposal_id: u64,
voting_round_id: pallas::Base,
) -> pallas::Base {
let proposal_id_base = pallas::Base::from(proposal_id);
let vote_decision = pallas::Base::from(TEST_VOTE_DECISION);
circuit.vote_decision = Value::known(vote_decision);
vote_commitment_hash(voting_round_id, shares_hash_val, proposal_id_base, vote_decision)
}
fn build_single_leaf_merkle_path(
leaf: pallas::Base,
) -> ([pallas::Base; VOTE_COMM_TREE_DEPTH], u32, pallas::Base) {
let mut empty_roots = [pallas::Base::zero(); VOTE_COMM_TREE_DEPTH];
empty_roots[0] = poseidon_hash_2(pallas::Base::zero(), pallas::Base::zero());
for i in 1..VOTE_COMM_TREE_DEPTH {
empty_roots[i] = poseidon_hash_2(empty_roots[i - 1], empty_roots[i - 1]);
}
let auth_path = empty_roots;
let mut current = leaf;
for i in 0..VOTE_COMM_TREE_DEPTH {
current = poseidon_hash_2(current, auth_path[i]);
}
(auth_path, 0, current)
}
fn make_test_data_with_authority_and_proposal(
proposal_authority_old: pallas::Base,
proposal_id: u64,
) -> (Circuit, Instance) {
let mut rng = OsRng;
let vsk = pallas::Scalar::random(&mut rng);
let vsk_nk = pallas::Base::random(&mut rng);
let rivk_v = pallas::Scalar::random(&mut rng);
let alpha_v = pallas::Scalar::random(&mut rng);
let (vpk_g_d_affine, vpk_pk_d_affine) = derive_voting_address(vsk, vsk_nk, rivk_v);
let g = pallas::Point::from(spend_auth_g_affine());
let ak_point = g * vsk;
let r_vpk = (ak_point + g * alpha_v).to_affine();
let r_vpk_x = *r_vpk.coordinates().unwrap().x();
let r_vpk_y = *r_vpk.coordinates().unwrap().y();
let vpk_g_d_x = *vpk_g_d_affine.coordinates().unwrap().x();
let vpk_pk_d_x = *vpk_pk_d_affine.coordinates().unwrap().x();
let total_note_value = pallas::Base::from(10_000u64);
let voting_round_id = pallas::Base::random(&mut rng);
let van_comm_rand = pallas::Base::random(&mut rng);
let vote_authority_note_old = van_integrity_hash(
vpk_g_d_x,
vpk_pk_d_x,
total_note_value,
voting_round_id,
proposal_authority_old,
van_comm_rand,
);
let (auth_path, position, vote_comm_tree_root) =
build_single_leaf_merkle_path(vote_authority_note_old);
let van_nullifier = van_nullifier_hash(vsk_nk, voting_round_id, vote_authority_note_old);
let one_shifted = pallas::Base::from(1u64 << proposal_id);
let proposal_authority_new = proposal_authority_old - one_shifted;
let vote_authority_note_new = van_integrity_hash(
vpk_g_d_x,
vpk_pk_d_x,
total_note_value,
voting_round_id,
proposal_authority_new,
van_comm_rand,
);
let shares_u64: [u64; 16] = [625; 16];
let (_ea_sk, ea_pk_point, ea_pk_affine) = generate_ea_keypair();
let ea_pk_x = *ea_pk_affine.coordinates().unwrap().x();
let ea_pk_y = *ea_pk_affine.coordinates().unwrap().y();
let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
encrypt_shares(shares_u64, ea_pk_point);
let mut circuit = Circuit::with_van_witnesses(
Value::known(auth_path),
Value::known(position),
Value::known(vpk_g_d_affine),
Value::known(vpk_pk_d_affine),
Value::known(total_note_value),
Value::known(proposal_authority_old),
Value::known(van_comm_rand),
Value::known(vote_authority_note_old),
Value::known(vsk),
Value::known(rivk_v),
Value::known(vsk_nk),
Value::known(alpha_v),
);
circuit.one_shifted = Value::known(one_shifted);
circuit.shares = shares_u64.map(|s| Value::known(pallas::Base::from(s)));
circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
circuit.share_blinds = share_blinds.map(Value::known);
circuit.share_randomness = randomness.map(Value::known);
circuit.ea_pk = Value::known(ea_pk_affine);
let vote_commitment = set_condition_11(&mut circuit, shares_hash_val, proposal_id, voting_round_id);
let instance = Instance::from_parts(
van_nullifier,
r_vpk_x,
r_vpk_y,
vote_authority_note_new,
vote_commitment,
vote_comm_tree_root,
pallas::Base::zero(),
pallas::Base::from(proposal_id),
voting_round_id,
ea_pk_x,
ea_pk_y,
);
(circuit, instance)
}
fn make_test_data_with_authority(proposal_authority_old: pallas::Base) -> (Circuit, Instance) {
make_test_data_with_authority_and_proposal(proposal_authority_old, TEST_PROPOSAL_ID)
}
fn make_test_data() -> (Circuit, Instance) {
make_test_data_with_authority(pallas::Base::from(13u64))
}
#[test]
fn van_integrity_valid_proof() {
let (circuit, instance) = make_test_data();
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert_eq!(prover.verify(), Ok(()));
}
#[test]
fn van_integrity_wrong_hash_fails() {
let mut rng = OsRng;
let (_, mut instance) = make_test_data();
let wrong_van = pallas::Base::random(&mut rng);
let (auth_path, position, root) = build_single_leaf_merkle_path(wrong_van);
instance.vote_comm_tree_root = root;
let vsk = pallas::Scalar::random(&mut rng);
let vsk_nk = pallas::Base::random(&mut rng);
let rivk_v = pallas::Scalar::random(&mut rng);
let alpha_v = pallas::Scalar::random(&mut rng);
let (vpk_g_d_affine, vpk_pk_d_affine) = derive_voting_address(vsk, vsk_nk, rivk_v);
let g = pallas::Point::from(spend_auth_g_affine());
let r_vpk = (g * vsk + g * alpha_v).to_affine();
instance.r_vpk_x = *r_vpk.coordinates().unwrap().x();
instance.r_vpk_y = *r_vpk.coordinates().unwrap().y();
let shares_u64: [u64; 16] = [625; 16];
let (_ea_sk, ea_pk_point, ea_pk_affine) = generate_ea_keypair();
let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
encrypt_shares(shares_u64, ea_pk_point);
let proposal_authority_old = pallas::Base::from(13u64);
let van_comm_rand = pallas::Base::random(&mut rng);
let mut circuit = Circuit::with_van_witnesses(
Value::known(auth_path),
Value::known(position),
Value::known(vpk_g_d_affine),
Value::known(vpk_pk_d_affine),
Value::known(pallas::Base::from(10_000u64)),
Value::known(proposal_authority_old),
Value::known(van_comm_rand),
Value::known(wrong_van),
Value::known(vsk),
Value::known(rivk_v),
Value::known(vsk_nk),
Value::known(alpha_v),
);
circuit.one_shifted = Value::known(pallas::Base::from(1u64 << TEST_PROPOSAL_ID));
circuit.shares = shares_u64.map(|s| Value::known(pallas::Base::from(s)));
circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
circuit.share_blinds = share_blinds.map(Value::known);
circuit.share_randomness = randomness.map(Value::known);
circuit.ea_pk = Value::known(ea_pk_affine);
let vc = set_condition_11(&mut circuit, shares_hash_val, TEST_PROPOSAL_ID, instance.voting_round_id);
instance.vote_commitment = vc;
instance.proposal_id = pallas::Base::from(TEST_PROPOSAL_ID);
instance.ea_pk_x = *ea_pk_affine.coordinates().unwrap().x();
instance.ea_pk_y = *ea_pk_affine.coordinates().unwrap().y();
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert!(prover.verify().is_err());
}
#[test]
fn van_integrity_wrong_round_id_fails() {
let (circuit, mut instance) = make_test_data();
instance.voting_round_id = pallas::Base::random(&mut OsRng);
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert!(prover.verify().is_err());
}
#[test]
fn van_integrity_hash_deterministic() {
let mut rng = OsRng;
let vpk_g_d = pallas::Base::random(&mut rng);
let vpk_pk_d = pallas::Base::random(&mut rng);
let val = pallas::Base::random(&mut rng);
let round = pallas::Base::random(&mut rng);
let auth = pallas::Base::random(&mut rng);
let rand = pallas::Base::random(&mut rng);
let h1 = van_integrity_hash(vpk_g_d, vpk_pk_d, val, round, auth, rand);
let h2 = van_integrity_hash(vpk_g_d, vpk_pk_d, val, round, auth, rand);
assert_eq!(h1, h2);
let h3 = van_integrity_hash(
pallas::Base::random(&mut rng),
vpk_pk_d,
val,
round,
auth,
rand,
);
assert_ne!(h1, h3);
}
#[test]
fn condition_3_wrong_vsk_fails() {
let mut rng = OsRng;
let vsk = pallas::Scalar::random(&mut rng);
let vsk_nk = pallas::Base::random(&mut rng);
let rivk_v = pallas::Scalar::random(&mut rng);
let (vpk_g_d_affine, vpk_pk_d_affine) = derive_voting_address(vsk, vsk_nk, rivk_v);
let vpk_g_d_x = *vpk_g_d_affine.coordinates().unwrap().x();
let vpk_pk_d_x = *vpk_pk_d_affine.coordinates().unwrap().x();
let total_note_value = pallas::Base::from(10_000u64);
let voting_round_id = pallas::Base::random(&mut rng);
let proposal_authority_old = pallas::Base::from(13u64);
let proposal_id = 3u64;
let van_comm_rand = pallas::Base::random(&mut rng);
let vote_authority_note_old = van_integrity_hash(
vpk_g_d_x, vpk_pk_d_x, total_note_value, voting_round_id,
proposal_authority_old, van_comm_rand,
);
let (auth_path, position, vote_comm_tree_root) =
build_single_leaf_merkle_path(vote_authority_note_old);
let van_nullifier = van_nullifier_hash(vsk_nk, voting_round_id, vote_authority_note_old);
let one_shifted = pallas::Base::from(1u64 << proposal_id);
let proposal_authority_new = proposal_authority_old - one_shifted;
let vote_authority_note_new = van_integrity_hash(
vpk_g_d_x, vpk_pk_d_x, total_note_value, voting_round_id,
proposal_authority_new, van_comm_rand,
);
let shares_u64: [u64; 16] = [625; 16];
let (_ea_sk, ea_pk_point, ea_pk_affine) = generate_ea_keypair();
let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
encrypt_shares(shares_u64, ea_pk_point);
let wrong_vsk = pallas::Scalar::random(&mut rng);
assert_ne!(wrong_vsk, vsk, "test assumes distinct vsk with high probability");
let alpha_v = pallas::Scalar::random(&mut rng);
let g = pallas::Point::from(spend_auth_g_affine());
let r_vpk = (g * vsk + g * alpha_v).to_affine();
let r_vpk_x = *r_vpk.coordinates().unwrap().x();
let r_vpk_y = *r_vpk.coordinates().unwrap().y();
let mut circuit = Circuit::with_van_witnesses(
Value::known(auth_path),
Value::known(position),
Value::known(vpk_g_d_affine),
Value::known(vpk_pk_d_affine),
Value::known(total_note_value),
Value::known(proposal_authority_old),
Value::known(van_comm_rand),
Value::known(vote_authority_note_old),
Value::known(wrong_vsk),
Value::known(rivk_v),
Value::known(vsk_nk),
Value::known(alpha_v),
);
circuit.one_shifted = Value::known(one_shifted);
circuit.shares = shares_u64.map(|s| Value::known(pallas::Base::from(s)));
circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
circuit.share_blinds = share_blinds.map(Value::known);
circuit.share_randomness = randomness.map(Value::known);
circuit.ea_pk = Value::known(ea_pk_affine);
let vc = set_condition_11(&mut circuit, shares_hash_val, proposal_id, voting_round_id);
let instance = Instance::from_parts(
van_nullifier,
r_vpk_x,
r_vpk_y,
vote_authority_note_new,
vc,
vote_comm_tree_root,
pallas::Base::zero(),
pallas::Base::from(proposal_id),
voting_round_id,
*ea_pk_affine.coordinates().unwrap().x(),
*ea_pk_affine.coordinates().unwrap().y(),
);
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert!(prover.verify().is_err(), "condition 3 must reject wrong vsk");
}
#[test]
fn condition_3_wrong_vpk_pk_d_fails() {
let mut rng = OsRng;
let vsk = pallas::Scalar::random(&mut rng);
let vsk_nk = pallas::Base::random(&mut rng);
let rivk_v = pallas::Scalar::random(&mut rng);
let (vpk_g_d_affine, _vpk_pk_d_correct) = derive_voting_address(vsk, vsk_nk, rivk_v);
let vpk_g_d_x = *vpk_g_d_affine.coordinates().unwrap().x();
let wrong_vpk_pk_d_affine = (pallas::Point::generator() * pallas::Scalar::from(99999u64))
.to_affine();
let wrong_vpk_pk_d_x = *wrong_vpk_pk_d_affine.coordinates().unwrap().x();
let total_note_value = pallas::Base::from(10_000u64);
let voting_round_id = pallas::Base::random(&mut rng);
let proposal_authority_old = pallas::Base::from(13u64);
let proposal_id = 3u64;
let van_comm_rand = pallas::Base::random(&mut rng);
let vote_authority_note_old = van_integrity_hash(
vpk_g_d_x,
wrong_vpk_pk_d_x,
total_note_value,
voting_round_id,
proposal_authority_old,
van_comm_rand,
);
let (auth_path, position, vote_comm_tree_root) =
build_single_leaf_merkle_path(vote_authority_note_old);
let van_nullifier = van_nullifier_hash(vsk_nk, voting_round_id, vote_authority_note_old);
let one_shifted = pallas::Base::from(1u64 << proposal_id);
let proposal_authority_new = proposal_authority_old - one_shifted;
let vote_authority_note_new = van_integrity_hash(
vpk_g_d_x,
wrong_vpk_pk_d_x,
total_note_value,
voting_round_id,
proposal_authority_new,
van_comm_rand,
);
let shares_u64: [u64; 16] = [625; 16];
let (_ea_sk, ea_pk_point, ea_pk_affine) = generate_ea_keypair();
let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
encrypt_shares(shares_u64, ea_pk_point);
let alpha_v = pallas::Scalar::random(&mut rng);
let g = pallas::Point::from(spend_auth_g_affine());
let r_vpk = (g * vsk + g * alpha_v).to_affine();
let r_vpk_x = *r_vpk.coordinates().unwrap().x();
let r_vpk_y = *r_vpk.coordinates().unwrap().y();
let mut circuit = Circuit::with_van_witnesses(
Value::known(auth_path),
Value::known(position),
Value::known(vpk_g_d_affine),
Value::known(wrong_vpk_pk_d_affine),
Value::known(total_note_value),
Value::known(proposal_authority_old),
Value::known(van_comm_rand),
Value::known(vote_authority_note_old),
Value::known(vsk),
Value::known(rivk_v),
Value::known(vsk_nk),
Value::known(alpha_v),
);
circuit.one_shifted = Value::known(one_shifted);
circuit.shares = shares_u64.map(|s| Value::known(pallas::Base::from(s)));
circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
circuit.share_blinds = share_blinds.map(Value::known);
circuit.share_randomness = randomness.map(Value::known);
circuit.ea_pk = Value::known(ea_pk_affine);
let vc = set_condition_11(&mut circuit, shares_hash_val, proposal_id, voting_round_id);
let instance = Instance::from_parts(
van_nullifier,
r_vpk_x,
r_vpk_y,
vote_authority_note_new,
vc,
vote_comm_tree_root,
pallas::Base::zero(),
pallas::Base::from(proposal_id),
voting_round_id,
*ea_pk_affine.coordinates().unwrap().x(),
*ea_pk_affine.coordinates().unwrap().y(),
);
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert!(prover.verify().is_err(), "condition 3 must reject wrong vpk_pk_d");
}
#[test]
fn condition_4_wrong_r_vpk_fails() {
let (circuit, mut instance) = make_test_data();
instance.r_vpk_x = pallas::Base::random(&mut OsRng);
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert!(prover.verify().is_err(), "condition 4 must reject wrong r_vpk");
}
#[test]
fn van_nullifier_wrong_public_input_fails() {
let (circuit, mut instance) = make_test_data();
instance.van_nullifier = pallas::Base::random(&mut OsRng);
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert!(prover.verify().is_err());
}
#[test]
fn van_nullifier_wrong_vsk_nk_fails() {
let mut rng = OsRng;
let vsk = pallas::Scalar::random(&mut rng);
let vsk_nk = pallas::Base::random(&mut rng);
let rivk_v = pallas::Scalar::random(&mut rng);
let (vpk_g_d_affine, vpk_pk_d_affine) = derive_voting_address(vsk, vsk_nk, rivk_v);
let vpk_g_d_x = *vpk_g_d_affine.coordinates().unwrap().x();
let vpk_pk_d_x = *vpk_pk_d_affine.coordinates().unwrap().x();
let total_note_value = pallas::Base::from(10_000u64);
let voting_round_id = pallas::Base::random(&mut rng);
let proposal_authority_old = pallas::Base::from(5u64); let van_comm_rand = pallas::Base::random(&mut rng);
let proposal_id = 0u64;
let vote_authority_note_old = van_integrity_hash(
vpk_g_d_x, vpk_pk_d_x, total_note_value, voting_round_id,
proposal_authority_old, van_comm_rand,
);
let (auth_path, position, vote_comm_tree_root) =
build_single_leaf_merkle_path(vote_authority_note_old);
let van_nullifier = van_nullifier_hash(vsk_nk, voting_round_id, vote_authority_note_old);
let one_shifted = pallas::Base::from(1u64 << proposal_id);
let proposal_authority_new = proposal_authority_old - one_shifted;
let vote_authority_note_new = van_integrity_hash(
vpk_g_d_x, vpk_pk_d_x, total_note_value, voting_round_id,
proposal_authority_new, van_comm_rand,
);
let wrong_vsk_nk = pallas::Base::random(&mut rng);
let alpha_v = pallas::Scalar::random(&mut rng);
let g = pallas::Point::from(spend_auth_g_affine());
let r_vpk = (g * vsk + g * alpha_v).to_affine();
let r_vpk_x = *r_vpk.coordinates().unwrap().x();
let r_vpk_y = *r_vpk.coordinates().unwrap().y();
let shares_u64: [u64; 16] = [625; 16];
let (_ea_sk, ea_pk_point, ea_pk_affine) = generate_ea_keypair();
let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
encrypt_shares(shares_u64, ea_pk_point);
let mut circuit = Circuit::with_van_witnesses(
Value::known(auth_path),
Value::known(position),
Value::known(vpk_g_d_affine),
Value::known(vpk_pk_d_affine),
Value::known(total_note_value),
Value::known(proposal_authority_old),
Value::known(van_comm_rand),
Value::known(vote_authority_note_old),
Value::known(vsk),
Value::known(rivk_v),
Value::known(wrong_vsk_nk),
Value::known(alpha_v),
);
circuit.one_shifted = Value::known(one_shifted);
circuit.shares = shares_u64.map(|s| Value::known(pallas::Base::from(s)));
circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
circuit.share_blinds = share_blinds.map(Value::known);
circuit.share_randomness = randomness.map(Value::known);
circuit.ea_pk = Value::known(ea_pk_affine);
let vc = set_condition_11(&mut circuit, shares_hash_val, proposal_id, voting_round_id);
let instance = Instance::from_parts(
van_nullifier,
r_vpk_x,
r_vpk_y,
vote_authority_note_new,
vc,
vote_comm_tree_root,
pallas::Base::zero(),
pallas::Base::from(proposal_id),
voting_round_id,
*ea_pk_affine.coordinates().unwrap().x(),
*ea_pk_affine.coordinates().unwrap().y(),
);
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert!(prover.verify().is_err());
}
#[test]
fn van_nullifier_hash_deterministic() {
let mut rng = OsRng;
let nk = pallas::Base::random(&mut rng);
let round = pallas::Base::random(&mut rng);
let van = pallas::Base::random(&mut rng);
let h1 = van_nullifier_hash(nk, round, van);
let h2 = van_nullifier_hash(nk, round, van);
assert_eq!(h1, h2);
let h3 = van_nullifier_hash(pallas::Base::random(&mut rng), round, van);
assert_ne!(h1, h3);
}
#[test]
fn domain_van_nullifier_deterministic() {
let d1 = domain_van_nullifier();
let d2 = domain_van_nullifier();
assert_eq!(d1, d2);
assert_ne!(d1, pallas::Base::zero());
}
#[test]
fn proposal_authority_decrement_minimum_valid() {
let (circuit, instance) =
make_test_data_with_authority_and_proposal(pallas::Base::from(2u64), 1);
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert_eq!(prover.verify(), Ok(()));
}
#[test]
fn proposal_authority_zero_fails() {
let (circuit, instance) = make_test_data_with_authority(pallas::Base::zero());
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert!(prover.verify().is_err());
}
#[test]
fn proposal_id_zero_fails() {
let (circuit, instance) =
make_test_data_with_authority_and_proposal(pallas::Base::one(), 0);
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert!(prover.verify().is_err(), "proposal_id = 0 must be rejected");
}
#[test]
fn proposal_authority_full_authority_proposal_1_passes() {
const MAX_PROPOSAL_AUTHORITY: u64 = 65535;
let (circuit, instance) = make_test_data_with_authority_and_proposal(
pallas::Base::from(MAX_PROPOSAL_AUTHORITY),
1,
);
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert_eq!(prover.verify(), Ok(()));
}
#[test]
fn proposal_authority_wrong_new_fails() {
let (circuit, mut instance) =
make_test_data_with_authority_and_proposal(pallas::Base::from(65535u64), 1);
instance.vote_authority_note_new = pallas::Base::random(&mut OsRng);
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert!(prover.verify().is_err());
}
#[test]
fn proposal_authority_bit_not_set_fails() {
let (circuit, instance) =
make_test_data_with_authority_and_proposal(pallas::Base::from(4u64), 1);
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert!(prover.verify().is_err());
}
#[test]
fn proposal_authority_condition6_run_sel_constraint() {
let (circuit, instance) =
make_test_data_with_authority_and_proposal(pallas::Base::from(3u64), 1);
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert_eq!(prover.verify(), Ok(()));
}
#[test]
fn proposal_authority_exceeds_16_bits_fails() {
let (circuit, instance) =
make_test_data_with_authority(pallas::Base::from(65536u64));
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert!(
prover.verify().is_err(),
"authority > 65535 must be rejected by the 16-bit bit decomposition"
);
}
#[test]
fn new_van_integrity_wrong_public_input_fails() {
let (circuit, mut instance) = make_test_data();
instance.vote_authority_note_new = pallas::Base::random(&mut OsRng);
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert!(prover.verify().is_err());
}
#[test]
fn new_van_integrity_large_authority() {
let (circuit, instance) =
make_test_data_with_authority(pallas::Base::from(0xFFF8u64));
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert_eq!(prover.verify(), Ok(()));
}
#[test]
fn van_membership_wrong_root_fails() {
let (circuit, mut instance) = make_test_data();
instance.vote_comm_tree_root = pallas::Base::random(&mut OsRng);
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert!(prover.verify().is_err());
}
#[test]
fn van_membership_nonzero_position() {
let mut rng = OsRng;
let vsk = pallas::Scalar::random(&mut rng);
let vsk_nk = pallas::Base::random(&mut rng);
let rivk_v = pallas::Scalar::random(&mut rng);
let (vpk_g_d_affine, vpk_pk_d_affine) = derive_voting_address(vsk, vsk_nk, rivk_v);
let vpk_g_d_x = *vpk_g_d_affine.coordinates().unwrap().x();
let vpk_pk_d_x = *vpk_pk_d_affine.coordinates().unwrap().x();
let total_note_value = pallas::Base::from(10_000u64);
let voting_round_id = pallas::Base::random(&mut rng);
let proposal_authority_old = pallas::Base::from(5u64); let proposal_id = 2u64;
let van_comm_rand = pallas::Base::random(&mut rng);
let vote_authority_note_old = van_integrity_hash(
vpk_g_d_x, vpk_pk_d_x, total_note_value, voting_round_id,
proposal_authority_old, van_comm_rand,
);
let position: u32 = 7;
let mut empty_roots = [pallas::Base::zero(); VOTE_COMM_TREE_DEPTH];
empty_roots[0] = poseidon_hash_2(pallas::Base::zero(), pallas::Base::zero());
for i in 1..VOTE_COMM_TREE_DEPTH {
empty_roots[i] = poseidon_hash_2(empty_roots[i - 1], empty_roots[i - 1]);
}
let auth_path = empty_roots;
let mut current = vote_authority_note_old;
for i in 0..VOTE_COMM_TREE_DEPTH {
if (position >> i) & 1 == 0 {
current = poseidon_hash_2(current, auth_path[i]);
} else {
current = poseidon_hash_2(auth_path[i], current);
}
}
let vote_comm_tree_root = current;
let van_nullifier = van_nullifier_hash(vsk_nk, voting_round_id, vote_authority_note_old);
let one_shifted = pallas::Base::from(1u64 << proposal_id);
let proposal_authority_new = proposal_authority_old - one_shifted;
let vote_authority_note_new = van_integrity_hash(
vpk_g_d_x, vpk_pk_d_x, total_note_value, voting_round_id,
proposal_authority_new, van_comm_rand,
);
let alpha_v = pallas::Scalar::random(&mut rng);
let g = pallas::Point::from(spend_auth_g_affine());
let r_vpk = (g * vsk + g * alpha_v).to_affine();
let r_vpk_x = *r_vpk.coordinates().unwrap().x();
let r_vpk_y = *r_vpk.coordinates().unwrap().y();
let shares_u64: [u64; 16] = [625; 16];
let (_ea_sk, ea_pk_point, ea_pk_affine) = generate_ea_keypair();
let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
encrypt_shares(shares_u64, ea_pk_point);
let mut circuit = Circuit::with_van_witnesses(
Value::known(auth_path),
Value::known(position),
Value::known(vpk_g_d_affine),
Value::known(vpk_pk_d_affine),
Value::known(total_note_value),
Value::known(proposal_authority_old),
Value::known(van_comm_rand),
Value::known(vote_authority_note_old),
Value::known(vsk),
Value::known(rivk_v),
Value::known(vsk_nk),
Value::known(alpha_v),
);
circuit.one_shifted = Value::known(one_shifted);
circuit.shares = shares_u64.map(|s| Value::known(pallas::Base::from(s)));
circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
circuit.share_blinds = share_blinds.map(Value::known);
circuit.share_randomness = randomness.map(Value::known);
circuit.ea_pk = Value::known(ea_pk_affine);
let vc = set_condition_11(&mut circuit, shares_hash_val, proposal_id, voting_round_id);
let instance = Instance::from_parts(
van_nullifier,
r_vpk_x,
r_vpk_y,
vote_authority_note_new,
vc,
vote_comm_tree_root,
pallas::Base::zero(),
pallas::Base::from(proposal_id),
voting_round_id,
*ea_pk_affine.coordinates().unwrap().x(),
*ea_pk_affine.coordinates().unwrap().y(),
);
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert_eq!(prover.verify(), Ok(()));
}
#[test]
fn poseidon_hash_2_deterministic() {
let mut rng = OsRng;
let a = pallas::Base::random(&mut rng);
let b = pallas::Base::random(&mut rng);
assert_eq!(poseidon_hash_2(a, b), poseidon_hash_2(a, b));
assert_ne!(poseidon_hash_2(a, b), poseidon_hash_2(b, a));
}
#[test]
fn shares_sum_wrong_total_fails() {
let (mut circuit, instance) = make_test_data();
circuit.shares[3] = Value::known(pallas::Base::from(999u64));
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert!(prover.verify().is_err());
}
#[test]
fn shares_range_max_valid() {
let max_share = pallas::Base::from((1u64 << 30) - 1); let total = (0..16).fold(pallas::Base::zero(), |acc, _| acc + max_share);
let mut rng = OsRng;
let vsk = pallas::Scalar::random(&mut rng);
let vsk_nk = pallas::Base::random(&mut rng);
let rivk_v = pallas::Scalar::random(&mut rng);
let (vpk_g_d_affine, vpk_pk_d_affine) = derive_voting_address(vsk, vsk_nk, rivk_v);
let vpk_g_d_x = *vpk_g_d_affine.coordinates().unwrap().x();
let vpk_pk_d_x = *vpk_pk_d_affine.coordinates().unwrap().x();
let voting_round_id = pallas::Base::random(&mut rng);
let proposal_authority_old = pallas::Base::from(5u64); let proposal_id = 2u64;
let van_comm_rand = pallas::Base::random(&mut rng);
let vote_authority_note_old = van_integrity_hash(
vpk_g_d_x, vpk_pk_d_x, total, voting_round_id,
proposal_authority_old, van_comm_rand,
);
let (auth_path, position, vote_comm_tree_root) =
build_single_leaf_merkle_path(vote_authority_note_old);
let van_nullifier = van_nullifier_hash(vsk_nk, voting_round_id, vote_authority_note_old);
let one_shifted = pallas::Base::from(1u64 << proposal_id);
let proposal_authority_new = proposal_authority_old - one_shifted;
let vote_authority_note_new = van_integrity_hash(
vpk_g_d_x, vpk_pk_d_x, total, voting_round_id,
proposal_authority_new, van_comm_rand,
);
let max_share_u64 = (1u64 << 30) - 1;
let shares_u64: [u64; 16] = [max_share_u64; 16];
let (_ea_sk, ea_pk_point, ea_pk_affine) = generate_ea_keypair();
let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
encrypt_shares(shares_u64, ea_pk_point);
let alpha_v = pallas::Scalar::random(&mut rng);
let g = pallas::Point::from(spend_auth_g_affine());
let r_vpk = (g * vsk + g * alpha_v).to_affine();
let r_vpk_x = *r_vpk.coordinates().unwrap().x();
let r_vpk_y = *r_vpk.coordinates().unwrap().y();
let mut circuit = Circuit::with_van_witnesses(
Value::known(auth_path),
Value::known(position),
Value::known(vpk_g_d_affine),
Value::known(vpk_pk_d_affine),
Value::known(total),
Value::known(proposal_authority_old),
Value::known(van_comm_rand),
Value::known(vote_authority_note_old),
Value::known(vsk),
Value::known(rivk_v),
Value::known(vsk_nk),
Value::known(alpha_v),
);
circuit.one_shifted = Value::known(one_shifted);
circuit.shares = [Value::known(max_share); 16];
circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
circuit.share_blinds = share_blinds.map(Value::known);
circuit.share_randomness = randomness.map(Value::known);
circuit.ea_pk = Value::known(ea_pk_affine);
let vc = set_condition_11(&mut circuit, shares_hash_val, proposal_id, voting_round_id);
let instance = Instance::from_parts(
van_nullifier,
r_vpk_x,
r_vpk_y,
vote_authority_note_new,
vc,
vote_comm_tree_root,
pallas::Base::zero(),
pallas::Base::from(proposal_id),
voting_round_id,
*ea_pk_affine.coordinates().unwrap().x(),
*ea_pk_affine.coordinates().unwrap().y(),
);
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert_eq!(prover.verify(), Ok(()));
}
#[test]
fn shares_range_overflow_fails() {
let (mut circuit, instance) = make_test_data();
circuit.shares[0] = Value::known(pallas::Base::from(1u64 << 30));
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert!(prover.verify().is_err());
}
#[test]
fn shares_range_field_wrap_fails() {
let (mut circuit, instance) = make_test_data();
circuit.shares[0] = Value::known(-pallas::Base::one());
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert!(prover.verify().is_err());
}
#[test]
fn shares_range_single_overflow_correct_sum_fails() {
let mut rng = OsRng;
let overflow_share = pallas::Base::from(1u64 << 30); let normal_share_u64 = 625u64;
let total_note_value = overflow_share + pallas::Base::from(15u64 * normal_share_u64);
let vsk = pallas::Scalar::random(&mut rng);
let vsk_nk = pallas::Base::random(&mut rng);
let rivk_v = pallas::Scalar::random(&mut rng);
let alpha_v = pallas::Scalar::random(&mut rng);
let (vpk_g_d_affine, vpk_pk_d_affine) = derive_voting_address(vsk, vsk_nk, rivk_v);
let vpk_g_d_x = *vpk_g_d_affine.coordinates().unwrap().x();
let vpk_pk_d_x = *vpk_pk_d_affine.coordinates().unwrap().x();
let voting_round_id = pallas::Base::random(&mut rng);
let proposal_authority_old = pallas::Base::from(13u64); let proposal_id = TEST_PROPOSAL_ID;
let van_comm_rand = pallas::Base::random(&mut rng);
let vote_authority_note_old = van_integrity_hash(
vpk_g_d_x, vpk_pk_d_x, total_note_value, voting_round_id,
proposal_authority_old, van_comm_rand,
);
let (auth_path, position, vote_comm_tree_root) =
build_single_leaf_merkle_path(vote_authority_note_old);
let van_nullifier = van_nullifier_hash(vsk_nk, voting_round_id, vote_authority_note_old);
let one_shifted = pallas::Base::from(1u64 << proposal_id);
let proposal_authority_new = proposal_authority_old - one_shifted;
let vote_authority_note_new = van_integrity_hash(
vpk_g_d_x, vpk_pk_d_x, total_note_value, voting_round_id,
proposal_authority_new, van_comm_rand,
);
let shares_u64: [u64; 16] = {
let mut arr = [normal_share_u64; 16];
arr[0] = 1u64 << 30;
arr
};
let (_ea_sk, ea_pk_point, ea_pk_affine) = generate_ea_keypair();
let (enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y, randomness, share_blinds, shares_hash_val) =
encrypt_shares(shares_u64, ea_pk_point);
let g = pallas::Point::from(spend_auth_g_affine());
let r_vpk = (g * vsk + g * alpha_v).to_affine();
let mut circuit = Circuit::with_van_witnesses(
Value::known(auth_path),
Value::known(position),
Value::known(vpk_g_d_affine),
Value::known(vpk_pk_d_affine),
Value::known(total_note_value),
Value::known(proposal_authority_old),
Value::known(van_comm_rand),
Value::known(vote_authority_note_old),
Value::known(vsk),
Value::known(rivk_v),
Value::known(vsk_nk),
Value::known(alpha_v),
);
circuit.one_shifted = Value::known(one_shifted);
circuit.shares = shares_u64.map(|s| Value::known(pallas::Base::from(s)));
circuit.enc_share_c1_x = enc_c1_x.map(Value::known);
circuit.enc_share_c2_x = enc_c2_x.map(Value::known);
circuit.enc_share_c1_y = enc_c1_y.map(Value::known);
circuit.enc_share_c2_y = enc_c2_y.map(Value::known);
circuit.share_blinds = share_blinds.map(Value::known);
circuit.share_randomness = randomness.map(Value::known);
circuit.ea_pk = Value::known(ea_pk_affine);
let vote_commitment =
set_condition_11(&mut circuit, shares_hash_val, proposal_id, voting_round_id);
let instance = Instance::from_parts(
van_nullifier,
*r_vpk.coordinates().unwrap().x(),
*r_vpk.coordinates().unwrap().y(),
vote_authority_note_new,
vote_commitment,
vote_comm_tree_root,
pallas::Base::zero(),
pallas::Base::from(proposal_id),
voting_round_id,
*ea_pk_affine.coordinates().unwrap().x(),
*ea_pk_affine.coordinates().unwrap().y(),
);
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert!(
prover.verify().is_err(),
"range check must reject a share equal to 2^30 even when the total sum is correct"
);
}
#[test]
fn shares_hash_valid_proof() {
let (circuit, instance) = make_test_data();
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert_eq!(prover.verify(), Ok(()));
}
#[test]
fn shares_hash_wrong_enc_share_fails() {
let (mut circuit, instance) = make_test_data();
circuit.enc_share_c1_x[0] = Value::known(pallas::Base::random(&mut OsRng));
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert!(prover.verify().is_err());
}
#[test]
fn shares_hash_wrong_instance_fails() {
let (circuit, mut instance) = make_test_data();
instance.vote_commitment = pallas::Base::random(&mut OsRng);
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert!(prover.verify().is_err());
}
#[test]
fn shares_hash_deterministic() {
let mut rng = OsRng;
let blinds: [pallas::Base; 16] =
core::array::from_fn(|_| pallas::Base::random(&mut rng));
let c1_x: [pallas::Base; 16] =
core::array::from_fn(|_| pallas::Base::random(&mut rng));
let c2_x: [pallas::Base; 16] =
core::array::from_fn(|_| pallas::Base::random(&mut rng));
let c1_y: [pallas::Base; 16] =
core::array::from_fn(|_| pallas::Base::random(&mut rng));
let c2_y: [pallas::Base; 16] =
core::array::from_fn(|_| pallas::Base::random(&mut rng));
let h1 = shares_hash(blinds, c1_x, c2_x, c1_y, c2_y);
let h2 = shares_hash(blinds, c1_x, c2_x, c1_y, c2_y);
assert_eq!(h1, h2);
let mut c1_x_alt = c1_x;
c1_x_alt[2] = pallas::Base::random(&mut rng);
let h3 = shares_hash(blinds, c1_x_alt, c2_x, c1_y, c2_y);
assert_ne!(h1, h3);
let h4 = shares_hash(blinds, c2_x, c1_x, c2_y, c1_y);
assert_ne!(h1, h4);
let blinds_alt: [pallas::Base; 16] =
core::array::from_fn(|_| pallas::Base::random(&mut rng));
let h5 = shares_hash(blinds_alt, c1_x, c2_x, c1_y, c2_y);
assert_ne!(h1, h5);
}
#[test]
fn share_commitment_deterministic() {
let mut rng = OsRng;
let blind = pallas::Base::random(&mut rng);
let c1_x = pallas::Base::random(&mut rng);
let c2_x = pallas::Base::random(&mut rng);
let c1_y = pallas::Base::random(&mut rng);
let c2_y = pallas::Base::random(&mut rng);
let h1 = share_commitment(blind, c1_x, c2_x, c1_y, c2_y);
let h2 = share_commitment(blind, c1_x, c2_x, c1_y, c2_y);
assert_eq!(h1, h2);
let h3 = share_commitment(blind, c2_x, c1_x, c2_y, c1_y);
assert_ne!(h1, h3);
let blind_alt = pallas::Base::random(&mut rng);
let h4 = share_commitment(blind_alt, c1_x, c2_x, c1_y, c2_y);
assert_ne!(h1, h4);
}
#[derive(Clone, Default)]
struct ShareCommitmentTestCircuit {
blind: pallas::Base,
c1_x: pallas::Base,
c2_x: pallas::Base,
c1_y: pallas::Base,
c2_y: pallas::Base,
}
#[derive(Clone)]
struct ShareCommitmentTestConfig {
primary: Column<InstanceColumn>,
advices: [Column<Advice>; 5],
poseidon_config: PoseidonConfig<pallas::Base, 3, 2>,
}
impl plonk::Circuit<pallas::Base> for ShareCommitmentTestCircuit {
type Config = ShareCommitmentTestConfig;
type FloorPlanner = floor_planner::V1;
fn without_witnesses(&self) -> Self {
Self::default()
}
fn configure(meta: &mut ConstraintSystem<pallas::Base>) -> Self::Config {
let primary = meta.instance_column();
meta.enable_equality(primary);
let advices: [Column<Advice>; 5] = core::array::from_fn(|_| meta.advice_column());
for col in &advices {
meta.enable_equality(*col);
}
let fixed: [Column<Fixed>; 6] = core::array::from_fn(|_| meta.fixed_column());
let constants = meta.fixed_column();
meta.enable_constant(constants);
let rc_a = fixed[0..3].try_into().unwrap();
let rc_b = fixed[3..6].try_into().unwrap();
let poseidon_config = PoseidonChip::configure::<poseidon::P128Pow5T3>(
meta,
advices[1..4].try_into().unwrap(),
advices[4],
rc_a,
rc_b,
);
ShareCommitmentTestConfig {
primary,
advices,
poseidon_config,
}
}
fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<pallas::Base>,
) -> Result<(), plonk::Error> {
let blind_cell = assign_free_advice(
layouter.namespace(|| "blind"),
config.advices[0],
Value::known(self.blind),
)?;
let c1_x_cell = assign_free_advice(
layouter.namespace(|| "c1_x"),
config.advices[0],
Value::known(self.c1_x),
)?;
let c2_x_cell = assign_free_advice(
layouter.namespace(|| "c2_x"),
config.advices[0],
Value::known(self.c2_x),
)?;
let c1_y_cell = assign_free_advice(
layouter.namespace(|| "c1_y"),
config.advices[0],
Value::known(self.c1_y),
)?;
let c2_y_cell = assign_free_advice(
layouter.namespace(|| "c2_y"),
config.advices[0],
Value::known(self.c2_y),
)?;
let chip = PoseidonChip::construct(config.poseidon_config.clone());
let result = hash_share_commitment_in_circuit(
chip,
layouter.namespace(|| "share_comm"),
blind_cell,
c1_x_cell,
c2_x_cell,
c1_y_cell,
c2_y_cell,
0,
)?;
layouter.constrain_instance(result.cell(), config.primary, 0)?;
Ok(())
}
}
#[test]
fn hash_share_commitment_in_circuit_matches_native() {
let mut rng = OsRng;
let blind = pallas::Base::random(&mut rng);
let c1_x = pallas::Base::random(&mut rng);
let c2_x = pallas::Base::random(&mut rng);
let c1_y = pallas::Base::random(&mut rng);
let c2_y = pallas::Base::random(&mut rng);
let expected = share_commitment(blind, c1_x, c2_x, c1_y, c2_y);
let circuit = ShareCommitmentTestCircuit {
blind,
c1_x,
c2_x,
c1_y,
c2_y,
};
let instance = vec![vec![expected]];
const TEST_K: u32 = 10;
let prover =
MockProver::run(TEST_K, &circuit, instance).expect("MockProver::run failed");
assert_eq!(prover.verify(), Ok(()));
}
#[test]
fn encryption_integrity_valid_proof() {
let (circuit, instance) = make_test_data();
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert_eq!(prover.verify(), Ok(()));
}
#[test]
fn encryption_integrity_wrong_randomness_fails() {
let (mut circuit, instance) = make_test_data();
circuit.share_randomness[0] = Value::known(pallas::Base::from(9999u64));
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert!(prover.verify().is_err());
}
#[test]
fn encryption_integrity_wrong_ea_pk_instance_fails() {
let (circuit, mut instance) = make_test_data();
instance.ea_pk_x = pallas::Base::from(12345u64);
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert!(prover.verify().is_err());
}
#[test]
fn encryption_integrity_wrong_share_fails() {
let (mut circuit, instance) = make_test_data();
circuit.shares[0] = Value::known(pallas::Base::from(9999u64));
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert!(prover.verify().is_err());
}
#[test]
fn encryption_integrity_wrong_enc_c2_x_fails() {
let (mut circuit, instance) = make_test_data();
circuit.enc_share_c2_x[0] = Value::known(pallas::Base::random(&mut OsRng));
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert!(prover.verify().is_err());
}
#[test]
fn elgamal_encrypt_deterministic() {
let (_ea_sk, ea_pk_point, _ea_pk_affine) = generate_ea_keypair();
let v = pallas::Base::from(1000u64);
let r = pallas::Base::from(42u64);
let (c1_a, c2_a, _, _) = elgamal_encrypt(v, r, ea_pk_point);
let (c1_b, c2_b, _, _) = elgamal_encrypt(v, r, ea_pk_point);
assert_eq!(c1_a, c1_b);
assert_eq!(c2_a, c2_b);
let (c1_c, _, _, _) = elgamal_encrypt(v, pallas::Base::from(99u64), ea_pk_point);
assert_ne!(c1_a, c1_c);
}
#[test]
fn base_to_scalar_accepts_elgamal_inputs() {
assert!(base_to_scalar(pallas::Base::zero()).is_some());
assert!(base_to_scalar(pallas::Base::from(1u64)).is_some());
assert!(base_to_scalar(pallas::Base::from(1_000u64)).is_some());
assert!(base_to_scalar(pallas::Base::from(404u64)).is_some());
for r in (1u64..=16).map(|i| i * 101) {
assert!(
base_to_scalar(pallas::Base::from(r)).is_some(),
"r = {} must convert for El Gamal",
r
);
}
}
#[test]
fn vote_commitment_integrity_valid_proof() {
let (circuit, instance) = make_test_data();
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert_eq!(prover.verify(), Ok(()));
}
#[test]
fn vote_commitment_wrong_decision_fails() {
let (mut circuit, instance) = make_test_data();
circuit.vote_decision = Value::known(pallas::Base::from(99u64));
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert!(prover.verify().is_err());
}
#[test]
fn vote_commitment_wrong_proposal_id_fails() {
let (circuit, mut instance) = make_test_data();
instance.proposal_id = pallas::Base::from(999u64);
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert!(prover.verify().is_err());
}
#[test]
fn vote_commitment_wrong_instance_fails() {
let (circuit, mut instance) = make_test_data();
instance.vote_commitment = pallas::Base::random(&mut OsRng);
let prover = MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]).unwrap();
assert!(prover.verify().is_err());
}
#[test]
fn vote_commitment_hash_deterministic() {
let mut rng = OsRng;
let rid = pallas::Base::random(&mut rng);
let sh = pallas::Base::random(&mut rng);
let pid = pallas::Base::from(5u64);
let dec = pallas::Base::from(1u64);
let h1 = vote_commitment_hash(rid, sh, pid, dec);
let h2 = vote_commitment_hash(rid, sh, pid, dec);
assert_eq!(h1, h2);
let h3 = vote_commitment_hash(rid, sh, pallas::Base::from(6u64), dec);
assert_ne!(h1, h3);
let h4 = vote_commitment_hash(pallas::Base::from(999u64), sh, pid, dec);
assert_ne!(h1, h4);
assert_ne!(h1, pallas::Base::zero());
}
#[test]
fn instance_has_eleven_public_inputs() {
let (_, instance) = make_test_data();
assert_eq!(instance.to_halo2_instance().len(), 11);
}
#[test]
fn default_circuit_with_valid_instance_fails() {
let (_, instance) = make_test_data();
let circuit = Circuit::default();
match MockProver::run(K, &circuit, vec![instance.to_halo2_instance()]) {
Ok(prover) => assert!(prover.verify().is_err()),
Err(_) => {} }
}
#[test]
#[ignore]
fn row_budget() {
use std::println;
use halo2_proofs::dev::CircuitCost;
use pasta_curves::vesta;
let (circuit, _) = make_test_data();
let cost = CircuitCost::<vesta::Point, _>::measure(K, &circuit);
let debug = alloc::format!("{cost:?}");
let extract = |field: &str| -> usize {
let prefix = alloc::format!("{field}: ");
debug.split(&prefix)
.nth(1)
.and_then(|s| s.split([',', ' ', '}']).next())
.and_then(|n| n.parse().ok())
.unwrap_or(0)
};
let max_rows = extract("max_rows");
let max_advice_rows = extract("max_advice_rows");
let max_fixed_rows = extract("max_fixed_rows");
let total_available = 1usize << K;
println!("=== vote-proof circuit row budget (K={K}) ===");
println!(" max_rows (floor-planner high-water mark): {max_rows}");
println!(" max_advice_rows: {max_advice_rows}");
println!(" max_fixed_rows: {max_fixed_rows}");
println!(" 2^K (total available rows): {total_available}");
println!(" headroom: {}", total_available.saturating_sub(max_rows));
println!(" utilisation: {:.1}%",
100.0 * max_rows as f64 / total_available as f64);
println!();
println!(" Full debug: {debug}");
let cost_default = CircuitCost::<vesta::Point, _>::measure(K, &Circuit::default());
let debug_default = alloc::format!("{cost_default:?}");
let max_rows_default = debug_default
.split("max_rows: ").nth(1)
.and_then(|s| s.split([',', ' ', '}']).next())
.and_then(|n| n.parse::<usize>().ok())
.unwrap_or(0);
if max_rows_default == max_rows {
println!(" Witness-independence: PASS \
(Circuit::default() max_rows={max_rows_default} == filled max_rows={max_rows})");
} else {
println!(" Witness-independence: FAIL \
(Circuit::default() max_rows={max_rows_default} != filled max_rows={max_rows}) \
— row count depends on witness values!");
}
println!(" VOTE_COMM_TREE_DEPTH (circuit constant): {VOTE_COMM_TREE_DEPTH}");
for probe_k in 11u32..=K {
let (c, inst) = make_test_data();
match MockProver::run(probe_k, &c, vec![inst.to_halo2_instance()]) {
Err(_) => {
println!(" K={probe_k}: not enough rows (synthesizer rejected)");
continue;
}
Ok(p) => match p.verify() {
Ok(()) => {
println!(" Minimum viable K: {probe_k} (2^{probe_k} = {} rows, {:.1}% headroom)",
1usize << probe_k,
100.0 * (1.0 - max_rows as f64 / (1usize << probe_k) as f64));
break;
}
Err(_) => println!(" K={probe_k}: too small"),
},
}
}
}
}