use spongefish::domain_separator;
use crate::alpha::BatchedAlpha;
use crate::field::{Goldilocks4, psi};
use crate::keygen::derive_ood_point;
use crate::params::Params;
use crate::positions::derive_positions;
use crate::sign::Signature;
use crate::transcript::{derive_betas, prefix_bytes};
use crate::whir::ConcreteWhirVerifier;
use crate::{Error, PublicKey};
pub fn verify(pk: &PublicKey, params: Params, msg: &[u8], sig: &Signature) -> Result<(), Error> {
if pk.n_star != params.n_star {
return Err(Error::ParamsMismatch);
}
let k = Params::K as usize;
if sig.y_values.len() != k {
return Err(Error::InvalidLength);
}
let positions = derive_positions(&pk.root, msg, k, params.t());
let mut xs: Vec<Goldilocks4> = positions
.iter()
.map(|&i| psi(i as u64, params.t() as u64))
.collect();
let z = derive_ood_point(&pk.root);
xs.push(z);
let betas = derive_betas(&pk.root, msg, &sig.y_values);
if betas.len() != xs.len() {
return Err(Error::VerificationFailed);
}
let mut v: Goldilocks4 = betas
.iter()
.zip(sig.y_values.iter())
.map(|(b, y)| *b * *y)
.sum();
v += betas[k] * pk.w;
let prefix = prefix_bytes(params, &pk.root, &pk.w, msg, &sig.y_values);
let domain = domain_separator!("jevil-v1")
.without_session()
.instance(&prefix);
let mut transcript = domain.std_verifier(&sig.whir_proof);
let alpha = BatchedAlpha::new(&xs, betas, params.nu());
let hvzk_budget = params.n() - params.m();
let whir = ConcreteWhirVerifier::build(params.m(), hvzk_budget, 64, 64);
whir.verify(&mut transcript, pk.root, alpha, v)
.map_err(|_| Error::VerificationFailed)?;
transcript
.check_eof()
.map_err(|_| Error::VerificationFailed)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::keygen::keygen;
use crate::sign::sign;
use rand::SeedableRng;
use rand_chacha::ChaCha20Rng;
#[test]
fn honest_signature_verifies() {
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!(verify(&pk, params, b"hello", &sig).is_ok());
}
#[test]
fn tampered_y_value_rejected() {
let params = Params::new(1);
let mut rng = ChaCha20Rng::seed_from_u64(1);
let (pk, sk, cache) = keygen(&mut rng, params);
let mut sig = sign(&sk, &pk, &cache, params, b"hi");
sig.y_values[0] += Goldilocks4::ONE;
assert_eq!(
verify(&pk, params, b"hi", &sig),
Err(Error::VerificationFailed)
);
}
#[test]
fn wrong_message_rejected() {
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"a");
assert!(verify(&pk, params, b"b", &sig).is_err());
}
#[test]
fn forged_root_attack_now_rejected() {
use crate::SecretKey;
use crate::keygen::{
SignerCache, build_whir_protocol, derive_coefficient_vector, derive_ood_point, horner,
};
let params = Params::new(1);
let mut rng_target = ChaCha20Rng::seed_from_u64(100);
let (pk_target, _sk_target, _cache_target) = keygen(&mut rng_target, params);
let z = derive_ood_point(&pk_target.root);
let w_required = pk_target.w;
let sigma_atk = [0xAAu8; 32];
let mut c_atk = derive_coefficient_vector(&sigma_atk, params);
let cur = horner(&c_atk, z);
c_atk[0] += w_required - cur;
let whir = build_whir_protocol(params);
let (_root_atk, whir_state_atk) = whir.commit(&c_atk, &sigma_atk);
let cache_atk = SignerCache {
c: c_atk,
whir_state: whir_state_atk,
};
let sk_fake = SecretKey::from_bytes([0u8; 32]);
let sig = sign(&sk_fake, &pk_target, &cache_atk, params, b"forgery");
let result = verify(&pk_target, params, b"forgery", &sig);
assert_eq!(result, Err(Error::VerificationFailed));
}
}