type HashFn = fn(&[u8]) -> [u8; 32];
pub mod constants {
pub const HASH_LEN: usize = 32;
pub const MESSAGE_LEN: usize = HASH_LEN;
pub const CHAIN_LEN: usize = 16;
pub const LG_CHAIN_LEN: usize = {
CHAIN_LEN.ilog2() as usize
};
pub const NUM_MESSAGE_CHUNKS: usize = {
(8 * HASH_LEN).div_ceil(LG_CHAIN_LEN)
};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_num_message_chunks() {
assert_eq!(NUM_MESSAGE_CHUNKS, 64);
}
}
pub const NUM_CHECKSUM_CHUNKS: usize = {
((NUM_MESSAGE_CHUNKS * (CHAIN_LEN - 1)).ilog2() as usize / LG_CHAIN_LEN) + 1
};
pub const NUM_SIGNATURE_CHUNKS: usize = NUM_MESSAGE_CHUNKS + NUM_CHECKSUM_CHUNKS;
pub const SIGNATURE_SIZE: usize = NUM_SIGNATURE_CHUNKS * HASH_LEN;
pub const PUBLIC_KEY_SIZE: usize = HASH_LEN * 2;
pub const PRF_INPUT_SIZE: usize = 1 + HASH_LEN + 2;
}
#[derive(Debug, Clone, Copy)]
pub struct PublicKey {
pub public_seed: [u8; constants::HASH_LEN],
pub public_key_hash: [u8; constants::HASH_LEN],
}
impl PublicKey {
pub fn to_bytes(&self) -> [u8; constants::PUBLIC_KEY_SIZE] {
let mut result = [0u8; constants::PUBLIC_KEY_SIZE];
result[..constants::HASH_LEN].copy_from_slice(&self.public_seed);
result[constants::HASH_LEN..].copy_from_slice(&self.public_key_hash);
result
}
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() != constants::PUBLIC_KEY_SIZE {
return None;
}
let mut public_seed = [0u8; constants::HASH_LEN];
let mut public_key_hash = [0u8; constants::HASH_LEN];
public_seed.copy_from_slice(&bytes[..constants::HASH_LEN]);
public_key_hash.copy_from_slice(&bytes[constants::HASH_LEN..]);
Some(PublicKey {
public_seed,
public_key_hash,
})
}
}
pub struct WOTSPlus {
hash_fn: HashFn,
}
impl WOTSPlus {
pub fn new(hash_fn: HashFn) -> Self {
Self { hash_fn }
}
fn prf(&self, seed: &[u8; constants::HASH_LEN], index: u16) -> [u8; constants::HASH_LEN] {
let mut input = [0u8; constants::PRF_INPUT_SIZE];
input[0] = 0x03; input[1..33].copy_from_slice(seed); input[33..].copy_from_slice(&index.to_be_bytes()); (self.hash_fn)(&input)
}
pub fn generate_randomization_elements(
&self,
public_seed: &[u8; constants::HASH_LEN]
) -> Vec<[u8; constants::HASH_LEN]> {
let mut elements = Vec::with_capacity(constants::NUM_SIGNATURE_CHUNKS);
for i in 0..constants::NUM_SIGNATURE_CHUNKS {
elements.push(self.prf(public_seed, i as u16));
}
elements
}
fn xor(a: &[u8; constants::HASH_LEN], b: &[u8; constants::HASH_LEN]) -> [u8; constants::HASH_LEN] {
let mut result = [0u8; constants::HASH_LEN];
for i in 0..constants::HASH_LEN {
result[i] = a[i] ^ b[i];
}
result
}
fn chain(
&self,
prev_chain_out: &[u8; constants::HASH_LEN],
randomization_elements: &[[u8; constants::HASH_LEN]],
index: u16,
steps: u16,
) -> [u8; constants::HASH_LEN] {
let mut chain_out = *prev_chain_out;
for i in 1..=steps {
let xored = Self::xor(&chain_out, &randomization_elements[(i + index) as usize]);
chain_out = (self.hash_fn)(&xored);
}
chain_out
}
fn compute_message_hash_chain_indexes(&self, message: &[u8]) -> Vec<u8> {
if message.len() != constants::MESSAGE_LEN {
panic!("Message length must be {} bytes", constants::MESSAGE_LEN);
}
let mut chain_segments_indexes = vec![0u8; constants::NUM_SIGNATURE_CHUNKS];
let mut idx = 0;
for byte in message {
chain_segments_indexes[idx] = byte >> 4;
chain_segments_indexes[idx + 1] = byte & 0x0f;
idx += 2;
}
let mut checksum: u32 = 0;
for &value in &chain_segments_indexes[..constants::NUM_MESSAGE_CHUNKS] {
checksum += constants::CHAIN_LEN as u32 - 1 - value as u32
}
for i in (0..constants::NUM_CHECKSUM_CHUNKS).rev() {
let shift = i * constants::LG_CHAIN_LEN as usize;
chain_segments_indexes[idx] = ((checksum >> shift) & (constants::CHAIN_LEN as u32 - 1)) as u8;
idx += 1;
}
chain_segments_indexes
}
pub fn get_public_key(&self, private_key: &[u8; constants::HASH_LEN]) -> PublicKey {
let public_seed = self.prf(private_key, 0);
self.get_public_key_with_public_seed(private_key, &public_seed)
}
pub fn get_public_key_with_public_seed(&self, private_key: &[u8; constants::HASH_LEN], public_seed: &[u8; constants::HASH_LEN]) -> PublicKey {
let randomization_elements = self.generate_randomization_elements(&public_seed);
let function_key = randomization_elements[0];
let mut public_key_segments = Vec::with_capacity(constants::SIGNATURE_SIZE);
for i in 0..constants::NUM_SIGNATURE_CHUNKS {
let mut to_hash = vec![0u8; constants::HASH_LEN * 2];
to_hash[..constants::HASH_LEN].copy_from_slice(&function_key);
to_hash[constants::HASH_LEN..].copy_from_slice(&self.prf(private_key, (i + 1) as u16));
let secret_key_segment = (self.hash_fn)(&to_hash);
let segment = self.chain(
&secret_key_segment,
&randomization_elements,
0,
(constants::CHAIN_LEN - 1) as u16,
);
public_key_segments.extend_from_slice(&segment);
}
let public_key_hash = (self.hash_fn)(&public_key_segments);
PublicKey {
public_seed: *public_seed,
public_key_hash,
}
}
pub fn generate_key_pair(&self, private_seed: &[u8; constants::HASH_LEN]) -> (PublicKey, [u8; constants::HASH_LEN]) {
let private_key = (self.hash_fn)(private_seed);
let public_key = self.get_public_key(&private_key);
(public_key, private_key)
}
pub fn sign(&self, private_key: &[u8; constants::HASH_LEN], message: &[u8]) -> Vec<[u8; constants::HASH_LEN]> {
if message.len() != constants::MESSAGE_LEN {
panic!("Message length must be {} bytes", constants::MESSAGE_LEN);
}
let public_seed = self.prf(private_key, 0);
let randomization_elements = self.generate_randomization_elements(&public_seed);
let function_key = randomization_elements[0];
let chain_segments = self.compute_message_hash_chain_indexes(message);
let mut signature = Vec::with_capacity(constants::NUM_SIGNATURE_CHUNKS);
for (i, &chain_idx) in chain_segments.iter().enumerate() {
let mut to_hash = vec![0u8; constants::HASH_LEN * 2];
to_hash[..constants::HASH_LEN].copy_from_slice(&function_key);
to_hash[constants::HASH_LEN..].copy_from_slice(&self.prf(private_key, (i + 1) as u16));
let secret_key_segment = (self.hash_fn)(&to_hash);
let sig_segment = self.chain(
&secret_key_segment,
&randomization_elements,
0,
chain_idx as u16,
);
signature.push(sig_segment);
}
signature
}
pub fn verify(&self, public_key: &PublicKey, message: &[u8], signature: &Vec<[u8; constants::HASH_LEN]>) -> bool {
if message.len() != constants::MESSAGE_LEN {
return false;
}
if signature.len() != constants::NUM_SIGNATURE_CHUNKS {
return false;
}
let randomization_elements = self.generate_randomization_elements(&public_key.public_seed);
let chain_segments = self.compute_message_hash_chain_indexes(message);
let mut public_key_segments = Vec::with_capacity(constants::SIGNATURE_SIZE);
for (i, &chain_idx) in chain_segments.iter().enumerate() {
let num_iterations = (constants::CHAIN_LEN - 1 - chain_idx as usize) as u16;
let segment = self.chain(
&signature[i],
&randomization_elements,
chain_idx as u16,
num_iterations,
);
public_key_segments.extend_from_slice(&segment);
}
let computed_hash = (self.hash_fn)(&public_key_segments);
computed_hash == public_key.public_key_hash
}
pub fn verify_with_randomization_elements(
&self,
public_key_hash: &[u8; constants::HASH_LEN],
message: &[u8],
signature: &Vec<[u8; constants::HASH_LEN]>,
randomization_elements: &Vec<[u8; constants::HASH_LEN]>,
) -> bool {
if message.len() != constants::MESSAGE_LEN {
return false;
}
if signature.len() != constants::NUM_SIGNATURE_CHUNKS {
return false;
}
if randomization_elements.len() != constants::NUM_SIGNATURE_CHUNKS {
return false;
}
let chain_segments = self.compute_message_hash_chain_indexes(message);
let mut public_key_segments = [0u8; constants::SIGNATURE_SIZE];
for (i, &chain_idx) in chain_segments.iter().enumerate() {
let num_iterations = (constants::CHAIN_LEN - 1 - chain_idx as usize) as u16;
let segment = self.chain(
&signature[i],
randomization_elements,
chain_idx as u16,
num_iterations,
);
let offset = i * constants::HASH_LEN;
public_key_segments[offset..offset + constants::HASH_LEN].copy_from_slice(&segment);
}
let computed_hash = (self.hash_fn)(&public_key_segments);
computed_hash == *public_key_hash
}
}
#[cfg(test)]
mod tests {
use super::*;
fn mock_hash(data: &[u8]) -> [u8; 32] {
let mut output = [0u8; 32];
for (i, &byte) in data.iter().enumerate().take(32) {
output[i] = byte;
}
output
}
#[test]
fn test_constants() {
assert_eq!(constants::HASH_LEN, 32);
assert_eq!(constants::MESSAGE_LEN, 32);
assert_eq!(constants::CHAIN_LEN, 16);
assert_eq!(constants::NUM_MESSAGE_CHUNKS, 64);
assert_eq!(constants::NUM_CHECKSUM_CHUNKS, 3);
assert_eq!(constants::NUM_SIGNATURE_CHUNKS, 67);
}
#[test]
fn test_key_generation_and_signing() {
let wots = WOTSPlus::new(mock_hash);
let private_seed = [1u8; 32];
let (public_key, private_key) = wots.generate_key_pair(&private_seed);
let message = [2u8; constants::MESSAGE_LEN];
let signature = wots.sign(&private_key, &message);
assert!(wots.verify(&public_key, &message, &signature));
}
#[test]
fn test_invalid_message_length() {
let wots = WOTSPlus::new(mock_hash);
let private_seed = [1u8; 32];
let (public_key, _) = wots.generate_key_pair(&private_seed);
let invalid_message = [2u8; constants::MESSAGE_LEN + 1];
let signature: Vec<[u8; 32]> = vec![[0u8; 32]; constants::NUM_SIGNATURE_CHUNKS];
assert!(!wots.verify(&public_key, &invalid_message, &signature));
}
#[test]
fn test_invalid_signature_length() {
let wots = WOTSPlus::new(mock_hash);
let private_seed = [1u8; 32];
let (public_key, _) = wots.generate_key_pair(&private_seed);
let message = [2u8; constants::MESSAGE_LEN];
let signature: Vec<[u8; 32]> = vec![[0u8; 32]; constants::NUM_SIGNATURE_CHUNKS];
assert!(!wots.verify(&public_key, &message, &signature));
}
#[test]
fn test_public_key_serialization() {
let public_key = PublicKey {
public_seed: [1u8; constants::HASH_LEN],
public_key_hash: [2u8; constants::HASH_LEN],
};
let bytes = public_key.to_bytes();
let recovered = PublicKey::from_bytes(&bytes).unwrap();
assert_eq!(recovered.public_seed, public_key.public_seed);
assert_eq!(recovered.public_key_hash, public_key.public_key_hash);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_num_message_chunks() {
assert_eq!(constants::NUM_MESSAGE_CHUNKS, 64);
}
#[test]
fn test_num_checksum_chunks() {
assert_eq!(constants::NUM_CHECKSUM_CHUNKS, 3);
}
}
}