winterwallet_core/
keypair.rs1use crate::{WinternitzError, WinternitzPrivkey};
2use hmac::{Hmac, Mac};
3use sha2::{Digest, Sha256, Sha512};
4use zeroize::{Zeroize, ZeroizeOnDrop};
5
6const MAX_PASSPHRASE_LEN: usize = 256;
7
8#[derive(Default, Zeroize, ZeroizeOnDrop)]
23pub struct WinternitzKeypair {
24 key: [u8; 32],
25 chain_code: [u8; 32],
26 wallet: u32,
27 parent: u32,
28 child: u32,
29}
30
31impl WinternitzKeypair {
32 pub const WORDLIST: &str = include_str!("wordlist.txt");
34
35 pub fn from_mnemonic(
39 mnemonic: &str,
40 wallet: u32,
41 ) -> Result<WinternitzKeypair, WinternitzError> {
42 Self::validate(mnemonic)?;
43 Ok(Self::from_mnemonic_unchecked(mnemonic, wallet))
44 }
45
46 pub fn from_mnemonic_at(
49 mnemonic: &str,
50 wallet: u32,
51 parent: u32,
52 child: u32,
53 ) -> Result<WinternitzKeypair, WinternitzError> {
54 Self::validate(mnemonic)?;
55 let mut kp = Self::from_mnemonic_unchecked(mnemonic, wallet);
56 kp.parent = parent;
57 kp.child = child;
58 Ok(kp)
59 }
60
61 pub fn wallet(&self) -> u32 {
63 self.wallet
64 }
65
66 pub fn parent(&self) -> u32 {
68 self.parent
69 }
70
71 pub fn child(&self) -> u32 {
73 self.child
74 }
75
76 pub fn generate_mnemonic(entropy: [u8; 32]) -> [&'static str; 24] {
79 let mut bits = [0u8; 33];
80 bits[..32].copy_from_slice(&entropy);
81 bits[32] = Sha256::digest(entropy)[0];
82
83 let mut words: [&'static str; 24] = [""; 24];
84 let mut bit_pos = 0usize;
85 for slot in &mut words {
86 let mut idx = 0u16;
87 for _ in 0..11 {
88 idx = (idx << 1) | (((bits[bit_pos / 8] >> (7 - (bit_pos % 8))) & 1) as u16);
89 bit_pos += 1;
90 }
91 *slot = Self::WORDLIST
92 .lines()
93 .nth(idx as usize)
94 .expect("11-bit index always < 2048");
95 }
96 words
97 }
98
99 fn validate(mnemonic: &str) -> Result<(), WinternitzError> {
100 let count = mnemonic.split_ascii_whitespace().count();
101 let total_bits = match count {
102 12 => 132,
103 15 => 165,
104 18 => 198,
105 21 => 231,
106 24 => 264,
107 _ => return Err(WinternitzError::InvalidMnemonic),
108 };
109 let entropy_bits = total_bits * 32 / 33;
110 let cs_bits = total_bits - entropy_bits;
111
112 let mut bits = [0u8; 33];
113 let mut bit_pos = 0usize;
114 for word in mnemonic.split_ascii_whitespace() {
115 let idx = Self::WORDLIST
116 .lines()
117 .position(|line| line == word)
118 .ok_or(WinternitzError::InvalidMnemonic)? as u16;
119 for b in (0..11).rev() {
120 if (idx >> b) & 1 == 1 {
121 bits[bit_pos / 8] |= 1 << (7 - (bit_pos % 8));
122 }
123 bit_pos += 1;
124 }
125 }
126
127 let entropy_bytes = entropy_bits / 8;
128 let hash = Sha256::digest(&bits[..entropy_bytes]);
129 let mask = 0xFFu8 << (8 - cs_bits);
130 if (bits[entropy_bytes] & mask) != (hash[0] & mask) {
131 return Err(WinternitzError::InvalidMnemonic);
132 }
133 Ok(())
134 }
135
136 pub fn seed(mnemonic: &str) -> Result<[u8; 64], WinternitzError> {
139 Self::seed_with_passphrase(mnemonic, "")
140 }
141
142 pub fn seed_with_passphrase(
146 mnemonic: &str,
147 passphrase: &str,
148 ) -> Result<[u8; 64], WinternitzError> {
149 if passphrase.len() > MAX_PASSPHRASE_LEN {
150 return Err(WinternitzError::InvalidLength);
151 }
152 Self::validate(mnemonic)?;
153 Ok(Self::raw_seed(mnemonic, passphrase))
154 }
155
156 fn raw_seed(mnemonic: &str, passphrase: &str) -> [u8; 64] {
157 debug_assert!(passphrase.len() <= MAX_PASSPHRASE_LEN);
158 const PREFIX: &[u8] = b"mnemonic";
159 let mut salt = [0u8; PREFIX.len() + MAX_PASSPHRASE_LEN];
160 salt[..PREFIX.len()].copy_from_slice(PREFIX);
161 salt[PREFIX.len()..PREFIX.len() + passphrase.len()].copy_from_slice(passphrase.as_bytes());
162 let mut seed = [0u8; 64];
163 pbkdf2::pbkdf2_hmac::<Sha512>(
164 mnemonic.as_bytes(),
165 &salt[..PREFIX.len() + passphrase.len()],
166 2048,
167 &mut seed,
168 );
169 seed
170 }
171
172 fn from_mnemonic_unchecked(mnemonic: &str, wallet: u32) -> WinternitzKeypair {
173 let seed = Self::raw_seed(mnemonic, "");
174
175 let mut mac = <Hmac<Sha512>>::new_from_slice(b"Winternitz seed")
176 .expect("HMAC accepts any key length");
177 mac.update(&seed);
178 let i = mac.finalize().into_bytes();
179
180 let mut key = [0u8; 32];
181 let mut chain_code = [0u8; 32];
182 key.copy_from_slice(&i[..32]);
183 chain_code.copy_from_slice(&i[32..]);
184
185 WinternitzKeypair {
186 key,
187 chain_code,
188 wallet,
189 parent: 0,
190 child: 0,
191 }
192 }
193
194 fn derive_child(key: &[u8; 32], chain_code: &[u8; 32], index: u32) -> ([u8; 32], [u8; 32]) {
195 let mut mac =
196 <Hmac<Sha512>>::new_from_slice(chain_code).expect("HMAC accepts any key length");
197 mac.update(&[0u8]);
198 mac.update(key);
199 mac.update(&(index | 0x8000_0000).to_be_bytes());
200 let i = mac.finalize().into_bytes();
201
202 let mut child_key = [0u8; 32];
203 let mut child_chain = [0u8; 32];
204 child_key.copy_from_slice(&i[..32]);
205 child_chain.copy_from_slice(&i[32..]);
206 (child_key, child_chain)
207 }
208
209 pub fn derive<const N: usize>(&self) -> WinternitzPrivkey<N> {
214 const { crate::assert_n::<N>() };
215 let (mut k, mut c) = (self.key, self.chain_code);
216 for idx in [self.wallet, self.parent, self.child] {
217 let (nk, nc) = Self::derive_child(&k, &c, idx);
218 k = nk;
219 c = nc;
220 }
221
222 let mut scalars = [[0u8; 32]; N];
223 for (i, slot) in scalars.iter_mut().enumerate() {
224 *slot = Self::derive_child(&k, &c, i as u32).0;
225 }
226 let checksum = [
227 Self::derive_child(&k, &c, N as u32).0,
228 Self::derive_child(&k, &c, (N + 1) as u32).0,
229 ];
230
231 WinternitzPrivkey::new(scalars, checksum)
232 }
233
234 pub fn sign_and_increment<const N: usize>(
239 &mut self,
240 message: &[&[u8]],
241 ) -> crate::WinternitzSignature<N> {
242 let sig = self.derive::<N>().sign(message);
243 self.increment_child();
244 sig
245 }
246
247 pub fn increment_child(&mut self) {
251 match self.child.checked_add(1) {
252 Some(c) => self.child = c,
253 None => {
254 self.child = 0;
255 self.increment_parent()
256 }
257 }
258 }
259
260 pub fn increment_parent(&mut self) {
263 self.parent = self.parent.checked_add(1).expect("parent overflow");
264 self.child = 0;
265 }
266}