use alloc::vec::Vec;
use super::key::PublicKey;
use crate::abi::{encode_bytes32_array, encode_tuple, word_from_u32, AbiReader, Field};
use crate::sphincs_plus_c::Signature as StatelessSignature;
use crate::HASH_LEN;
const MAX_STATEFUL_AUTH_PATH_LEN: usize =
crate::shrincs::uxmss::MAX_STATEFUL_SIGNATURES_LIMIT as usize;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Signature {
pub randomizer: [u8; HASH_LEN],
pub counter: u32,
pub chains: Vec<[u8; HASH_LEN]>,
pub auth_path: Vec<[u8; HASH_LEN]>,
}
impl Signature {
pub(crate) fn encode_body(&self) -> Vec<u8> {
encode_tuple(alloc::vec![
Field::Static(self.randomizer),
Field::Static(word_from_u32(self.counter)),
Field::Dynamic(encode_bytes32_array(&self.chains)),
Field::Dynamic(encode_bytes32_array(&self.auth_path)),
])
}
pub fn to_bytes(&self) -> Vec<u8> {
encode_tuple(alloc::vec![Field::Dynamic(self.encode_body())])
}
pub(crate) fn decode(reader: &AbiReader, base: usize) -> Option<Self> {
Some(Self {
randomizer: reader.read_bytes32(base)?,
counter: reader.read_u32(base.checked_add(32)?)?,
chains: reader.decode_array_bytes32(
base,
base.checked_add(64)?,
crate::wots_c::NUM_CHAINS,
)?,
auth_path: reader.decode_array_bytes32(
base,
base.checked_add(96)?,
MAX_STATEFUL_AUTH_PATH_LEN,
)?,
})
}
pub fn from_bytes(data: &[u8]) -> Option<Self> {
let reader = AbiReader::new(data);
let signature_start = reader.decode_offset(0, 0)?;
let decoded = Self::decode(&reader, signature_start)?;
reader.finish()?;
Some(decoded)
}
}
impl TryFrom<&[u8]> for Signature {
type Error = ();
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
Self::from_bytes(value).ok_or(())
}
}
pub fn encode_stateful_envelope(public_key: &PublicKey, signature: &Signature) -> Vec<u8> {
encode_tuple(alloc::vec![
Field::Dynamic(public_key.encode_body()),
Field::Dynamic(signature.encode_body()),
])
}
pub fn decode_stateful_envelope(data: &[u8]) -> Option<(PublicKey, Signature)> {
let reader = AbiReader::new(data);
let public_key_start = reader.decode_offset(0, 0)?;
let signature_start = reader.decode_offset(0, 32)?;
let decoded = (
PublicKey::decode(&reader, public_key_start)?,
Signature::decode(&reader, signature_start)?,
);
reader.finish()?;
Some(decoded)
}
pub fn encode_stateless_envelope(
public_key: &PublicKey,
signature: &StatelessSignature,
) -> Vec<u8> {
encode_tuple(alloc::vec![
Field::Dynamic(public_key.encode_body()),
Field::Dynamic(signature.encode_body()),
])
}
pub fn decode_stateless_envelope(data: &[u8]) -> Option<(PublicKey, StatelessSignature)> {
let reader = AbiReader::new(data);
let public_key_start = reader.decode_offset(0, 0)?;
let signature_start = reader.decode_offset(0, 32)?;
let decoded = (
PublicKey::decode(&reader, public_key_start)?,
StatelessSignature::decode(&reader, signature_start)?,
);
reader.finish()?;
Some(decoded)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::profiles::NUM_HYPERTREE_LAYERS;
use crate::sphincs_plus_c::{ForsEntry as Entry, ForsSignature, LayerSignature};
use crate::wots_c::Signature as WotsCSignature;
use alloc::vec;
fn sample_public_key() -> PublicKey {
let mut stateful_public_key = vec![0u8; 68];
for (index, byte) in stateful_public_key.iter_mut().enumerate() {
*byte = index as u8;
}
PublicKey {
stateful_public_key,
public_key_commitment: vec![0xAA; 32],
pk_seed: vec![0xBB; 32],
hypertree_root: vec![0xCC; 32],
}
}
fn sample_stateful_signature() -> Signature {
Signature {
randomizer: [0x11; HASH_LEN],
counter: 0x0102_0304,
chains: vec![[0x22; HASH_LEN], [0x33; HASH_LEN], [0x44; HASH_LEN]],
auth_path: vec![[0x55; HASH_LEN], [0x66; HASH_LEN]],
}
}
fn sample_stateless_signature() -> StatelessSignature {
StatelessSignature {
fors: ForsSignature {
randomizer: [0x77; HASH_LEN],
counter: 7,
entries: vec![
Entry {
secret_leaf: [0x88; HASH_LEN],
auth_path: vec![[0x99; HASH_LEN], [0xA0; HASH_LEN]],
},
Entry {
secret_leaf: [0xA1; HASH_LEN],
auth_path: vec![[0xA2; HASH_LEN]],
},
],
},
hypertree: (0..NUM_HYPERTREE_LAYERS)
.map(|layer| LayerSignature {
wots_c_pk_hash: [0xB1 ^ layer; HASH_LEN],
wots_c_signature: WotsCSignature {
randomizer: [0xB2 ^ layer; HASH_LEN],
counter: 9 + layer as u32,
chains: vec![[0xB3 ^ layer; HASH_LEN], [0xB4 ^ layer; HASH_LEN]],
},
auth_path: vec![[0xB5 ^ layer; HASH_LEN]],
})
.collect(),
}
}
#[test]
fn to_bytes_from_bytes_round_trips() {
let signature = sample_stateful_signature();
let encoded = signature.to_bytes();
let decoded = Signature::from_bytes(&encoded).expect("valid encoding must decode");
assert_eq!(decoded, signature);
assert_eq!(decoded.to_bytes(), encoded);
}
#[test]
fn from_bytes_rejects_trailing_bytes() {
let mut encoded = sample_stateful_signature().to_bytes();
encoded.extend_from_slice(&[0xAA, 0xBB]);
assert!(
Signature::from_bytes(&encoded).is_none(),
"trailing junk on the signature envelope must be rejected"
);
}
#[test]
fn try_from_delegates_to_from_bytes() {
let signature = sample_stateful_signature();
let encoded = signature.to_bytes();
let decoded = Signature::try_from(encoded.as_slice()).expect("valid encoding must decode");
assert_eq!(decoded, signature);
let truncated = &encoded[..encoded.len() - 1];
assert!(Signature::try_from(truncated).is_err());
}
#[test]
fn stateful_envelope_round_trips() {
let public_key = sample_public_key();
let signature = sample_stateful_signature();
let encoded = encode_stateful_envelope(&public_key, &signature);
let (decoded_key, decoded_sig) =
decode_stateful_envelope(&encoded).expect("valid envelope must decode");
assert_eq!(decoded_key, public_key);
assert_eq!(decoded_sig, signature);
assert_eq!(
encode_stateful_envelope(&decoded_key, &decoded_sig),
encoded
);
}
#[test]
fn stateless_envelope_round_trips() {
let public_key = sample_public_key();
let signature = sample_stateless_signature();
let encoded = encode_stateless_envelope(&public_key, &signature);
let (decoded_key, decoded_sig) =
decode_stateless_envelope(&encoded).expect("valid envelope must decode");
assert_eq!(decoded_key, public_key);
assert_eq!(decoded_sig, signature);
assert_eq!(
encode_stateless_envelope(&decoded_key, &decoded_sig),
encoded
);
}
#[test]
fn prepare_stateless_delegation_extracts_pinned_sibling_shapes() {
let mut public_key = sample_public_key();
let commitment = *crate::shrincs::key::Commitment::of(
&public_key.stateful_public_key,
&public_key.pk_seed.clone().try_into().unwrap(),
&public_key.hypertree_root.clone().try_into().unwrap(),
)
.as_bytes();
public_key.public_key_commitment = commitment.to_vec();
let signature = sample_stateless_signature();
let envelope = encode_stateless_envelope(&public_key, &signature);
let (delegate_key, delegate_signature) =
crate::shrincs::prepare_stateless_delegation(commitment, &envelope)
.expect("matching commitment must delegate");
let mut expected_key = [0u8; 64];
expected_key[..32].copy_from_slice(&public_key.pk_seed);
expected_key[32..].copy_from_slice(&public_key.hypertree_root);
assert_eq!(delegate_key, expected_key);
assert_eq!(delegate_signature, signature.to_bytes());
let mut wrong_commitment = commitment;
wrong_commitment[0] ^= 0x01;
assert!(
crate::shrincs::prepare_stateless_delegation(wrong_commitment, &envelope).is_none()
);
}
#[test]
fn truncated_stateful_envelope_is_rejected() {
let encoded = encode_stateful_envelope(&sample_public_key(), &sample_stateful_signature());
for cut in [0usize, 1, 32, 63, encoded.len() - 1] {
assert!(
decode_stateful_envelope(&encoded[..cut]).is_none(),
"truncation at {cut} must be rejected"
);
}
}
#[test]
fn stateful_envelope_with_out_of_bounds_offset_is_rejected() {
let mut encoded =
encode_stateful_envelope(&sample_public_key(), &sample_stateful_signature());
for byte in &mut encoded[24..32] {
*byte = 0xFF;
}
assert!(decode_stateful_envelope(&encoded).is_none());
}
#[test]
fn stateful_envelope_with_dirty_offset_high_bits_is_rejected() {
let mut encoded =
encode_stateful_envelope(&sample_public_key(), &sample_stateful_signature());
encoded[0] = 0x01;
assert!(decode_stateful_envelope(&encoded).is_none());
}
#[test]
fn stateful_signature_with_dirty_counter_high_bits_is_rejected() {
let public_key = sample_public_key();
let signature = sample_stateful_signature();
let mut encoded = encode_stateful_envelope(&public_key, &signature);
let signature_offset = 32usize;
let signature_start = usize::try_from(u32::from_be_bytes(
encoded[signature_offset + 28..signature_offset + 32]
.try_into()
.unwrap(),
))
.unwrap();
encoded[signature_start + 32] = 0x01;
assert!(decode_stateful_envelope(&encoded).is_none());
}
#[test]
fn stateful_envelope_with_dirty_bytes_padding_is_rejected() {
let public_key = sample_public_key();
let signature = sample_stateful_signature();
let mut encoded = encode_stateful_envelope(&public_key, &signature);
let last = encoded.len();
let public_key_body_start = 64usize;
let field_data_start = public_key_body_start + 128 + 32;
let pad_byte_pos = field_data_start + 68 + 27; assert!(pad_byte_pos < last);
encoded[pad_byte_pos] = 0x01;
assert!(decode_stateful_envelope(&encoded).is_none());
}
fn read_abi_usize(buf: &[u8], pos: usize) -> usize {
usize::try_from(u64::from_be_bytes(
buf[pos + 24..pos + 32].try_into().unwrap(),
))
.unwrap()
}
fn write_abi_usize(buf: &mut [u8], pos: usize, value: usize) {
buf[pos..pos + 24].fill(0);
buf[pos + 24..pos + 32].copy_from_slice(&(value as u64).to_be_bytes());
}
#[test]
fn trailing_bytes_are_rejected() {
let mut encoded =
encode_stateful_envelope(&sample_public_key(), &sample_stateful_signature());
encoded.push(0x00);
assert!(
decode_stateful_envelope(&encoded).is_none(),
"single trailing byte must be rejected"
);
}
#[test]
fn oversized_array_length_is_rejected() {
let public_key = sample_public_key();
let signature = sample_stateful_signature();
let mut encoded = encode_stateful_envelope(&public_key, &signature);
let signature_start = read_abi_usize(&encoded, 32);
let chains_start = signature_start + read_abi_usize(&encoded, signature_start + 64);
write_abi_usize(&mut encoded, chains_start, crate::wots_c::NUM_CHAINS + 1);
assert!(
decode_stateful_envelope(&encoded).is_none(),
"WOTS-C chain count + 1 must be rejected"
);
}
}