use crate::HASH_LEN;
pub fn to_message(hash: &[u8; HASH_LEN]) -> [u8; HASH_LEN] {
*hash
}
pub fn verify(pk: &key::PublicKey, message: &[u8], sig: &Signature) -> bool {
verify_raw(pk.pk_seed.as_bytes(), pk.root.as_bytes(), message, sig)
}
pub fn verify_hash(pk: &key::PublicKey, hash: &[u8; HASH_LEN], sig: &Signature) -> bool {
verify(pk, &to_message(hash), sig)
}
pub(crate) fn verify_raw(
pk_seed: &[u8; HASH_LEN],
hypertree_root: &[u8; HASH_LEN],
message: &[u8],
signature: &Signature,
) -> bool {
if signature.hypertree.is_empty() {
return false;
}
let Some((fors_root, seed_tree_index, seed_leaf_index)) =
fors_c::verify_fors_c_and_return_root(pk_seed, hypertree_root, message, &signature.fors)
else {
return false;
};
hypertree::verify_hypertree(
pk_seed,
hypertree_root,
fors_root,
hypertree::HypertreeSeed {
tree_index: seed_tree_index,
leaf_index: seed_leaf_index,
},
&signature.hypertree,
)
}
pub(crate) mod fors_c;
pub use fors_c::{Entry as ForsEntry, Signature as ForsSignature};
pub(crate) mod hypertree;
pub use hypertree::LayerSignature;
pub mod key;
pub use key::{Key, PkSeed, PrfSeed, PrivateKey, PublicKey, Root, SkSeed};
pub mod signature;
pub use signature::{encode_public_key, Signature};
pub mod verifier;
pub use verifier::SphincsPlusCVerifier;
pub fn sign(signing_key: &key::Key, message: &[u8]) -> Option<Signature> {
let signed_fors = fors_c::sign_fors_c(signing_key, message)?;
let hypertree_layers = hypertree::sign_hypertree(
signing_key,
signed_fors.root,
signed_fors.tree_index,
signed_fors.leaf_index,
)?;
Some(Signature {
fors: signed_fors.signature,
hypertree: hypertree_layers,
})
}
pub fn sign_hash(signing_key: &key::Key, hash: &[u8; HASH_LEN]) -> Option<Signature> {
sign(signing_key, &to_message(hash))
}
pub fn keygen(
sk_seed: [u8; HASH_LEN],
prf_seed: [u8; HASH_LEN],
pk_seed: [u8; HASH_LEN],
) -> key::Key {
let hypertree_root = hypertree::hypertree_public_root(&sk_seed, &pk_seed);
key::Key::new(
key::PrivateKey::new(key::SkSeed::new(sk_seed), key::PrfSeed::new(prf_seed)),
key::PublicKey {
pk_seed: key::PkSeed::new(pk_seed),
root: key::Root::new(hypertree_root),
},
)
}
pub(crate) fn keygen_from_master_seed(seed: &[u8]) -> key::Key {
let sk = crate::hash::derive32(b"shrincs-stateless-sk-seed", seed, &[]);
let prf = crate::hash::derive32(b"shrincs-stateless-prf-seed", seed, &[]);
let pk = crate::hash::derive32(b"shrincs-pk-seed", seed, &[]);
keygen(sk, prf, pk)
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(not(any(feature = "profile-128s-q18", feature = "profile-128s-q20")))]
use crate::hash::{derive32, hash_packed};
#[cfg(not(any(feature = "profile-128s-q18", feature = "profile-128s-q20")))]
fn independent_keygen(seed: &[u8]) -> (Key, PublicKey) {
let key = keygen(
derive32(b"shrincs-stateless-sk-seed", seed, &[]),
derive32(b"shrincs-stateless-prf-seed", seed, &[]),
derive32(b"shrincs-pk-seed", seed, &[]),
);
let public_key = key.public_key;
(key, public_key)
}
#[cfg(not(any(feature = "profile-128s-q18", feature = "profile-128s-q20")))]
#[test]
fn sphincs_plus_c_sign_verify_round_trip() {
let (sk, pk) = independent_keygen(b"sphincs-plus-c independent rt");
let message = hash_packed(&[b"sphincs-plus-c-rt-message"]);
let sig = sign(&sk, &message).expect("sign");
assert!(verify(&pk, &message, &sig));
assert!(verify_hash(&pk, &message, &sig));
let mut key = [0u8; 64];
key[..32].copy_from_slice(pk.pk_seed.as_bytes());
key[32..].copy_from_slice(pk.root.as_bytes());
assert!(crate::sphincs_plus_c::verifier::SphincsPlusCVerifier::new()
.verify_signature(&key, &message, &sig,));
}
#[test]
fn to_message_is_identity() {
let h = [0xabu8; 32];
assert_eq!(to_message(&h), h);
}
#[cfg(not(any(feature = "profile-128s-q18", feature = "profile-128s-q20")))]
#[test]
fn sign_hash_round_trips_through_the_verifier_interface() {
use crate::verifier::{VerifierInterface, VerifyOutcome};
let (sk, pk) = independent_keygen(b"sphincs sign_hash round trip");
let hash = hash_packed(&[b"sphincs-plus-c sign_hash rt"]);
let signature = sign_hash(&sk, &hash).expect("sign_hash");
let mut key = [0u8; 64];
key[..32].copy_from_slice(pk.pk_seed.as_bytes());
key[32..].copy_from_slice(pk.root.as_bytes());
assert_eq!(
SphincsPlusCVerifier::new().verify(&key, &hash, &signature.to_bytes()),
VerifyOutcome::Valid
);
}
#[cfg(all(
feature = "std",
not(feature = "parallel"),
not(any(feature = "profile-128s-q18", feature = "profile-128s-q20"))
))]
#[test]
fn stateless_verify_hash_count_matches_model_and_reports_cu_floor() {
use crate::hash::backend::metrics;
use crate::profiles::{
FORS_TREE_HEIGHT, HYPERTREE_HEIGHT, NUM_FORS_TREES, NUM_HYPERTREE_LAYERS,
NUM_WOTS_CHAINS, WOTS_CHAIN_LEN,
};
let (sk, pk) = independent_keygen(b"sphincs-plus-c cu estimator");
let message = hash_packed(&[b"sphincs-plus-c-cu-message"]);
let sig = sign(&sk, &message).expect("sign");
metrics::reset();
assert!(verify(&pk, &message, &sig));
let (calls, bytes, slice_cost) = metrics::snapshot();
let signed_trees = u64::from(NUM_FORS_TREES) - 1;
let digest_bytes = (u64::from(NUM_FORS_TREES) * u64::from(FORS_TREE_HEIGHT)
+ u64::from(HYPERTREE_HEIGHT))
.div_ceil(8);
let fors_digest_blocks = if digest_bytes <= 32 {
1
} else {
digest_bytes.div_ceil(32)
};
let fors_calls = fors_digest_blocks + signed_trees * (1 + u64::from(FORS_TREE_HEIGHT)) + 1;
let subtree_height = u64::from(HYPERTREE_HEIGHT / NUM_HYPERTREE_LAYERS);
let chain_steps = u64::from(NUM_WOTS_CHAINS) * u64::from(WOTS_CHAIN_LEN - 1)
- u64::from(crate::wots_c::TARGET_SUM);
let per_layer = 1 + chain_steps + 1 + subtree_height;
let expected_calls = fors_calls + u64::from(NUM_HYPERTREE_LAYERS) * per_layer;
assert_eq!(calls, expected_calls, "verify hash-count model drifted");
let cu_floor = metrics::estimated_syscall_cu(calls, slice_cost);
hashsigs_println!(
"CU estimate profile={}: stateless verify = {calls} hash syscalls, \
{bytes} bytes hashed, syscall floor ≈ {cu_floor} CU \
(excludes SBF instruction execution and borsh deserialization)",
crate::profiles::PROFILE_NAME
);
}
}