1use bitcoin::bip32::{ChildNumber, DerivationPath, Xpriv};
6use bitcoin::secp256k1::hashes::{hmac, sha256, Hash, HashEngine, HmacEngine};
7use bitcoin::{secp256k1, Network};
8use thiserror::Error;
9use tracing::instrument;
10
11use super::nut00::{BlindedMessage, PreMint, PreMintSecrets};
12use super::nut01::SecretKey;
13use super::nut02::Id;
14use crate::amount::{FeeAndAmounts, SplitTarget};
15use crate::dhke::blind_message;
16use crate::secret::Secret;
17use crate::util::hex;
18use crate::{Amount, SECP256K1};
19
20#[derive(Debug, Error)]
22pub enum Error {
23 #[error(transparent)]
25 DHKE(#[from] crate::dhke::Error),
26 #[error(transparent)]
28 Amount(#[from] crate::amount::Error),
29 #[error(transparent)]
31 NUT00(#[from] crate::nuts::nut00::Error),
32 #[error(transparent)]
34 NUT02(#[from] crate::nuts::nut02::Error),
35 #[error(transparent)]
37 Bip32(#[from] bitcoin::bip32::Error),
38 #[error(transparent)]
40 Hmac(#[from] bitcoin::secp256k1::hashes::FromSliceError),
41 #[error(transparent)]
43 SecpError(#[from] bitcoin::secp256k1::Error),
44}
45
46impl Secret {
47 pub fn from_seed(seed: &[u8; 64], keyset_id: Id, counter: u32) -> Result<Self, Error> {
49 match keyset_id.get_version() {
50 super::nut02::KeySetVersion::Version00 => Self::legacy_derive(seed, keyset_id, counter),
51 super::nut02::KeySetVersion::Version01 => Self::derive(seed, keyset_id, counter),
52 }
53 }
54
55 fn legacy_derive(seed: &[u8; 64], keyset_id: Id, counter: u32) -> Result<Self, Error> {
56 let xpriv = Xpriv::new_master(Network::Bitcoin, seed)?;
57 let path = derive_path_from_keyset_id(keyset_id)?
58 .child(ChildNumber::from_hardened_idx(counter)?)
59 .child(ChildNumber::from_normal_idx(0)?);
60 let derived_xpriv = xpriv.derive_priv(&SECP256K1, &path)?;
61
62 Ok(Self::new(hex::encode(
63 derived_xpriv.private_key.secret_bytes(),
64 )))
65 }
66
67 fn derive(seed: &[u8; 64], keyset_id: Id, counter: u32) -> Result<Self, Error> {
68 let mut message = Vec::new();
69 message.extend_from_slice(b"Cashu_KDF_HMAC_SHA256");
70 message.extend_from_slice(&keyset_id.to_bytes());
71 message.extend_from_slice(&(counter as u64).to_be_bytes());
72 message.extend_from_slice(b"\x00");
73
74 let mut engine = HmacEngine::<sha256::Hash>::new(seed);
75 engine.input(&message);
76 let hmac_result = hmac::Hmac::<sha256::Hash>::from_engine(engine);
77 let result_bytes = hmac_result.to_byte_array();
78
79 Ok(Self::new(hex::encode(&result_bytes[..32])))
80 }
81}
82
83impl SecretKey {
84 pub fn from_seed(seed: &[u8; 64], keyset_id: Id, counter: u32) -> Result<Self, Error> {
86 match keyset_id.get_version() {
87 super::nut02::KeySetVersion::Version00 => Self::legacy_derive(seed, keyset_id, counter),
88 super::nut02::KeySetVersion::Version01 => Self::derive(seed, keyset_id, counter),
89 }
90 }
91
92 fn legacy_derive(seed: &[u8; 64], keyset_id: Id, counter: u32) -> Result<Self, Error> {
93 let xpriv = Xpriv::new_master(Network::Bitcoin, seed)?;
94 let path = derive_path_from_keyset_id(keyset_id)?
95 .child(ChildNumber::from_hardened_idx(counter)?)
96 .child(ChildNumber::from_normal_idx(1)?);
97 let derived_xpriv = xpriv.derive_priv(&SECP256K1, &path)?;
98
99 Ok(Self::from(derived_xpriv.private_key))
100 }
101
102 fn derive(seed: &[u8; 64], keyset_id: Id, counter: u32) -> Result<Self, Error> {
103 let mut message = Vec::new();
104 message.extend_from_slice(b"Cashu_KDF_HMAC_SHA256");
105 message.extend_from_slice(&keyset_id.to_bytes());
106 message.extend_from_slice(&(counter as u64).to_be_bytes());
107 message.extend_from_slice(b"\x01");
108
109 let mut engine = HmacEngine::<sha256::Hash>::new(seed);
110 engine.input(&message);
111 let hmac_result = hmac::Hmac::<sha256::Hash>::from_engine(engine);
112 let result_bytes = hmac_result.to_byte_array();
113
114 Ok(Self::from(secp256k1::SecretKey::from_slice(
115 &result_bytes[..32],
116 )?))
117 }
118}
119
120impl PreMintSecrets {
121 #[instrument(skip(seed))]
124 pub fn from_seed(
125 keyset_id: Id,
126 counter: u32,
127 seed: &[u8; 64],
128 amount: Amount,
129 amount_split_target: &SplitTarget,
130 fee_and_amounts: &FeeAndAmounts,
131 ) -> Result<Self, Error> {
132 let mut pre_mint_secrets = PreMintSecrets::new(keyset_id);
133
134 let mut counter = counter;
135
136 for amount in amount.split_targeted(amount_split_target, fee_and_amounts)? {
137 let secret = Secret::from_seed(seed, keyset_id, counter)?;
138 let blinding_factor = SecretKey::from_seed(seed, keyset_id, counter)?;
139
140 let (blinded, r) = blind_message(&secret.to_bytes(), Some(blinding_factor))?;
141
142 let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
143
144 let pre_mint = PreMint {
145 blinded_message,
146 secret: secret.clone(),
147 r,
148 amount,
149 };
150
151 pre_mint_secrets.secrets.push(pre_mint);
152 counter += 1;
153 }
154
155 Ok(pre_mint_secrets)
156 }
157
158 pub fn from_seed_blank(
160 keyset_id: Id,
161 counter: u32,
162 seed: &[u8; 64],
163 amount: Amount,
164 ) -> Result<Self, Error> {
165 if amount <= Amount::ZERO {
166 return Ok(PreMintSecrets::new(keyset_id));
167 }
168 let count = ((u64::from(amount) as f64).log2().ceil() as u64).max(1);
169 let mut pre_mint_secrets = PreMintSecrets::new(keyset_id);
170
171 let mut counter = counter;
172
173 for _ in 0..count {
174 let secret = Secret::from_seed(seed, keyset_id, counter)?;
175 let blinding_factor = SecretKey::from_seed(seed, keyset_id, counter)?;
176
177 let (blinded, r) = blind_message(&secret.to_bytes(), Some(blinding_factor))?;
178
179 let amount = Amount::ZERO;
180
181 let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
182
183 let pre_mint = PreMint {
184 blinded_message,
185 secret: secret.clone(),
186 r,
187 amount,
188 };
189
190 pre_mint_secrets.secrets.push(pre_mint);
191 counter += 1;
192 }
193
194 Ok(pre_mint_secrets)
195 }
196
197 pub fn restore_batch(
200 keyset_id: Id,
201 seed: &[u8; 64],
202 start_count: u32,
203 end_count: u32,
204 ) -> Result<Self, Error> {
205 let mut pre_mint_secrets = PreMintSecrets::new(keyset_id);
206
207 for i in start_count..=end_count {
208 let secret = Secret::from_seed(seed, keyset_id, i)?;
209 let blinding_factor = SecretKey::from_seed(seed, keyset_id, i)?;
210
211 let (blinded, r) = blind_message(&secret.to_bytes(), Some(blinding_factor))?;
212
213 let blinded_message = BlindedMessage::new(Amount::ZERO, keyset_id, blinded);
214
215 let pre_mint = PreMint {
216 blinded_message,
217 secret: secret.clone(),
218 r,
219 amount: Amount::ZERO,
220 };
221
222 pre_mint_secrets.secrets.push(pre_mint);
223 }
224
225 Ok(pre_mint_secrets)
226 }
227}
228
229fn derive_path_from_keyset_id(id: Id) -> Result<DerivationPath, Error> {
230 let index = u32::from(id);
231
232 let keyset_child_number = ChildNumber::from_hardened_idx(index)?;
233 Ok(DerivationPath::from(vec![
234 ChildNumber::from_hardened_idx(129372)?,
235 ChildNumber::from_hardened_idx(0)?,
236 keyset_child_number,
237 ]))
238}
239
240#[cfg(test)]
241mod tests {
242 use std::str::FromStr;
243
244 use bip39::Mnemonic;
245 use bitcoin::bip32::DerivationPath;
246
247 use super::*;
248
249 #[test]
250 fn test_secret_from_seed() {
251 let seed =
252 "half depart obvious quality work element tank gorilla view sugar picture humble";
253 let mnemonic = Mnemonic::from_str(seed).unwrap();
254 let seed: [u8; 64] = mnemonic.to_seed("");
255 let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
256
257 let test_secrets = [
258 "485875df74771877439ac06339e284c3acfcd9be7abf3bc20b516faeadfe77ae",
259 "8f2b39e8e594a4056eb1e6dbb4b0c38ef13b1b2c751f64f810ec04ee35b77270",
260 "bc628c79accd2364fd31511216a0fab62afd4a18ff77a20deded7b858c9860c8",
261 "59284fd1650ea9fa17db2b3acf59ecd0f2d52ec3261dd4152785813ff27a33bf",
262 "576c23393a8b31cc8da6688d9c9a96394ec74b40fdaf1f693a6bb84284334ea0",
263 ];
264
265 for (i, test_secret) in test_secrets.iter().enumerate() {
266 let secret = Secret::from_seed(&seed, keyset_id, i.try_into().unwrap()).unwrap();
267 assert_eq!(secret, Secret::from_str(test_secret).unwrap())
268 }
269 }
270 #[test]
271 fn test_r_from_seed() {
272 let seed =
273 "half depart obvious quality work element tank gorilla view sugar picture humble";
274 let mnemonic = Mnemonic::from_str(seed).unwrap();
275 let seed: [u8; 64] = mnemonic.to_seed("");
276 let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
277
278 let test_rs = [
279 "ad00d431add9c673e843d4c2bf9a778a5f402b985b8da2d5550bf39cda41d679",
280 "967d5232515e10b81ff226ecf5a9e2e2aff92d66ebc3edf0987eb56357fd6248",
281 "b20f47bb6ae083659f3aa986bfa0435c55c6d93f687d51a01f26862d9b9a4899",
282 "fb5fca398eb0b1deb955a2988b5ac77d32956155f1c002a373535211a2dfdc29",
283 "5f09bfbfe27c439a597719321e061e2e40aad4a36768bb2bcc3de547c9644bf9",
284 ];
285
286 for (i, test_r) in test_rs.iter().enumerate() {
287 let r = SecretKey::from_seed(&seed, keyset_id, i.try_into().unwrap()).unwrap();
288 assert_eq!(r, SecretKey::from_hex(test_r).unwrap())
289 }
290 }
291
292 #[test]
293 fn test_derive_path_from_keyset_id() {
294 let test_cases = [
295 ("009a1f293253e41e", "m/129372'/0'/864559728'"),
296 ("0000000000000000", "m/129372'/0'/0'"),
297 ("00ffffffffffffff", "m/129372'/0'/33554431'"),
298 ];
299
300 for (id_hex, expected_path) in test_cases {
301 let id = Id::from_str(id_hex).unwrap();
302 let path = derive_path_from_keyset_id(id).unwrap();
303 assert_eq!(
304 DerivationPath::from_str(expected_path).unwrap(),
305 path,
306 "Path derivation failed for ID {id_hex}"
307 );
308 }
309 }
310
311 #[test]
312 fn test_secret_derivation_keyset_v2() {
313 let seed =
314 "half depart obvious quality work element tank gorilla view sugar picture humble";
315 let mnemonic = Mnemonic::from_str(seed).unwrap();
316 let seed: [u8; 64] = mnemonic.to_seed("");
317
318 let keyset_id =
320 Id::from_str("012e23479a0029432eaad0d2040c09be53bab592d5cbf1d55e0dd26c9495951b30")
321 .unwrap();
322
323 let test_secrets = [
325 "ba250bf927b1df5dd0a07c543be783a4349a7f99904acd3406548402d3484118",
326 "3a6423fe56abd5e74ec9d22a91ee110cd2ce45a7039901439d62e5534d3438c1",
327 "843484a75b78850096fac5b513e62854f11d57491cf775a6fd2edf4e583ae8c0",
328 "3600608d5cf8197374f060cfbcff134d2cd1fb57eea68cbcf2fa6917c58911b6",
329 "717fce9cc6f9ea060d20dd4e0230af4d63f3894cc49dd062fd99d033ea1ac1dd",
330 ];
331
332 for (i, test_secret) in test_secrets.iter().enumerate() {
333 let secret = Secret::from_seed(&seed, keyset_id, i.try_into().unwrap()).unwrap();
334 assert_eq!(secret.to_string().len(), 64); let secret2 = Secret::from_str(test_secret).unwrap();
340 assert_eq!(secret, secret2);
341 }
342 }
343
344 #[test]
345 fn test_secret_key_derivation_keyset_v2() {
346 let seed =
347 "half depart obvious quality work element tank gorilla view sugar picture humble";
348 let mnemonic = Mnemonic::from_str(seed).unwrap();
349 let seed: [u8; 64] = mnemonic.to_seed("");
350
351 let keyset_id =
353 Id::from_str("012e23479a0029432eaad0d2040c09be53bab592d5cbf1d55e0dd26c9495951b30")
354 .unwrap();
355
356 let test_secret_keys = [
357 "4f8b32a54aed811b692a665ed296b4c1fc2f37a8be4006379e95063a76693745",
358 "c4b8412ee644067007423480c9e556385b71ffdff0f340bc16a95c0534fe0e01",
359 "ceff40983441c40acaf77d2a8ddffd5c1c84391fb9fd0dc4607c186daab1c829",
360 "41ad26b840fb62d29b2318a82f1d9cd40dc0f1e58183cc57562f360a32fdfad6",
361 "fb986a9c76758593b0e2d1a5172ade977c858d87111a220e16c292a9347abf81",
362 ];
363
364 for (i, test_secret) in test_secret_keys.iter().enumerate() {
365 let secret_key = SecretKey::from_seed(&seed, keyset_id, i as u32).unwrap();
366
367 let secret_bytes = secret_key.secret_bytes();
369 assert_eq!(secret_bytes.len(), 32);
370
371 let secret_key2 = SecretKey::from_str(test_secret).unwrap();
373 assert_eq!(secret_key, secret_key2);
374 }
375 }
376
377 #[test]
378 fn test_v2_derivation_with_different_keysets() {
379 let seed =
380 "half depart obvious quality work element tank gorilla view sugar picture humble";
381 let mnemonic = Mnemonic::from_str(seed).unwrap();
382 let seed: [u8; 64] = mnemonic.to_seed("");
383
384 let keyset_id_1 =
385 Id::from_str("01adc013fa9d85171586660abab27579888611659d357bc86bc09cb26eee8bc035")
386 .unwrap();
387 let keyset_id_2 =
388 Id::from_str("01bef024fb9e85171586660abab27579888611659d357bc86bc09cb26eee8bc046")
389 .unwrap();
390
391 for counter in 0..3 {
393 let secret_1 = Secret::from_seed(&seed, keyset_id_1, counter).unwrap();
394 let secret_2 = Secret::from_seed(&seed, keyset_id_2, counter).unwrap();
395 assert_ne!(
396 secret_1, secret_2,
397 "Different keyset IDs should produce different secrets for counter {}",
398 counter
399 );
400
401 let secret_key_1 = SecretKey::from_seed(&seed, keyset_id_1, counter).unwrap();
402 let secret_key_2 = SecretKey::from_seed(&seed, keyset_id_2, counter).unwrap();
403 assert_ne!(
404 secret_key_1, secret_key_2,
405 "Different keyset IDs should produce different secret keys for counter {}",
406 counter
407 );
408 }
409 }
410
411 #[test]
412 fn test_v2_derivation_incremental_counters() {
413 let seed =
414 "half depart obvious quality work element tank gorilla view sugar picture humble";
415 let mnemonic = Mnemonic::from_str(seed).unwrap();
416 let seed: [u8; 64] = mnemonic.to_seed("");
417
418 let keyset_id =
419 Id::from_str("01adc013fa9d85171586660abab27579888611659d357bc86bc09cb26eee8bc035")
420 .unwrap();
421
422 let mut secrets = Vec::new();
423 let mut secret_keys = Vec::new();
424
425 for counter in 0..10 {
427 let secret = Secret::from_seed(&seed, keyset_id, counter).unwrap();
428 let secret_key = SecretKey::from_seed(&seed, keyset_id, counter).unwrap();
429
430 assert!(
432 !secrets.contains(&secret),
433 "Duplicate secret found for counter {}",
434 counter
435 );
436 assert!(
437 !secret_keys.contains(&secret_key),
438 "Duplicate secret key found for counter {}",
439 counter
440 );
441
442 secrets.push(secret);
443 secret_keys.push(secret_key);
444 }
445 }
446
447 #[test]
448 fn test_v2_hmac_message_construction() {
449 let seed =
450 "half depart obvious quality work element tank gorilla view sugar picture humble";
451 let mnemonic = Mnemonic::from_str(seed).unwrap();
452 let seed: [u8; 64] = mnemonic.to_seed("");
453
454 let keyset_id =
455 Id::from_str("01adc013fa9d85171586660abab27579888611659d357bc86bc09cb26eee8bc035")
456 .unwrap();
457 let counter: u32 = 42;
458
459 let _expected_prefix = b"Cashu_KDF_HMAC_SHA512";
462 let keyset_bytes = keyset_id.to_bytes();
463 let _counter_bytes = (counter as u64).to_be_bytes();
464
465 assert_eq!(keyset_bytes.len(), 33);
467 assert_eq!(keyset_bytes[0], 0x01);
468
469 let secret = Secret::from_seed(&seed, keyset_id, counter).unwrap();
471 let secret_key = SecretKey::from_seed(&seed, keyset_id, counter).unwrap();
472
473 assert_eq!(secret.to_string().len(), 64); assert_eq!(secret_key.secret_bytes().len(), 32);
476 }
477
478 #[test]
479 fn test_pre_mint_secrets_with_v2_keyset() {
480 let seed =
481 "half depart obvious quality work element tank gorilla view sugar picture humble";
482 let mnemonic = Mnemonic::from_str(seed).unwrap();
483 let seed: [u8; 64] = mnemonic.to_seed("");
484
485 let keyset_id =
486 Id::from_str("01adc013fa9d85171586660abab27579888611659d357bc86bc09cb26eee8bc035")
487 .unwrap();
488 let amount = Amount::from(1000u64);
489 let split_target = SplitTarget::default();
490 let fee_and_amounts = (0, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
491
492 let pre_mint_secrets =
494 PreMintSecrets::from_seed(keyset_id, 0, &seed, amount, &split_target, &fee_and_amounts)
495 .unwrap();
496
497 for (i, pre_mint) in pre_mint_secrets.secrets.iter().enumerate() {
499 let expected_secret = Secret::from_seed(&seed, keyset_id, i as u32).unwrap();
501 assert_eq!(pre_mint.secret, expected_secret);
502
503 assert_eq!(
505 pre_mint.blinded_message.keyset_id.get_version(),
506 super::super::nut02::KeySetVersion::Version01
507 );
508 }
509 }
510
511 #[test]
512 fn test_restore_batch_with_v2_keyset() {
513 let seed =
514 "half depart obvious quality work element tank gorilla view sugar picture humble";
515 let mnemonic = Mnemonic::from_str(seed).unwrap();
516 let seed: [u8; 64] = mnemonic.to_seed("");
517
518 let keyset_id =
519 Id::from_str("01adc013fa9d85171586660abab27579888611659d357bc86bc09cb26eee8bc035")
520 .unwrap();
521
522 let start_count = 5;
523 let end_count = 10;
524
525 let pre_mint_secrets =
527 PreMintSecrets::restore_batch(keyset_id, &seed, start_count, end_count).unwrap();
528
529 assert_eq!(
530 pre_mint_secrets.secrets.len(),
531 (end_count - start_count + 1) as usize
532 );
533
534 for (i, pre_mint) in pre_mint_secrets.secrets.iter().enumerate() {
536 let counter = start_count + i as u32;
537 let expected_secret = Secret::from_seed(&seed, keyset_id, counter).unwrap();
538 assert_eq!(pre_mint.secret, expected_secret);
539 }
540 }
541}