use crate::hash::keccak_packed;
use crate::sphincs_plus_c::verifier::SphincsPlusCVerifier;
use crate::verifier::VerifyOutcome;
pub use super::action_context::ActionContext;
pub use super::key::PublicKey;
pub use super::signature::Signature as StatefulSignature;
pub use super::uxmss::STATEFUL_PUBLIC_KEY_BYTES;
pub use crate::hash::suite::HASH_SUITE_KECCAK_256;
pub use crate::hash::{ADDRESS_TYPE_FORS_TREE, ADDRESS_TYPE_TREE, ADDRESS_TYPE_WOTS_HASH};
pub use crate::HASH_LEN;
pub use crate::sphincs_plus_c::Signature as StatelessSignature;
use super::dispatch as core_shrincs;
use super::dispatch::{stateful_action_message_hash, stateless_action_message_hash};
use super::key::Commitment;
use super::signature::{decode_stateful_envelope, decode_stateless_envelope};
#[cfg(test)]
use super::signature::{encode_stateful_envelope, encode_stateless_envelope};
pub use crate::profiles::{
FORS_TREE_HEIGHT, HASH_TRUNC_LEN, HYPERTREE_HEIGHT, NUM_FORS_TREES, NUM_HYPERTREE_LAYERS,
NUM_WOTS_CHAINS, PROFILE_NAME, STATELESS_SIGNATURE_LIMIT, WOTS_CHAIN_LEN,
};
#[derive(Debug, Clone, Copy)]
pub struct ShrincsVerifier;
impl Default for ShrincsVerifier {
fn default() -> Self {
Self::new()
}
}
impl ShrincsVerifier {
pub fn new() -> Self {
Self
}
pub fn verify_stateful(
&self,
expected_public_key_commitment: [u8; HASH_LEN],
public_key: &PublicKey,
context: &ActionContext,
signature: &StatefulSignature,
) -> bool {
core_shrincs::verify_stateful(
expected_public_key_commitment,
public_key,
context,
signature,
)
}
pub fn verify_stateless(
&self,
expected_public_key_commitment: [u8; HASH_LEN],
public_key: &PublicKey,
context: &ActionContext,
signature: &StatelessSignature,
) -> bool {
core_shrincs::verify_stateless(
expected_public_key_commitment,
public_key,
context,
signature,
)
}
#[cfg(test)]
pub(crate) fn verify_stateful_unsafe_raw(
&self,
expected_public_key_commitment: [u8; HASH_LEN],
public_key: &PublicKey,
message: &[u8],
signature: &StatefulSignature,
) -> bool {
core_shrincs::verify_stateful_unsafe_raw(
expected_public_key_commitment,
public_key,
message,
signature,
)
}
#[cfg(test)]
pub(crate) fn verify_stateless_unsafe_raw(
&self,
expected_public_key_commitment: [u8; HASH_LEN],
public_key: &PublicKey,
message: &[u8],
signature: &StatelessSignature,
) -> bool {
core_shrincs::verify_stateless_unsafe_raw(
expected_public_key_commitment,
public_key,
message,
signature,
)
}
pub fn stateful_action_message_hash(
&self,
expected_public_key_commitment: [u8; HASH_LEN],
context: &ActionContext,
) -> [u8; HASH_LEN] {
stateful_action_message_hash(expected_public_key_commitment, context)
}
pub fn stateless_action_message_hash(
&self,
expected_public_key_commitment: [u8; HASH_LEN],
context: &ActionContext,
) -> [u8; HASH_LEN] {
stateless_action_message_hash(expected_public_key_commitment, context)
}
pub fn public_key_commitment(
&self,
stateful_public_key: &[u8],
pk_seed: [u8; HASH_LEN],
hypertree_root: [u8; HASH_LEN],
) -> [u8; HASH_LEN] {
*Commitment::of(stateful_public_key, &pk_seed, &hypertree_root).as_bytes()
}
}
impl ShrincsVerifier {
pub fn version_tag() -> [u8; HASH_LEN] {
keccak_packed(&[b"quip.shrincs-verifier.v1"])
}
}
pub trait ShrincsVerifierExt {
fn verify_stateless_signature(
&self,
key: &[u8],
hash: &[u8; HASH_LEN],
stateless_envelope: &[u8],
) -> VerifyOutcome;
}
impl ShrincsVerifierExt for ShrincsVerifier {
fn verify_stateless_signature(
&self,
key: &[u8],
hash: &[u8; HASH_LEN],
stateless_envelope: &[u8],
) -> VerifyOutcome {
let Some(commitment) = Commitment::from_bytes(key).map(|c| *c.as_bytes()) else {
return VerifyOutcome::Invalid;
};
if decode_stateless_envelope(stateless_envelope).is_none() {
return VerifyOutcome::Malformed;
}
let Some((delegate_key, delegate_signature_envelope)) =
super::prepare_stateless_delegation(commitment, stateless_envelope)
else {
return VerifyOutcome::Invalid;
};
let Some(delegate_signature) =
crate::sphincs_plus_c::Signature::from_bytes(&delegate_signature_envelope)
else {
return VerifyOutcome::Malformed;
};
if SphincsPlusCVerifier::new().verify_signature(&delegate_key, hash, &delegate_signature) {
VerifyOutcome::Valid
} else {
VerifyOutcome::Invalid
}
}
}
impl crate::verifier::VerifierInterface for ShrincsVerifier {
fn verify(
&self,
key: &[u8],
hash: &[u8; HASH_LEN],
signature_envelope: &[u8],
) -> VerifyOutcome {
let Some(commitment) = Commitment::from_bytes(key).map(|c| *c.as_bytes()) else {
return VerifyOutcome::Invalid;
};
let Some((public_key, signature)) = decode_stateful_envelope(signature_envelope) else {
return VerifyOutcome::Malformed;
};
if super::verify_stateful_unsafe_raw(commitment, &public_key, hash, &signature) {
VerifyOutcome::Valid
} else {
VerifyOutcome::Invalid
}
}
}
#[cfg(test)]
use crate::verifier::VerifierInterface as _;
#[cfg(test)]
mod interface_tests {
use super::*;
use crate::shrincs::{PublicKey, ShrincsSigner, StatelessSignature};
fn keypair(seed: &[u8]) -> (crate::shrincs::Keys, PublicKey) {
ShrincsSigner::keygen(seed, 4).expect("keygen must succeed for a valid seed/budget")
}
fn commitment_of(public_key: &PublicKey) -> Vec<u8> {
public_key.public_key_commitment.clone()
}
#[test]
fn version_tag_matches_pinned_solidity_constant() {
const EXPECTED: [u8; HASH_LEN] = [
0x06, 0x4b, 0x5b, 0x1b, 0x1f, 0x5d, 0x6d, 0xc3, 0xd3, 0x8c, 0x8e, 0xd9, 0xf3, 0x8f,
0xd2, 0x4f, 0x68, 0x62, 0x83, 0x29, 0xf9, 0x32, 0x9a, 0x54, 0xb8, 0xe8, 0xc5, 0x3e,
0x3b, 0x06, 0xda, 0x58,
];
assert_eq!(ShrincsVerifier::version_tag(), EXPECTED);
}
#[test]
fn verify_accepts_a_valid_stateful_signature_over_the_raw_hash() {
let (mut signing_key, public_key) = keypair(b"verifier stateful accept seed");
let hash = [0x42u8; HASH_LEN];
let signature = ShrincsSigner::sign_stateful_raw(&mut signing_key, &hash)
.expect("signing must succeed for a fresh key");
let envelope = encode_stateful_envelope(&public_key, &signature);
let outcome = ShrincsVerifier::new().verify(&commitment_of(&public_key), &hash, &envelope);
assert_eq!(outcome, VerifyOutcome::Valid);
}
#[test]
fn verify_rejects_a_signature_over_a_different_hash() {
let (mut signing_key, public_key) = keypair(b"verifier stateful reject seed");
let hash = [0x11u8; HASH_LEN];
let signature = ShrincsSigner::sign_stateful_raw(&mut signing_key, &hash)
.expect("signing must succeed for a fresh key");
let envelope = encode_stateful_envelope(&public_key, &signature);
let wrong_hash = [0x22u8; HASH_LEN];
let outcome =
ShrincsVerifier::new().verify(&commitment_of(&public_key), &wrong_hash, &envelope);
assert_eq!(outcome, VerifyOutcome::Invalid);
}
#[test]
fn verify_rejects_a_wrong_length_key() {
let (mut signing_key, public_key) = keypair(b"verifier stateful wrong key seed");
let hash = [0x33u8; HASH_LEN];
let signature = ShrincsSigner::sign_stateful_raw(&mut signing_key, &hash)
.expect("signing must succeed for a fresh key");
let envelope = encode_stateful_envelope(&public_key, &signature);
let mut short_key = commitment_of(&public_key);
short_key.pop();
let outcome = ShrincsVerifier::new().verify(&short_key, &hash, &envelope);
assert_eq!(outcome, VerifyOutcome::Invalid);
}
#[test]
fn verify_reports_a_truncated_envelope_as_malformed() {
let (mut signing_key, public_key) = keypair(b"verifier stateful malformed seed");
let hash = [0x44u8; HASH_LEN];
let signature = ShrincsSigner::sign_stateful_raw(&mut signing_key, &hash)
.expect("signing must succeed for a fresh key");
let envelope = encode_stateful_envelope(&public_key, &signature);
let outcome = ShrincsVerifier::new().verify(
&commitment_of(&public_key),
&hash,
&envelope[..envelope.len() - 1],
);
assert_eq!(outcome, VerifyOutcome::Malformed);
}
#[test]
fn verify_reports_an_empty_envelope_as_malformed() {
let (_signing_key, public_key) = keypair(b"verifier stateful empty seed");
let hash = [0x55u8; HASH_LEN];
let outcome = ShrincsVerifier::new().verify(&commitment_of(&public_key), &hash, &[]);
assert_eq!(outcome, VerifyOutcome::Malformed);
}
#[test]
fn verify_reports_a_mismatched_commitment_as_invalid_not_malformed() {
let (mut signing_key, public_key) = keypair(b"verifier stateful wrong commitment seed");
let hash = [0x5du8; HASH_LEN];
let signature = ShrincsSigner::sign_stateful_raw(&mut signing_key, &hash)
.expect("signing must succeed for a fresh key");
let envelope = encode_stateful_envelope(&public_key, &signature);
let mut wrong_commitment = commitment_of(&public_key);
wrong_commitment[0] ^= 0x01;
let outcome = ShrincsVerifier::new().verify(&wrong_commitment, &hash, &envelope);
assert_eq!(outcome, VerifyOutcome::Invalid);
}
#[test]
fn verify_stateless_accepts_a_valid_stateless_signature_over_the_raw_hash() {
let (signing_key, public_key) = keypair(b"verifier stateless accept seed");
let hash = [0x66u8; HASH_LEN];
let signature: StatelessSignature = ShrincsSigner::sign_stateless_raw(&signing_key, &hash)
.expect("stateless signing must succeed for a fresh key");
let envelope = encode_stateless_envelope(&public_key, &signature);
let outcome = ShrincsVerifier::new().verify_stateless_signature(
&commitment_of(&public_key),
&hash,
&envelope,
);
assert_eq!(outcome, VerifyOutcome::Valid);
}
#[test]
fn verify_stateless_rejects_a_signature_over_a_different_hash() {
let (signing_key, public_key) = keypair(b"verifier stateless reject seed");
let hash = [0x77u8; HASH_LEN];
let signature = ShrincsSigner::sign_stateless_raw(&signing_key, &hash)
.expect("stateless signing must succeed for a fresh key");
let envelope = encode_stateless_envelope(&public_key, &signature);
let wrong_hash = [0x88u8; HASH_LEN];
let outcome = ShrincsVerifier::new().verify_stateless_signature(
&commitment_of(&public_key),
&wrong_hash,
&envelope,
);
assert_eq!(outcome, VerifyOutcome::Invalid);
}
#[test]
fn verify_stateless_rejects_a_wrong_length_key() {
let (signing_key, public_key) = keypair(b"verifier stateless wrong key seed");
let hash = [0x99u8; HASH_LEN];
let signature = ShrincsSigner::sign_stateless_raw(&signing_key, &hash)
.expect("stateless signing must succeed for a fresh key");
let envelope = encode_stateless_envelope(&public_key, &signature);
let mut short_key = commitment_of(&public_key);
short_key.pop();
let outcome =
ShrincsVerifier::new().verify_stateless_signature(&short_key, &hash, &envelope);
assert_eq!(outcome, VerifyOutcome::Invalid);
}
#[test]
fn verify_stateless_reports_a_mismatched_commitment_as_invalid_not_malformed() {
let (signing_key, public_key) = keypair(b"verifier stateless wrong commitment seed");
let hash = [0xaau8; HASH_LEN];
let signature = ShrincsSigner::sign_stateless_raw(&signing_key, &hash)
.expect("stateless signing must succeed for a fresh key");
let envelope = encode_stateless_envelope(&public_key, &signature);
let mut wrong_commitment = commitment_of(&public_key);
wrong_commitment[0] ^= 0x01;
let outcome =
ShrincsVerifier::new().verify_stateless_signature(&wrong_commitment, &hash, &envelope);
assert_eq!(outcome, VerifyOutcome::Invalid);
}
#[test]
fn verify_stateless_reports_a_truncated_envelope_as_malformed() {
let (signing_key, public_key) = keypair(b"verifier stateless malformed seed");
let hash = [0xbbu8; HASH_LEN];
let signature = ShrincsSigner::sign_stateless_raw(&signing_key, &hash)
.expect("stateless signing must succeed for a fresh key");
let envelope = encode_stateless_envelope(&public_key, &signature);
let outcome = ShrincsVerifier::new().verify_stateless_signature(
&commitment_of(&public_key),
&hash,
&envelope[..envelope.len() - 1],
);
assert_eq!(outcome, VerifyOutcome::Malformed);
}
#[test]
fn verify_stateless_reports_an_empty_envelope_as_malformed() {
let (_signing_key, public_key) = keypair(b"verifier stateless empty seed");
let hash = [0xccu8; HASH_LEN];
let outcome = ShrincsVerifier::new().verify_stateless_signature(
&commitment_of(&public_key),
&hash,
&[],
);
assert_eq!(outcome, VerifyOutcome::Malformed);
}
}