use std::{string::String, vec::Vec};
use halo2_proofs::{
pasta::EqAffine,
plonk::{self, keygen_pk, keygen_vk},
poly::commitment::Params,
};
use super::circuit::{Circuit, Instance, K};
use crate::{
prove_error::{create_proof_bytes, verify_proof_bytes},
ProveError,
};
pub type VoteProofKeys = (
Params<EqAffine>,
plonk::ProvingKey<EqAffine>,
plonk::VerifyingKey<EqAffine>,
);
static VOTE_PROOF_KEYS_CACHE: std::sync::OnceLock<Result<VoteProofKeys, String>> =
std::sync::OnceLock::new();
pub fn vote_proof_cached_keys() -> Result<&'static VoteProofKeys, ProveError> {
match VOTE_PROOF_KEYS_CACHE.get_or_init(|| {
let params = vote_proof_params();
vote_proof_proving_key(¶ms)
.map(|(pk, vk)| (params, pk, vk))
.map_err(|error| error.to_string())
}) {
Ok(keys) => Ok(keys),
Err(error) => Err(ProveError::CachedKeygen(error.clone())),
}
}
pub fn warm_vote_proof_keys() -> Result<(), ProveError> {
vote_proof_cached_keys().map(|_| ())
}
pub fn vote_proof_params() -> Params<EqAffine> {
Params::new(K)
}
pub fn vote_proof_proving_key(
params: &Params<EqAffine>,
) -> Result<(plonk::ProvingKey<EqAffine>, plonk::VerifyingKey<EqAffine>), ProveError> {
let empty_circuit = Circuit::default();
let vk = keygen_vk(params, &empty_circuit).map_err(ProveError::KeygenVk)?;
let pk = keygen_pk(params, vk.clone(), &empty_circuit).map_err(ProveError::KeygenPk)?;
Ok((pk, vk))
}
pub fn create_vote_proof(circuit: Circuit, instance: &Instance) -> Result<Vec<u8>, ProveError> {
let (params, pk, _vk) = vote_proof_cached_keys()?;
let public_inputs = instance.to_halo2_instance();
create_proof_bytes(params, pk, circuit, &public_inputs)
}
pub fn verify_vote_proof(proof: &[u8], instance: &Instance) -> Result<(), String> {
let (params, _pk, vk) = vote_proof_cached_keys().map_err(|error| error.to_string())?;
let public_inputs = instance.to_halo2_instance();
verify_proof_bytes("vote proof", params, vk, proof, &public_inputs)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ProveError;
use halo2_proofs::plonk;
use pasta_curves::group::ff::PrimeField;
use pasta_curves::pallas;
fn serialize_instance(instance: &Instance) -> Vec<u8> {
instance
.to_halo2_instance()
.into_iter()
.flat_map(|input| input.to_repr())
.collect()
}
fn minimal_instance() -> Instance {
Instance::from_parts(
pallas::Base::from(1),
pallas::Base::from(2),
pallas::Base::from(3),
pallas::Base::from(4),
pallas::Base::from(5),
pallas::Base::from(6),
pallas::Base::from(7),
pallas::Base::from(8),
pallas::Base::from(9),
pallas::Base::from(10),
pallas::Base::from(11),
)
}
#[test]
fn create_vote_proof_signature_returns_result() {
let _: fn(Circuit, &Instance) -> Result<Vec<u8>, ProveError> = create_vote_proof;
}
#[test]
#[ignore = "long-running K=13 proof keygen; run when touching vote proof creation"]
fn create_vote_proof_returns_err_for_missing_witnesses() {
let instance = minimal_instance();
let err = create_vote_proof(Circuit::default(), &instance).unwrap_err();
assert!(matches!(err, ProveError::Halo2(plonk::Error::Synthesis)));
}
#[test]
fn public_input_count_matches_instance_layout() {
let instance = minimal_instance();
assert_eq!(
instance.to_halo2_instance().len(),
Instance::NUM_PUBLIC_INPUTS
);
assert_eq!(
serialize_instance(&instance).len(),
Instance::NUM_PUBLIC_INPUTS * 32
);
}
#[test]
#[ignore = "TODO(sean): runs K=13 keygen; run with `cargo test -- --ignored vk_fingerprint_unchanged`"]
fn vk_fingerprint_unchanged() {
let (_, _, vk) = vote_proof_cached_keys().expect("vote proof keys");
let pinned = format!("{:?}", vk.pinned());
let fingerprint = blake2b_simd::Params::new()
.hash_length(32)
.hash(pinned.as_bytes());
let actual: &[u8] = fingerprint.as_bytes();
let expected: [u8; 32] = [
0x72, 0xfc, 0x08, 0xf2, 0x18, 0xf9, 0xfb, 0x37, 0x20, 0x40, 0x0f, 0xd1, 0xf0, 0x30,
0x96, 0xe8, 0x5a, 0xd2, 0xef, 0x1e, 0xaa, 0x23, 0xb6, 0x40, 0x2c, 0x07, 0x98, 0xc6,
0x6d, 0x5d, 0xe9, 0x76,
];
assert_eq!(
actual,
expected.as_slice(),
"vote proof VK fingerprint changed; if intentional, update `expected` to:\n{:02x?}",
actual,
);
}
#[test]
#[ignore = "expensive end-to-end proof generation; run with --ignored when touching verification"]
fn typed_verify_accepts_proof_created_by_typed_builder() {
use crate::gadgets::elgamal::spend_auth_g_affine;
use crate::vote_proof::build_vote_proof_from_delegation;
use group::Curve;
use orchard::keys::SpendingKey;
let sk = SpendingKey::from_bytes([0x42; 32]).expect("valid test spending key");
let ea_pk = (spend_auth_g_affine() * pallas::Scalar::from(42u64)).to_affine();
let bundle = build_vote_proof_from_delegation(
&sk,
1,
12_500_000,
pallas::Base::from(0xDEAD_u64),
pallas::Base::from(0xCAFE_u64),
[pallas::Base::zero(); crate::params::VOTE_COMM_TREE_DEPTH],
0,
123,
1,
1,
ea_pk,
pallas::Scalar::from(7u64),
65535,
true,
)
.expect("vote proof builder should produce a valid proof");
verify_vote_proof(&bundle.proof, &bundle.instance)
.expect("typed verifier should accept the builder's proof and public inputs");
}
}