use crate::error::{QrllibError, Result};
use crate::xmss::{XMSS_PUBLIC_KEY_SIZE, Xmss, XmssHashFunction, XmssHeight, verify_xmss};
const OID_LEN: usize = 4;
pub const EXPANDED_SEED_SIZE: usize = 96;
pub const RFC_PUBLIC_KEY_SIZE: usize = OID_LEN + XMSS_PUBLIC_KEY_SIZE;
#[allow(non_camel_case_types)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u32)]
pub enum ParameterSet {
XmssSha2_10_256 = 0x00000001,
XmssSha2_16_256 = 0x00000002,
XmssSha2_20_256 = 0x00000003,
XmssShake_10_256 = 0x00000007,
XmssShake_16_256 = 0x00000008,
XmssShake_20_256 = 0x00000009,
}
impl ParameterSet {
pub fn from_oid(oid: u32) -> Result<Self> {
match oid {
0x00000001 => Ok(Self::XmssSha2_10_256),
0x00000002 => Ok(Self::XmssSha2_16_256),
0x00000003 => Ok(Self::XmssSha2_20_256),
0x00000007 => Ok(Self::XmssShake_10_256),
0x00000008 => Ok(Self::XmssShake_16_256),
0x00000009 => Ok(Self::XmssShake_20_256),
_ => Err(QrllibError::UnsupportedXmssParameterSet(oid)),
}
}
pub const fn oid(self) -> u32 {
self as u32
}
pub fn height(self) -> XmssHeight {
let h = match self {
Self::XmssSha2_10_256 | Self::XmssShake_10_256 => 10_u8,
Self::XmssSha2_16_256 | Self::XmssShake_16_256 => 16_u8,
Self::XmssSha2_20_256 | Self::XmssShake_20_256 => 20_u8,
};
XmssHeight::new(h).expect("ParameterSet heights {10, 16, 20} are all valid XmssHeights")
}
pub const fn hash_function(self) -> XmssHashFunction {
match self {
Self::XmssSha2_10_256 | Self::XmssSha2_16_256 | Self::XmssSha2_20_256 => {
XmssHashFunction::Sha2_256
}
Self::XmssShake_10_256 | Self::XmssShake_16_256 | Self::XmssShake_20_256 => {
XmssHashFunction::Shake256
}
}
}
}
pub fn new_keypair(p: ParameterSet, expanded_seed: &[u8; EXPANDED_SEED_SIZE]) -> Result<Xmss> {
Xmss::initialize_tree_from_expanded_seed(p.height(), p.hash_function(), expanded_seed)
}
pub fn marshal_public_key(xmss: &Xmss, p: ParameterSet) -> [u8; RFC_PUBLIC_KEY_SIZE] {
let mut out = [0_u8; RFC_PUBLIC_KEY_SIZE];
out[..OID_LEN].copy_from_slice(&p.oid().to_be_bytes());
out[OID_LEN..].copy_from_slice(&xmss.public_key());
out
}
pub fn unmarshal_public_key(
bytes: &[u8; RFC_PUBLIC_KEY_SIZE],
) -> Result<(ParameterSet, [u8; 32], [u8; 32])> {
let mut oid_buf = [0_u8; 4];
oid_buf.copy_from_slice(&bytes[..4]);
let oid = u32::from_be_bytes(oid_buf);
let p = ParameterSet::from_oid(oid)?;
let mut root = [0_u8; 32];
let mut pub_seed = [0_u8; 32];
root.copy_from_slice(&bytes[4..36]);
pub_seed.copy_from_slice(&bytes[36..68]);
Ok((p, root, pub_seed))
}
pub fn verify(
p: ParameterSet,
message: &[u8],
signature: &[u8],
rfc_pk: &[u8; RFC_PUBLIC_KEY_SIZE],
) -> bool {
let (decoded_p, _root, _pub_seed) = match unmarshal_public_key(rfc_pk) {
Ok(t) => t,
Err(_) => return false,
};
if decoded_p != p {
return false;
}
verify_xmss(p.hash_function(), message, signature, &rfc_pk[OID_LEN..])
}
#[cfg(test)]
mod tests {
use super::*;
fn ascending_expanded_seed() -> [u8; EXPANDED_SEED_SIZE] {
let mut seed = [0_u8; EXPANDED_SEED_SIZE];
for (i, b) in seed.iter_mut().enumerate() {
*b = i as u8;
}
seed
}
#[test]
fn from_oid_round_trips() {
for &(oid, expected) in &[
(0x00000001_u32, ParameterSet::XmssSha2_10_256),
(0x00000002, ParameterSet::XmssSha2_16_256),
(0x00000003, ParameterSet::XmssSha2_20_256),
(0x00000007, ParameterSet::XmssShake_10_256),
(0x00000008, ParameterSet::XmssShake_16_256),
(0x00000009, ParameterSet::XmssShake_20_256),
] {
let p = ParameterSet::from_oid(oid).expect("supported oid");
assert_eq!(p, expected);
assert_eq!(p.oid(), oid);
}
}
#[test]
fn from_oid_rejects_unsupported() {
for oid in [0x00000004_u32, 0x00000005, 0x00000006, 0x0000000a, 0xffffffff] {
let err = ParameterSet::from_oid(oid).unwrap_err();
assert!(matches!(err, QrllibError::UnsupportedXmssParameterSet(o) if o == oid));
}
}
#[test]
fn keypair_marshal_unmarshal_round_trip() {
let seed = ascending_expanded_seed();
let tree =
new_keypair(ParameterSet::XmssSha2_10_256, &seed).expect("keypair from expanded seed");
let rfc_pk = marshal_public_key(&tree, ParameterSet::XmssSha2_10_256);
assert_eq!(&rfc_pk[..4], &[0x00, 0x00, 0x00, 0x01]);
assert_eq!(&rfc_pk[36..68], &seed[64..96]);
let (p, root, pub_seed) = unmarshal_public_key(&rfc_pk).expect("unmarshal");
assert_eq!(p, ParameterSet::XmssSha2_10_256);
assert_eq!(&pub_seed, &seed[64..96]);
assert_eq!(&root, &tree.public_key()[..32]);
}
#[test]
fn verify_accepts_in_house_signature_with_rfc_pk() {
let seed = ascending_expanded_seed();
let mut tree =
new_keypair(ParameterSet::XmssSha2_10_256, &seed).expect("keypair from expanded seed");
let message = b"rfc8391 module round-trip";
let signature = tree.sign(message).expect("sign");
let rfc_pk = marshal_public_key(&tree, ParameterSet::XmssSha2_10_256);
assert!(verify(ParameterSet::XmssSha2_10_256, message, &signature, &rfc_pk));
assert!(!verify(ParameterSet::XmssSha2_16_256, message, &signature, &rfc_pk));
assert!(!verify(ParameterSet::XmssSha2_10_256, b"tampered", &signature, &rfc_pk));
}
#[test]
fn distinct_seeds_produce_distinct_roots() {
let mut seed_a = ascending_expanded_seed();
seed_a[0] ^= 0x01;
let mut seed_b = ascending_expanded_seed();
seed_b[0] ^= 0x02;
let tree_a = new_keypair(ParameterSet::XmssSha2_10_256, &seed_a).expect("a");
let tree_b = new_keypair(ParameterSet::XmssSha2_10_256, &seed_b).expect("b");
assert_ne!(tree_a.public_key(), tree_b.public_key());
}
}