nut13.rs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  1. //! NUT-13: Deterministic Secrets
  2. //!
  3. //! <https://github.com/cashubtc/nuts/blob/main/13.md>
  4. use bitcoin::bip32::{ChildNumber, DerivationPath, Xpriv};
  5. use bitcoin::secp256k1::hashes::{hmac, sha256, Hash, HashEngine, HmacEngine};
  6. use bitcoin::{secp256k1, Network};
  7. use thiserror::Error;
  8. use tracing::instrument;
  9. use super::nut00::{BlindedMessage, PreMint, PreMintSecrets};
  10. use super::nut01::SecretKey;
  11. use super::nut02::Id;
  12. use crate::amount::SplitTarget;
  13. use crate::dhke::blind_message;
  14. use crate::secret::Secret;
  15. use crate::util::hex;
  16. use crate::{Amount, SECP256K1};
  17. /// NUT13 Error
  18. #[derive(Debug, Error)]
  19. pub enum Error {
  20. /// DHKE error
  21. #[error(transparent)]
  22. DHKE(#[from] crate::dhke::Error),
  23. /// Amount Error
  24. #[error(transparent)]
  25. Amount(#[from] crate::amount::Error),
  26. /// NUT00 Error
  27. #[error(transparent)]
  28. NUT00(#[from] crate::nuts::nut00::Error),
  29. /// NUT02 Error
  30. #[error(transparent)]
  31. NUT02(#[from] crate::nuts::nut02::Error),
  32. /// Bip32 Error
  33. #[error(transparent)]
  34. Bip32(#[from] bitcoin::bip32::Error),
  35. /// HMAC Error
  36. #[error(transparent)]
  37. Hmac(#[from] bitcoin::secp256k1::hashes::FromSliceError),
  38. /// SecretKey Error
  39. #[error(transparent)]
  40. SecpError(#[from] bitcoin::secp256k1::Error),
  41. }
  42. impl Secret {
  43. /// Create new [`Secret`] from seed
  44. pub fn from_seed(seed: &[u8; 64], keyset_id: Id, counter: u32) -> Result<Self, Error> {
  45. match keyset_id.get_version() {
  46. super::nut02::KeySetVersion::Version00 => Self::legacy_derive(seed, keyset_id, counter),
  47. super::nut02::KeySetVersion::Version01 => Self::derive(seed, keyset_id, counter),
  48. }
  49. }
  50. fn legacy_derive(seed: &[u8; 64], keyset_id: Id, counter: u32) -> Result<Self, Error> {
  51. let xpriv = Xpriv::new_master(Network::Bitcoin, seed)?;
  52. let path = derive_path_from_keyset_id(keyset_id)?
  53. .child(ChildNumber::from_hardened_idx(counter)?)
  54. .child(ChildNumber::from_normal_idx(0)?);
  55. let derived_xpriv = xpriv.derive_priv(&SECP256K1, &path)?;
  56. Ok(Self::new(hex::encode(
  57. derived_xpriv.private_key.secret_bytes(),
  58. )))
  59. }
  60. fn derive(seed: &[u8; 64], keyset_id: Id, counter: u32) -> Result<Self, Error> {
  61. let mut message = Vec::new();
  62. message.extend_from_slice(b"Cashu_KDF_HMAC_SHA256");
  63. message.extend_from_slice(&keyset_id.to_bytes());
  64. message.extend_from_slice(&(counter as u64).to_be_bytes());
  65. message.extend_from_slice(b"\x00");
  66. let mut engine = HmacEngine::<sha256::Hash>::new(seed);
  67. engine.input(&message);
  68. let hmac_result = hmac::Hmac::<sha256::Hash>::from_engine(engine);
  69. let result_bytes = hmac_result.to_byte_array();
  70. Ok(Self::new(hex::encode(&result_bytes[..32])))
  71. }
  72. }
  73. impl SecretKey {
  74. /// Create new [`SecretKey`] from seed
  75. pub fn from_seed(seed: &[u8; 64], keyset_id: Id, counter: u32) -> Result<Self, Error> {
  76. match keyset_id.get_version() {
  77. super::nut02::KeySetVersion::Version00 => Self::legacy_derive(seed, keyset_id, counter),
  78. super::nut02::KeySetVersion::Version01 => Self::derive(seed, keyset_id, counter),
  79. }
  80. }
  81. fn legacy_derive(seed: &[u8; 64], keyset_id: Id, counter: u32) -> Result<Self, Error> {
  82. let xpriv = Xpriv::new_master(Network::Bitcoin, seed)?;
  83. let path = derive_path_from_keyset_id(keyset_id)?
  84. .child(ChildNumber::from_hardened_idx(counter)?)
  85. .child(ChildNumber::from_normal_idx(1)?);
  86. let derived_xpriv = xpriv.derive_priv(&SECP256K1, &path)?;
  87. Ok(Self::from(derived_xpriv.private_key))
  88. }
  89. fn derive(seed: &[u8; 64], keyset_id: Id, counter: u32) -> Result<Self, Error> {
  90. let mut message = Vec::new();
  91. message.extend_from_slice(b"Cashu_KDF_HMAC_SHA256");
  92. message.extend_from_slice(&keyset_id.to_bytes());
  93. message.extend_from_slice(&(counter as u64).to_be_bytes());
  94. message.extend_from_slice(b"\x01");
  95. let mut engine = HmacEngine::<sha256::Hash>::new(seed);
  96. engine.input(&message);
  97. let hmac_result = hmac::Hmac::<sha256::Hash>::from_engine(engine);
  98. let result_bytes = hmac_result.to_byte_array();
  99. Ok(Self::from(secp256k1::SecretKey::from_slice(
  100. &result_bytes[..32],
  101. )?))
  102. }
  103. }
  104. impl PreMintSecrets {
  105. /// Generate blinded messages from predetermined secrets and blindings
  106. /// factor
  107. #[instrument(skip(seed))]
  108. pub fn from_seed(
  109. keyset_id: Id,
  110. counter: u32,
  111. seed: &[u8; 64],
  112. amount: Amount,
  113. amount_split_target: &SplitTarget,
  114. amounts_ppk: &[u64],
  115. ) -> Result<Self, Error> {
  116. let mut pre_mint_secrets = PreMintSecrets::new(keyset_id);
  117. let mut counter = counter;
  118. for amount in amount.split_targeted(amount_split_target, amounts_ppk)? {
  119. let secret = Secret::from_seed(seed, keyset_id, counter)?;
  120. let blinding_factor = SecretKey::from_seed(seed, keyset_id, counter)?;
  121. let (blinded, r) = blind_message(&secret.to_bytes(), Some(blinding_factor))?;
  122. let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
  123. let pre_mint = PreMint {
  124. blinded_message,
  125. secret: secret.clone(),
  126. r,
  127. amount,
  128. };
  129. pre_mint_secrets.secrets.push(pre_mint);
  130. counter += 1;
  131. }
  132. Ok(pre_mint_secrets)
  133. }
  134. /// New [`PreMintSecrets`] from seed with a zero amount used for change
  135. pub fn from_seed_blank(
  136. keyset_id: Id,
  137. counter: u32,
  138. seed: &[u8; 64],
  139. amount: Amount,
  140. ) -> Result<Self, Error> {
  141. if amount <= Amount::ZERO {
  142. return Ok(PreMintSecrets::new(keyset_id));
  143. }
  144. let count = ((u64::from(amount) as f64).log2().ceil() as u64).max(1);
  145. let mut pre_mint_secrets = PreMintSecrets::new(keyset_id);
  146. let mut counter = counter;
  147. for _ in 0..count {
  148. let secret = Secret::from_seed(seed, keyset_id, counter)?;
  149. let blinding_factor = SecretKey::from_seed(seed, keyset_id, counter)?;
  150. let (blinded, r) = blind_message(&secret.to_bytes(), Some(blinding_factor))?;
  151. let amount = Amount::ZERO;
  152. let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
  153. let pre_mint = PreMint {
  154. blinded_message,
  155. secret: secret.clone(),
  156. r,
  157. amount,
  158. };
  159. pre_mint_secrets.secrets.push(pre_mint);
  160. counter += 1;
  161. }
  162. Ok(pre_mint_secrets)
  163. }
  164. /// Generate blinded messages from predetermined secrets and blindings
  165. /// factor
  166. pub fn restore_batch(
  167. keyset_id: Id,
  168. seed: &[u8; 64],
  169. start_count: u32,
  170. end_count: u32,
  171. ) -> Result<Self, Error> {
  172. let mut pre_mint_secrets = PreMintSecrets::new(keyset_id);
  173. for i in start_count..=end_count {
  174. let secret = Secret::from_seed(seed, keyset_id, i)?;
  175. let blinding_factor = SecretKey::from_seed(seed, keyset_id, i)?;
  176. let (blinded, r) = blind_message(&secret.to_bytes(), Some(blinding_factor))?;
  177. let blinded_message = BlindedMessage::new(Amount::ZERO, keyset_id, blinded);
  178. let pre_mint = PreMint {
  179. blinded_message,
  180. secret: secret.clone(),
  181. r,
  182. amount: Amount::ZERO,
  183. };
  184. pre_mint_secrets.secrets.push(pre_mint);
  185. }
  186. Ok(pre_mint_secrets)
  187. }
  188. }
  189. fn derive_path_from_keyset_id(id: Id) -> Result<DerivationPath, Error> {
  190. let index = u32::from(id);
  191. let keyset_child_number = ChildNumber::from_hardened_idx(index)?;
  192. Ok(DerivationPath::from(vec![
  193. ChildNumber::from_hardened_idx(129372)?,
  194. ChildNumber::from_hardened_idx(0)?,
  195. keyset_child_number,
  196. ]))
  197. }
  198. #[cfg(test)]
  199. mod tests {
  200. use std::str::FromStr;
  201. use bip39::Mnemonic;
  202. use bitcoin::bip32::DerivationPath;
  203. use super::*;
  204. #[test]
  205. fn test_secret_from_seed() {
  206. let seed =
  207. "half depart obvious quality work element tank gorilla view sugar picture humble";
  208. let mnemonic = Mnemonic::from_str(seed).unwrap();
  209. let seed: [u8; 64] = mnemonic.to_seed("");
  210. let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
  211. let test_secrets = [
  212. "485875df74771877439ac06339e284c3acfcd9be7abf3bc20b516faeadfe77ae",
  213. "8f2b39e8e594a4056eb1e6dbb4b0c38ef13b1b2c751f64f810ec04ee35b77270",
  214. "bc628c79accd2364fd31511216a0fab62afd4a18ff77a20deded7b858c9860c8",
  215. "59284fd1650ea9fa17db2b3acf59ecd0f2d52ec3261dd4152785813ff27a33bf",
  216. "576c23393a8b31cc8da6688d9c9a96394ec74b40fdaf1f693a6bb84284334ea0",
  217. ];
  218. for (i, test_secret) in test_secrets.iter().enumerate() {
  219. let secret = Secret::from_seed(&seed, keyset_id, i.try_into().unwrap()).unwrap();
  220. assert_eq!(secret, Secret::from_str(test_secret).unwrap())
  221. }
  222. }
  223. #[test]
  224. fn test_r_from_seed() {
  225. let seed =
  226. "half depart obvious quality work element tank gorilla view sugar picture humble";
  227. let mnemonic = Mnemonic::from_str(seed).unwrap();
  228. let seed: [u8; 64] = mnemonic.to_seed("");
  229. let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
  230. let test_rs = [
  231. "ad00d431add9c673e843d4c2bf9a778a5f402b985b8da2d5550bf39cda41d679",
  232. "967d5232515e10b81ff226ecf5a9e2e2aff92d66ebc3edf0987eb56357fd6248",
  233. "b20f47bb6ae083659f3aa986bfa0435c55c6d93f687d51a01f26862d9b9a4899",
  234. "fb5fca398eb0b1deb955a2988b5ac77d32956155f1c002a373535211a2dfdc29",
  235. "5f09bfbfe27c439a597719321e061e2e40aad4a36768bb2bcc3de547c9644bf9",
  236. ];
  237. for (i, test_r) in test_rs.iter().enumerate() {
  238. let r = SecretKey::from_seed(&seed, keyset_id, i.try_into().unwrap()).unwrap();
  239. assert_eq!(r, SecretKey::from_hex(test_r).unwrap())
  240. }
  241. }
  242. #[test]
  243. fn test_derive_path_from_keyset_id() {
  244. let test_cases = [
  245. ("009a1f293253e41e", "m/129372'/0'/864559728'"),
  246. ("0000000000000000", "m/129372'/0'/0'"),
  247. ("00ffffffffffffff", "m/129372'/0'/33554431'"),
  248. ];
  249. for (id_hex, expected_path) in test_cases {
  250. let id = Id::from_str(id_hex).unwrap();
  251. let path = derive_path_from_keyset_id(id).unwrap();
  252. assert_eq!(
  253. DerivationPath::from_str(expected_path).unwrap(),
  254. path,
  255. "Path derivation failed for ID {id_hex}"
  256. );
  257. }
  258. }
  259. #[test]
  260. fn test_secret_derivation_keyset_v2() {
  261. let seed =
  262. "half depart obvious quality work element tank gorilla view sugar picture humble";
  263. let mnemonic = Mnemonic::from_str(seed).unwrap();
  264. let seed: [u8; 64] = mnemonic.to_seed("");
  265. // Test with a v2 keyset ID (33 bytes, starting with "01")
  266. let keyset_id =
  267. Id::from_str("012e23479a0029432eaad0d2040c09be53bab592d5cbf1d55e0dd26c9495951b30")
  268. .unwrap();
  269. // Expected secrets derived using the new derivation
  270. let test_secrets = [
  271. "ba250bf927b1df5dd0a07c543be783a4349a7f99904acd3406548402d3484118",
  272. "3a6423fe56abd5e74ec9d22a91ee110cd2ce45a7039901439d62e5534d3438c1",
  273. "843484a75b78850096fac5b513e62854f11d57491cf775a6fd2edf4e583ae8c0",
  274. "3600608d5cf8197374f060cfbcff134d2cd1fb57eea68cbcf2fa6917c58911b6",
  275. "717fce9cc6f9ea060d20dd4e0230af4d63f3894cc49dd062fd99d033ea1ac1dd",
  276. ];
  277. for (i, test_secret) in test_secrets.iter().enumerate() {
  278. let secret = Secret::from_seed(&seed, keyset_id, i.try_into().unwrap()).unwrap();
  279. // Note: The actual expected values would need to be computed from a reference implementation
  280. // For now, we just verify the derivation works and produces consistent results
  281. assert_eq!(secret.to_string().len(), 64); // Should be 32 bytes = 64 hex chars
  282. // Test deterministic derivation: same inputs should produce same outputs
  283. let secret2 = Secret::from_str(test_secret).unwrap();
  284. assert_eq!(secret, secret2);
  285. }
  286. }
  287. #[test]
  288. fn test_secret_key_derivation_keyset_v2() {
  289. let seed =
  290. "half depart obvious quality work element tank gorilla view sugar picture humble";
  291. let mnemonic = Mnemonic::from_str(seed).unwrap();
  292. let seed: [u8; 64] = mnemonic.to_seed("");
  293. // Test with a v2 keyset ID (33 bytes, starting with "01")
  294. let keyset_id =
  295. Id::from_str("012e23479a0029432eaad0d2040c09be53bab592d5cbf1d55e0dd26c9495951b30")
  296. .unwrap();
  297. let test_secret_keys = [
  298. "4f8b32a54aed811b692a665ed296b4c1fc2f37a8be4006379e95063a76693745",
  299. "c4b8412ee644067007423480c9e556385b71ffdff0f340bc16a95c0534fe0e01",
  300. "ceff40983441c40acaf77d2a8ddffd5c1c84391fb9fd0dc4607c186daab1c829",
  301. "41ad26b840fb62d29b2318a82f1d9cd40dc0f1e58183cc57562f360a32fdfad6",
  302. "fb986a9c76758593b0e2d1a5172ade977c858d87111a220e16c292a9347abf81",
  303. ];
  304. for (i, test_secret) in test_secret_keys.iter().enumerate() {
  305. let secret_key = SecretKey::from_seed(&seed, keyset_id, i as u32).unwrap();
  306. // Verify the secret key is valid (32 bytes)
  307. let secret_bytes = secret_key.secret_bytes();
  308. assert_eq!(secret_bytes.len(), 32);
  309. // Test deterministic derivation
  310. let secret_key2 = SecretKey::from_str(test_secret).unwrap();
  311. assert_eq!(secret_key, secret_key2);
  312. }
  313. }
  314. #[test]
  315. fn test_v2_derivation_with_different_keysets() {
  316. let seed =
  317. "half depart obvious quality work element tank gorilla view sugar picture humble";
  318. let mnemonic = Mnemonic::from_str(seed).unwrap();
  319. let seed: [u8; 64] = mnemonic.to_seed("");
  320. let keyset_id_1 =
  321. Id::from_str("01adc013fa9d85171586660abab27579888611659d357bc86bc09cb26eee8bc035")
  322. .unwrap();
  323. let keyset_id_2 =
  324. Id::from_str("01bef024fb9e85171586660abab27579888611659d357bc86bc09cb26eee8bc046")
  325. .unwrap();
  326. // Different keyset IDs should produce different secrets even with same counter
  327. for counter in 0..3 {
  328. let secret_1 = Secret::from_seed(&seed, keyset_id_1, counter).unwrap();
  329. let secret_2 = Secret::from_seed(&seed, keyset_id_2, counter).unwrap();
  330. assert_ne!(
  331. secret_1, secret_2,
  332. "Different keyset IDs should produce different secrets for counter {}",
  333. counter
  334. );
  335. let secret_key_1 = SecretKey::from_seed(&seed, keyset_id_1, counter).unwrap();
  336. let secret_key_2 = SecretKey::from_seed(&seed, keyset_id_2, counter).unwrap();
  337. assert_ne!(
  338. secret_key_1, secret_key_2,
  339. "Different keyset IDs should produce different secret keys for counter {}",
  340. counter
  341. );
  342. }
  343. }
  344. #[test]
  345. fn test_v2_derivation_incremental_counters() {
  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. let keyset_id =
  351. Id::from_str("01adc013fa9d85171586660abab27579888611659d357bc86bc09cb26eee8bc035")
  352. .unwrap();
  353. let mut secrets = Vec::new();
  354. let mut secret_keys = Vec::new();
  355. // Generate secrets with incremental counters
  356. for counter in 0..10 {
  357. let secret = Secret::from_seed(&seed, keyset_id, counter).unwrap();
  358. let secret_key = SecretKey::from_seed(&seed, keyset_id, counter).unwrap();
  359. // Ensure no duplicates
  360. assert!(
  361. !secrets.contains(&secret),
  362. "Duplicate secret found for counter {}",
  363. counter
  364. );
  365. assert!(
  366. !secret_keys.contains(&secret_key),
  367. "Duplicate secret key found for counter {}",
  368. counter
  369. );
  370. secrets.push(secret);
  371. secret_keys.push(secret_key);
  372. }
  373. }
  374. #[test]
  375. fn test_v2_hmac_message_construction() {
  376. let seed =
  377. "half depart obvious quality work element tank gorilla view sugar picture humble";
  378. let mnemonic = Mnemonic::from_str(seed).unwrap();
  379. let seed: [u8; 64] = mnemonic.to_seed("");
  380. let keyset_id =
  381. Id::from_str("01adc013fa9d85171586660abab27579888611659d357bc86bc09cb26eee8bc035")
  382. .unwrap();
  383. let counter: u32 = 42;
  384. // Test that the HMAC message is constructed correctly
  385. // Message should be: b"Cashu_KDF_HMAC_SHA512" + keyset_id.to_bytes() + counter.to_be_bytes()
  386. let _expected_prefix = b"Cashu_KDF_HMAC_SHA512";
  387. let keyset_bytes = keyset_id.to_bytes();
  388. let _counter_bytes = (counter as u64).to_be_bytes();
  389. // Verify keyset ID v2 structure: version byte (01) + 32 bytes
  390. assert_eq!(keyset_bytes.len(), 33);
  391. assert_eq!(keyset_bytes[0], 0x01);
  392. // The actual HMAC construction is internal, but we can verify the derivation works
  393. let secret = Secret::from_seed(&seed, keyset_id, counter).unwrap();
  394. let secret_key = SecretKey::from_seed(&seed, keyset_id, counter).unwrap();
  395. // Verify outputs are valid hex strings of correct length
  396. assert_eq!(secret.to_string().len(), 64); // 32 bytes as hex
  397. assert_eq!(secret_key.secret_bytes().len(), 32);
  398. }
  399. #[test]
  400. fn test_pre_mint_secrets_with_v2_keyset() {
  401. let seed =
  402. "half depart obvious quality work element tank gorilla view sugar picture humble";
  403. let mnemonic = Mnemonic::from_str(seed).unwrap();
  404. let seed: [u8; 64] = mnemonic.to_seed("");
  405. let keyset_id =
  406. Id::from_str("01adc013fa9d85171586660abab27579888611659d357bc86bc09cb26eee8bc035")
  407. .unwrap();
  408. let amount = Amount::from(1000u64);
  409. let split_target = SplitTarget::default();
  410. let amounts_ppk = (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>();
  411. // Test PreMintSecrets generation with v2 keyset
  412. let pre_mint_secrets =
  413. PreMintSecrets::from_seed(keyset_id, 0, &seed, amount, &split_target, &amounts_ppk)
  414. .unwrap();
  415. // Verify all secrets in the pre_mint use the new v2 derivation
  416. for (i, pre_mint) in pre_mint_secrets.secrets.iter().enumerate() {
  417. // Verify the secret was derived correctly
  418. let expected_secret = Secret::from_seed(&seed, keyset_id, i as u32).unwrap();
  419. assert_eq!(pre_mint.secret, expected_secret);
  420. // Verify keyset ID version
  421. assert_eq!(
  422. pre_mint.blinded_message.keyset_id.get_version(),
  423. super::super::nut02::KeySetVersion::Version01
  424. );
  425. }
  426. }
  427. #[test]
  428. fn test_restore_batch_with_v2_keyset() {
  429. let seed =
  430. "half depart obvious quality work element tank gorilla view sugar picture humble";
  431. let mnemonic = Mnemonic::from_str(seed).unwrap();
  432. let seed: [u8; 64] = mnemonic.to_seed("");
  433. let keyset_id =
  434. Id::from_str("01adc013fa9d85171586660abab27579888611659d357bc86bc09cb26eee8bc035")
  435. .unwrap();
  436. let start_count = 5;
  437. let end_count = 10;
  438. // Test batch restoration with v2 keyset
  439. let pre_mint_secrets =
  440. PreMintSecrets::restore_batch(keyset_id, &seed, start_count, end_count).unwrap();
  441. assert_eq!(
  442. pre_mint_secrets.secrets.len(),
  443. (end_count - start_count + 1) as usize
  444. );
  445. // Verify each secret in the batch
  446. for (i, pre_mint) in pre_mint_secrets.secrets.iter().enumerate() {
  447. let counter = start_count + i as u32;
  448. let expected_secret = Secret::from_seed(&seed, keyset_id, counter).unwrap();
  449. assert_eq!(pre_mint.secret, expected_secret);
  450. }
  451. }
  452. }