1use bitcoin::bip32::{ChildNumber, DerivationPath, Xpriv};
6use thiserror::Error;
7use tracing::instrument;
8
9use super::nut00::{BlindedMessage, PreMint, PreMintSecrets};
10use super::nut01::SecretKey;
11use super::nut02::Id;
12use crate::amount::SplitTarget;
13use crate::dhke::blind_message;
14use crate::secret::Secret;
15use crate::util::hex;
16use crate::{Amount, SECP256K1};
17
18#[derive(Debug, Error)]
20pub enum Error {
21 #[error(transparent)]
23 DHKE(#[from] crate::dhke::Error),
24 #[error(transparent)]
26 Amount(#[from] crate::amount::Error),
27 #[error(transparent)]
29 NUT00(#[from] crate::nuts::nut00::Error),
30 #[error(transparent)]
32 NUT02(#[from] crate::nuts::nut02::Error),
33 #[error(transparent)]
35 Bip32(#[from] bitcoin::bip32::Error),
36}
37
38impl Secret {
39 pub fn from_xpriv(xpriv: Xpriv, keyset_id: Id, counter: u32) -> Result<Self, Error> {
41 let path = derive_path_from_keyset_id(keyset_id)?
42 .child(ChildNumber::from_hardened_idx(counter)?)
43 .child(ChildNumber::from_normal_idx(0)?);
44 let derived_xpriv = xpriv.derive_priv(&SECP256K1, &path)?;
45
46 Ok(Self::new(hex::encode(
47 derived_xpriv.private_key.secret_bytes(),
48 )))
49 }
50}
51
52impl SecretKey {
53 pub fn from_xpriv(xpriv: Xpriv, keyset_id: Id, counter: u32) -> Result<Self, Error> {
55 let path = derive_path_from_keyset_id(keyset_id)?
56 .child(ChildNumber::from_hardened_idx(counter)?)
57 .child(ChildNumber::from_normal_idx(1)?);
58 let derived_xpriv = xpriv.derive_priv(&SECP256K1, &path)?;
59
60 Ok(Self::from(derived_xpriv.private_key))
61 }
62}
63
64impl PreMintSecrets {
65 #[instrument(skip(xpriv))]
68 pub fn from_xpriv(
69 keyset_id: Id,
70 counter: u32,
71 xpriv: Xpriv,
72 amount: Amount,
73 amount_split_target: &SplitTarget,
74 ) -> Result<Self, Error> {
75 let mut pre_mint_secrets = PreMintSecrets::new(keyset_id);
76
77 let mut counter = counter;
78
79 for amount in amount.split_targeted(amount_split_target)? {
80 let secret = Secret::from_xpriv(xpriv, keyset_id, counter)?;
81 let blinding_factor = SecretKey::from_xpriv(xpriv, keyset_id, counter)?;
82
83 let (blinded, r) = blind_message(&secret.to_bytes(), Some(blinding_factor))?;
84
85 let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
86
87 let pre_mint = PreMint {
88 blinded_message,
89 secret: secret.clone(),
90 r,
91 amount,
92 };
93
94 pre_mint_secrets.secrets.push(pre_mint);
95 counter += 1;
96 }
97
98 Ok(pre_mint_secrets)
99 }
100
101 pub fn from_xpriv_blank(
103 keyset_id: Id,
104 counter: u32,
105 xpriv: Xpriv,
106 amount: Amount,
107 ) -> Result<Self, Error> {
108 if amount <= Amount::ZERO {
109 return Ok(PreMintSecrets::new(keyset_id));
110 }
111 let count = ((u64::from(amount) as f64).log2().ceil() as u64).max(1);
112 let mut pre_mint_secrets = PreMintSecrets::new(keyset_id);
113
114 let mut counter = counter;
115
116 for _ in 0..count {
117 let secret = Secret::from_xpriv(xpriv, keyset_id, counter)?;
118 let blinding_factor = SecretKey::from_xpriv(xpriv, keyset_id, counter)?;
119
120 let (blinded, r) = blind_message(&secret.to_bytes(), Some(blinding_factor))?;
121
122 let amount = Amount::ZERO;
123
124 let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
125
126 let pre_mint = PreMint {
127 blinded_message,
128 secret: secret.clone(),
129 r,
130 amount,
131 };
132
133 pre_mint_secrets.secrets.push(pre_mint);
134 counter += 1;
135 }
136
137 Ok(pre_mint_secrets)
138 }
139
140 pub fn restore_batch(
143 keyset_id: Id,
144 xpriv: Xpriv,
145 start_count: u32,
146 end_count: u32,
147 ) -> Result<Self, Error> {
148 let mut pre_mint_secrets = PreMintSecrets::new(keyset_id);
149
150 for i in start_count..=end_count {
151 let secret = Secret::from_xpriv(xpriv, keyset_id, i)?;
152 let blinding_factor = SecretKey::from_xpriv(xpriv, keyset_id, i)?;
153
154 let (blinded, r) = blind_message(&secret.to_bytes(), Some(blinding_factor))?;
155
156 let blinded_message = BlindedMessage::new(Amount::ZERO, keyset_id, blinded);
157
158 let pre_mint = PreMint {
159 blinded_message,
160 secret: secret.clone(),
161 r,
162 amount: Amount::ZERO,
163 };
164
165 pre_mint_secrets.secrets.push(pre_mint);
166 }
167
168 Ok(pre_mint_secrets)
169 }
170}
171
172fn derive_path_from_keyset_id(id: Id) -> Result<DerivationPath, Error> {
173 let index = u32::from(id);
174
175 let keyset_child_number = ChildNumber::from_hardened_idx(index)?;
176 Ok(DerivationPath::from(vec![
177 ChildNumber::from_hardened_idx(129372)?,
178 ChildNumber::from_hardened_idx(0)?,
179 keyset_child_number,
180 ]))
181}
182
183#[cfg(test)]
184mod tests {
185 use std::str::FromStr;
186
187 use bip39::Mnemonic;
188 use bitcoin::bip32::DerivationPath;
189 use bitcoin::Network;
190
191 use super::*;
192
193 #[test]
194 fn test_secret_from_seed() {
195 let seed =
196 "half depart obvious quality work element tank gorilla view sugar picture humble";
197 let mnemonic = Mnemonic::from_str(seed).unwrap();
198 let seed: [u8; 64] = mnemonic.to_seed("");
199 let xpriv = Xpriv::new_master(Network::Bitcoin, &seed).unwrap();
200 let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
201
202 let test_secrets = [
203 "485875df74771877439ac06339e284c3acfcd9be7abf3bc20b516faeadfe77ae",
204 "8f2b39e8e594a4056eb1e6dbb4b0c38ef13b1b2c751f64f810ec04ee35b77270",
205 "bc628c79accd2364fd31511216a0fab62afd4a18ff77a20deded7b858c9860c8",
206 "59284fd1650ea9fa17db2b3acf59ecd0f2d52ec3261dd4152785813ff27a33bf",
207 "576c23393a8b31cc8da6688d9c9a96394ec74b40fdaf1f693a6bb84284334ea0",
208 ];
209
210 for (i, test_secret) in test_secrets.iter().enumerate() {
211 let secret = Secret::from_xpriv(xpriv, keyset_id, i.try_into().unwrap()).unwrap();
212 assert_eq!(secret, Secret::from_str(test_secret).unwrap())
213 }
214 }
215 #[test]
216 fn test_r_from_seed() {
217 let seed =
218 "half depart obvious quality work element tank gorilla view sugar picture humble";
219 let mnemonic = Mnemonic::from_str(seed).unwrap();
220 let seed: [u8; 64] = mnemonic.to_seed("");
221 let xpriv = Xpriv::new_master(Network::Bitcoin, &seed).unwrap();
222 let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
223
224 let test_rs = [
225 "ad00d431add9c673e843d4c2bf9a778a5f402b985b8da2d5550bf39cda41d679",
226 "967d5232515e10b81ff226ecf5a9e2e2aff92d66ebc3edf0987eb56357fd6248",
227 "b20f47bb6ae083659f3aa986bfa0435c55c6d93f687d51a01f26862d9b9a4899",
228 "fb5fca398eb0b1deb955a2988b5ac77d32956155f1c002a373535211a2dfdc29",
229 "5f09bfbfe27c439a597719321e061e2e40aad4a36768bb2bcc3de547c9644bf9",
230 ];
231
232 for (i, test_r) in test_rs.iter().enumerate() {
233 let r = SecretKey::from_xpriv(xpriv, keyset_id, i.try_into().unwrap()).unwrap();
234 assert_eq!(r, SecretKey::from_hex(test_r).unwrap())
235 }
236 }
237
238 #[test]
239 fn test_derive_path_from_keyset_id() {
240 let test_cases = [
241 ("009a1f293253e41e", "m/129372'/0'/864559728'"),
242 ("0000000000000000", "m/129372'/0'/0'"),
243 ("00ffffffffffffff", "m/129372'/0'/33554431'"),
244 ];
245
246 for (id_hex, expected_path) in test_cases {
247 let id = Id::from_str(id_hex).unwrap();
248 let path = derive_path_from_keyset_id(id).unwrap();
249 assert_eq!(
250 DerivationPath::from_str(expected_path).unwrap(),
251 path,
252 "Path derivation failed for ID {id_hex}"
253 );
254 }
255 }
256}