use spongefish::domain_separator;
use crate::field::{Goldilocks4, psi};
use crate::keygen::{SignerCache, build_whir_protocol, derive_ood_point, horner};
use crate::params::Params;
use crate::positions::derive_positions;
use crate::transcript::{derive_betas, prefix_bytes};
use crate::{Error, PublicKey, SecretKey};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Signature {
pub y_values: Vec<Goldilocks4>,
pub whir_proof: Vec<u8>,
}
impl Signature {
pub fn to_bytes(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(self.y_values.len() * 32 + self.whir_proof.len());
for y in &self.y_values {
out.extend_from_slice(&y.to_bytes());
}
out.extend_from_slice(&self.whir_proof);
out
}
pub fn from_bytes(b: &[u8]) -> Result<Self, Error> {
let k = Params::K as usize;
let head_len = k * 32;
if b.len() < head_len {
return Err(Error::InvalidLength);
}
let mut y_values = Vec::with_capacity(k);
for chunk in b[..head_len].chunks_exact(32) {
let g = Goldilocks4::from_bytes(chunk).ok_or(Error::NonCanonicalField)?;
y_values.push(g);
}
let whir_proof = b[head_len..].to_vec();
Ok(Self {
y_values,
whir_proof,
})
}
}
pub fn sign(
sk: &SecretKey,
pk: &PublicKey,
cache: &SignerCache,
params: Params,
msg: &[u8],
) -> Signature {
let k = Params::K as usize;
let t = params.t();
let m = params.m();
let positions = derive_positions(&pk.root, msg, k, t);
let xs_msg: Vec<Goldilocks4> = positions.iter().map(|&i| psi(i as u64, t as u64)).collect();
let ys: Vec<Goldilocks4> = xs_msg.iter().map(|&x| horner(&cache.c, x)).collect();
let z = derive_ood_point(&pk.root);
let mut xs = xs_msg;
xs.push(z);
let betas = derive_betas(&pk.root, msg, &ys);
debug_assert_eq!(betas.len(), xs.len());
let mut alpha = vec![Goldilocks4::ZERO; m];
let mut x_powers = vec![Goldilocks4::ONE; xs.len()];
for slot in alpha.iter_mut() {
let mut sum = Goldilocks4::ZERO;
for t in 0..xs.len() {
sum += betas[t] * x_powers[t];
}
*slot = sum;
for t in 0..xs.len() {
x_powers[t] *= xs[t];
}
}
let prefix = prefix_bytes(params, &pk.root, &pk.w, msg, &ys);
let domain = domain_separator!("jevil-v1")
.without_session()
.instance(&prefix);
let mut transcript = domain.std_prover();
let mask_seed = derive_prover_randomness_seed(sk, &pk.root, msg, &ys);
let whir = build_whir_protocol(params);
whir.prove(&mut transcript, &cache.whir_state, alpha, &mask_seed);
Signature {
y_values: ys,
whir_proof: transcript.narg_string().to_vec(),
}
}
fn derive_prover_randomness_seed(
sk: &SecretKey,
root: &[u8; 32],
msg: &[u8],
ys: &[Goldilocks4],
) -> [u8; 32] {
use crate::hash::{JV_OPRD, hash};
let y_bytes: Vec<[u8; 32]> = ys.iter().map(|y| y.to_bytes()).collect();
let mut inputs: Vec<&[u8]> = Vec::with_capacity(3 + ys.len());
inputs.push(sk.seed());
inputs.push(root);
inputs.push(msg);
for yb in &y_bytes {
inputs.push(yb);
}
let h = hash(JV_OPRD, &inputs, 32);
let mut out = [0u8; 32];
out.copy_from_slice(&h);
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::keygen::keygen;
use rand::SeedableRng;
use rand_chacha::ChaCha20Rng;
#[test]
fn signature_has_k_y_values_and_nonempty_proof() {
let params = Params::new(1);
let mut rng = ChaCha20Rng::seed_from_u64(0);
let (pk, sk, cache) = keygen(&mut rng, params);
let sig = sign(&sk, &pk, &cache, params, b"hello");
assert_eq!(sig.y_values.len(), Params::K as usize);
assert!(!sig.whir_proof.is_empty());
}
#[test]
fn signature_serde_round_trip() {
let params = Params::new(1);
let mut rng = ChaCha20Rng::seed_from_u64(2);
let (pk, sk, cache) = keygen(&mut rng, params);
let sig = sign(&sk, &pk, &cache, params, b"round");
let bytes = sig.to_bytes();
let parsed = Signature::from_bytes(&bytes).unwrap();
assert_eq!(parsed.y_values, sig.y_values);
assert_eq!(parsed.whir_proof, sig.whir_proof);
}
}