use crate::{WinternitzError, WinternitzPrivkey};
use hmac::{Hmac, Mac};
use sha2::{Digest, Sha256, Sha512};
use zeroize::{Zeroize, ZeroizeOnDrop};
const MAX_PASSPHRASE_LEN: usize = 256;
#[derive(Default, Zeroize, ZeroizeOnDrop)]
pub struct WinternitzKeypair {
key: [u8; 32],
chain_code: [u8; 32],
wallet: u32,
parent: u32,
child: u32,
}
impl WinternitzKeypair {
pub const WORDLIST: &str = include_str!("wordlist.txt");
pub fn from_mnemonic(
mnemonic: &str,
wallet: u32,
) -> Result<WinternitzKeypair, WinternitzError> {
Self::validate(mnemonic)?;
Ok(Self::from_mnemonic_unchecked(mnemonic, wallet))
}
pub fn from_mnemonic_at(
mnemonic: &str,
wallet: u32,
parent: u32,
child: u32,
) -> Result<WinternitzKeypair, WinternitzError> {
Self::validate(mnemonic)?;
let mut kp = Self::from_mnemonic_unchecked(mnemonic, wallet);
kp.parent = parent;
kp.child = child;
Ok(kp)
}
pub fn wallet(&self) -> u32 {
self.wallet
}
pub fn parent(&self) -> u32 {
self.parent
}
pub fn child(&self) -> u32 {
self.child
}
pub fn generate_mnemonic(entropy: [u8; 32]) -> [&'static str; 24] {
let mut bits = [0u8; 33];
bits[..32].copy_from_slice(&entropy);
bits[32] = Sha256::digest(entropy)[0];
let mut words: [&'static str; 24] = [""; 24];
let mut bit_pos = 0usize;
for slot in &mut words {
let mut idx = 0u16;
for _ in 0..11 {
idx = (idx << 1) | (((bits[bit_pos / 8] >> (7 - (bit_pos % 8))) & 1) as u16);
bit_pos += 1;
}
*slot = Self::WORDLIST
.lines()
.nth(idx as usize)
.expect("11-bit index always < 2048");
}
words
}
fn validate(mnemonic: &str) -> Result<(), WinternitzError> {
let count = mnemonic.split_ascii_whitespace().count();
let total_bits = match count {
12 => 132,
15 => 165,
18 => 198,
21 => 231,
24 => 264,
_ => return Err(WinternitzError::InvalidMnemonic),
};
let entropy_bits = total_bits * 32 / 33;
let cs_bits = total_bits - entropy_bits;
let mut bits = [0u8; 33];
let mut bit_pos = 0usize;
for word in mnemonic.split_ascii_whitespace() {
let idx = Self::WORDLIST
.lines()
.position(|line| line == word)
.ok_or(WinternitzError::InvalidMnemonic)? as u16;
for b in (0..11).rev() {
if (idx >> b) & 1 == 1 {
bits[bit_pos / 8] |= 1 << (7 - (bit_pos % 8));
}
bit_pos += 1;
}
}
let entropy_bytes = entropy_bits / 8;
let hash = Sha256::digest(&bits[..entropy_bytes]);
let mask = 0xFFu8 << (8 - cs_bits);
if (bits[entropy_bytes] & mask) != (hash[0] & mask) {
return Err(WinternitzError::InvalidMnemonic);
}
Ok(())
}
pub fn seed(mnemonic: &str) -> Result<[u8; 64], WinternitzError> {
Self::seed_with_passphrase(mnemonic, "")
}
pub fn seed_with_passphrase(
mnemonic: &str,
passphrase: &str,
) -> Result<[u8; 64], WinternitzError> {
if passphrase.len() > MAX_PASSPHRASE_LEN {
return Err(WinternitzError::InvalidLength);
}
Self::validate(mnemonic)?;
Ok(Self::raw_seed(mnemonic, passphrase))
}
fn raw_seed(mnemonic: &str, passphrase: &str) -> [u8; 64] {
debug_assert!(passphrase.len() <= MAX_PASSPHRASE_LEN);
const PREFIX: &[u8] = b"mnemonic";
let mut salt = [0u8; PREFIX.len() + MAX_PASSPHRASE_LEN];
salt[..PREFIX.len()].copy_from_slice(PREFIX);
salt[PREFIX.len()..PREFIX.len() + passphrase.len()].copy_from_slice(passphrase.as_bytes());
let mut seed = [0u8; 64];
pbkdf2::pbkdf2_hmac::<Sha512>(
mnemonic.as_bytes(),
&salt[..PREFIX.len() + passphrase.len()],
2048,
&mut seed,
);
seed
}
fn from_mnemonic_unchecked(mnemonic: &str, wallet: u32) -> WinternitzKeypair {
let seed = Self::raw_seed(mnemonic, "");
let mut mac = <Hmac<Sha512>>::new_from_slice(b"Winternitz seed")
.expect("HMAC accepts any key length");
mac.update(&seed);
let i = mac.finalize().into_bytes();
let mut key = [0u8; 32];
let mut chain_code = [0u8; 32];
key.copy_from_slice(&i[..32]);
chain_code.copy_from_slice(&i[32..]);
WinternitzKeypair {
key,
chain_code,
wallet,
parent: 0,
child: 0,
}
}
fn derive_child(key: &[u8; 32], chain_code: &[u8; 32], index: u32) -> ([u8; 32], [u8; 32]) {
let mut mac =
<Hmac<Sha512>>::new_from_slice(chain_code).expect("HMAC accepts any key length");
mac.update(&[0u8]);
mac.update(key);
mac.update(&(index | 0x8000_0000).to_be_bytes());
let i = mac.finalize().into_bytes();
let mut child_key = [0u8; 32];
let mut child_chain = [0u8; 32];
child_key.copy_from_slice(&i[..32]);
child_chain.copy_from_slice(&i[32..]);
(child_key, child_chain)
}
pub fn derive<const N: usize>(&self) -> WinternitzPrivkey<N> {
const { crate::assert_n::<N>() };
let (mut k, mut c) = (self.key, self.chain_code);
for idx in [self.wallet, self.parent, self.child] {
let (nk, nc) = Self::derive_child(&k, &c, idx);
k = nk;
c = nc;
}
let mut scalars = [[0u8; 32]; N];
for (i, slot) in scalars.iter_mut().enumerate() {
*slot = Self::derive_child(&k, &c, i as u32).0;
}
let checksum = [
Self::derive_child(&k, &c, N as u32).0,
Self::derive_child(&k, &c, (N + 1) as u32).0,
];
WinternitzPrivkey::new(scalars, checksum)
}
pub fn sign_and_increment<const N: usize>(
&mut self,
message: &[&[u8]],
) -> crate::WinternitzSignature<N> {
let sig = self.derive::<N>().sign(message);
self.increment_child();
sig
}
pub fn increment_child(&mut self) {
match self.child.checked_add(1) {
Some(c) => self.child = c,
None => {
self.child = 0;
self.increment_parent()
}
}
}
pub fn increment_parent(&mut self) {
self.parent = self.parent.checked_add(1).expect("parent overflow");
self.child = 0;
}
}