use rand::{CryptoRng, Rng};
use crate::field::Goldilocks4;
use crate::hash::{JV_OOD, JV_SEED};
use crate::params::Params;
use crate::whir::{ConcreteWhirProtocol, WhirSignerState};
use crate::{PublicKey, SecretKey};
pub struct SignerCache {
pub(crate) c: Vec<Goldilocks4>,
pub(crate) whir_state: WhirSignerState,
}
impl Drop for SignerCache {
fn drop(&mut self) {
use zeroize::Zeroize;
self.c.zeroize();
self.whir_state.internal.zeroize();
if let Some(state) = self.whir_state.initial_state.as_mut() {
for g in state.msg.iter_mut() {
g.zeroize();
}
}
}
}
impl SignerCache {
pub fn from_secret(sk: &SecretKey, params: Params) -> Self {
let c = derive_coefficient_vector(sk.seed(), params);
let whir = build_whir_protocol(params);
let (_root, state) = whir.commit(&c, sk.seed());
Self {
c,
whir_state: state,
}
}
}
pub fn keygen<R: Rng + CryptoRng>(
rng: &mut R,
params: Params,
) -> (PublicKey, SecretKey, SignerCache) {
let mut sigma = [0u8; SecretKey::BYTES];
rng.fill_bytes(&mut sigma);
let c = derive_coefficient_vector(&sigma, params);
let whir = build_whir_protocol(params);
let (root, whir_state) = whir.commit(&c, &sigma);
let z = derive_ood_point(&root);
let w = horner(&c, z);
let pk = PublicKey {
root,
w,
n_star: params.n_star,
};
let sk = SecretKey::from_bytes(sigma);
let cache = SignerCache { c, whir_state };
(pk, sk, cache)
}
pub(crate) fn derive_ood_point(root: &[u8; 32]) -> Goldilocks4 {
derive_field_elements(root, JV_OOD, 1)[0]
}
pub(crate) fn horner(coeffs: &[Goldilocks4], x: Goldilocks4) -> Goldilocks4 {
let mut acc = Goldilocks4::ZERO;
for c in coeffs.iter().rev() {
acc = acc * x + *c;
}
acc
}
pub(crate) fn derive_coefficient_vector(
sigma: &[u8; SecretKey::BYTES],
params: Params,
) -> Vec<Goldilocks4> {
derive_field_elements(sigma, JV_SEED, params.m())
}
pub(crate) fn build_whir_protocol(params: Params) -> ConcreteWhirProtocol {
let hvzk_budget = params.n() - params.m();
ConcreteWhirProtocol::build(params.m(), hvzk_budget, 64, 64)
}
fn derive_field_elements(input: &[u8; 32], tag: [u8; 8], count: usize) -> Vec<Goldilocks4> {
crate::hash::shake_field_elements(tag, &[input], count)
}
#[cfg(test)]
mod tests {
use super::*;
use rand::SeedableRng;
use rand_chacha::ChaCha20Rng;
#[test]
fn derive_coefficient_vector_is_deterministic() {
let params = Params::new(1);
let sigma = [42u8; 32];
let a = derive_coefficient_vector(&sigma, params);
let b = derive_coefficient_vector(&sigma, params);
assert_eq!(a, b);
assert_eq!(a.len(), params.m());
}
#[test]
fn keygen_is_deterministic_under_seeded_rng() {
let params = Params::new(1);
let mut a = ChaCha20Rng::seed_from_u64(0);
let mut b = ChaCha20Rng::seed_from_u64(0);
let (pk_a, _, _) = keygen(&mut a, params);
let (pk_b, _, _) = keygen(&mut b, params);
assert_eq!(pk_a.root, pk_b.root);
assert_eq!(pk_a.w, pk_b.w);
assert_eq!(pk_a.n_star, pk_b.n_star);
}
#[test]
fn w_matches_horner_on_z() {
let params = Params::new(1);
let mut rng = ChaCha20Rng::seed_from_u64(11);
let (pk, _sk, cache) = keygen(&mut rng, params);
let z = derive_ood_point(&pk.root);
assert_eq!(pk.w, horner(&cache.c, z));
}
#[test]
fn signer_cache_from_secret_matches_keygen() {
let params = Params::new(1);
let mut rng = ChaCha20Rng::seed_from_u64(1);
let (pk, sk, cache) = keygen(&mut rng, params);
let cache2 = SignerCache::from_secret(&sk, params);
assert_eq!(cache.c, cache2.c);
assert_eq!(cache.whir_state.internal, cache2.whir_state.internal);
let whir = build_whir_protocol(params);
let (rebuilt_root, _) = whir.commit(&cache2.c, sk.seed());
assert_eq!(pk.root, rebuilt_root);
}
}