use crate::sphincs_plus_c::{self, PublicKey, Signature};
use crate::HASH_LEN;
#[derive(Debug, Clone, Copy)]
pub struct SphincsPlusCVerifier;
impl Default for SphincsPlusCVerifier {
fn default() -> Self {
Self::new()
}
}
impl SphincsPlusCVerifier {
pub fn new() -> Self {
Self
}
pub fn version_tag() -> [u8; HASH_LEN] {
crate::hash::keccak_packed(&[b"quip.sphincsplusc-verifier.v1"])
}
pub fn verify_signature(
&self,
key: &[u8],
hash: &[u8; HASH_LEN],
signature: &Signature,
) -> bool {
if key.len() != 64 {
return false;
}
let Some(pk) = PublicKey::from_slices(&key[..32], &key[32..64]) else {
return false;
};
sphincs_plus_c::verify_hash(&pk, hash, signature)
}
pub fn verify_with_pk(
&self,
pk: &PublicKey,
hash: &[u8; HASH_LEN],
signature: &Signature,
) -> bool {
sphincs_plus_c::verify_hash(pk, hash, signature)
}
pub fn verify_message(&self, pk: &PublicKey, message: &[u8], signature: &Signature) -> bool {
sphincs_plus_c::verify(pk, message, signature)
}
}
impl crate::verifier::VerifierInterface for SphincsPlusCVerifier {
fn verify(
&self,
key: &[u8],
hash: &[u8; 32],
signature: &[u8],
) -> crate::verifier::VerifyOutcome {
use crate::verifier::VerifyOutcome;
if key.len() != 64 {
return VerifyOutcome::Invalid;
}
let Some(decoded) = Signature::from_bytes(signature) else {
return VerifyOutcome::Malformed;
};
let mut hash32 = [0u8; 32];
hash32.copy_from_slice(hash);
if self.verify_signature(key, &hash32, &decoded) {
VerifyOutcome::Valid
} else {
VerifyOutcome::Invalid
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(not(any(feature = "profile-128s-q18", feature = "profile-128s-q20")))]
use crate::verifier::{VerifierInterface, VerifyOutcome};
#[test]
fn version_tag_matches_pinned_solidity_constant() {
const EXPECTED: [u8; HASH_LEN] = [
0xb3, 0xee, 0x3b, 0x4a, 0x95, 0x9f, 0xcc, 0xaf, 0x76, 0xdc, 0xbb, 0x8f, 0x88, 0x7c,
0x05, 0xff, 0xe4, 0xbd, 0x73, 0xd8, 0x80, 0x32, 0xd7, 0xe2, 0xe5, 0xfd, 0xc8, 0x3a,
0x67, 0x17, 0x29, 0xa8,
];
assert_eq!(SphincsPlusCVerifier::version_tag(), EXPECTED);
}
#[cfg(not(any(feature = "profile-128s-q18", feature = "profile-128s-q20")))]
fn signed_stateless_envelope(seed_label: &[u8], hash: [u8; HASH_LEN]) -> ([u8; 64], Vec<u8>) {
use crate::hash::hash_packed;
use crate::sphincs_plus_c;
let sk_seed = hash_packed(&[b"sphincs-plus-c-verifier-sk", seed_label]);
let prf_seed = hash_packed(&[b"sphincs-plus-c-verifier-prf", seed_label]);
let pk_seed = hash_packed(&[b"sphincs-plus-c-verifier-pk", seed_label]);
let sk = sphincs_plus_c::keygen(sk_seed, prf_seed, pk_seed);
let signature = sphincs_plus_c::sign(&sk, &hash).expect("stateless sign");
let envelope = signature.to_bytes();
let key = key64(&sk.public_key);
(key, envelope)
}
#[cfg(not(any(feature = "profile-128s-q18", feature = "profile-128s-q20")))]
fn key64(pk: &crate::sphincs_plus_c::PublicKey) -> [u8; 64] {
let mut key = [0u8; 64];
key[..32].copy_from_slice(pk.pk_seed.as_bytes());
key[32..].copy_from_slice(pk.root.as_bytes());
key
}
#[cfg(not(any(feature = "profile-128s-q18", feature = "profile-128s-q20")))]
#[test]
fn verify_accepts_valid_64_byte_key_and_stateless_envelope() {
let hash = [0x42u8; HASH_LEN];
let (key, envelope) = signed_stateless_envelope(b"verify-envelope valid", hash);
let outcome = SphincsPlusCVerifier::new().verify(&key, &hash, &envelope);
assert_eq!(outcome, VerifyOutcome::Valid);
}
#[cfg(not(any(feature = "profile-128s-q18", feature = "profile-128s-q20")))]
#[test]
fn verify_rejects_wrong_length_key() {
let hash = [0x43u8; HASH_LEN];
let (key, envelope) = signed_stateless_envelope(b"verify-envelope wrong key", hash);
let short_key = &key[..63];
let outcome = SphincsPlusCVerifier::new().verify(short_key, &hash, &envelope);
assert_eq!(outcome, VerifyOutcome::Invalid);
let long_key = [&key[..], &[0u8]].concat();
let outcome = SphincsPlusCVerifier::new().verify(&long_key, &hash, &envelope);
assert_eq!(outcome, VerifyOutcome::Invalid);
}
#[cfg(not(any(feature = "profile-128s-q18", feature = "profile-128s-q20")))]
#[test]
fn verify_reports_malformed_envelope() {
let hash = [0x44u8; HASH_LEN];
let (key, envelope) = signed_stateless_envelope(b"verify-envelope malformed", hash);
let outcome = SphincsPlusCVerifier::new().verify(
&key,
&hash,
&envelope[..envelope.len().saturating_sub(1)],
);
assert_eq!(outcome, VerifyOutcome::Malformed);
let outcome = SphincsPlusCVerifier::new().verify(&key, &hash, &[]);
assert_eq!(outcome, VerifyOutcome::Malformed);
}
}