1#![deny(unsafe_code)]
111#![deny(non_upper_case_globals)]
112#![deny(non_camel_case_types)]
113#![deny(non_snake_case)]
114#![deny(unused_mut)]
115#![deny(dead_code)]
116#![deny(unused_imports)]
117#![deny(missing_docs)]
118
119#[cfg(not(any(feature = "std")))]
120compile_error!("`std` must be enabled");
121
122pub use bitcoin;
123pub use miniscript;
124
125mod payload;
126mod template;
127
128use anyhow::{Result, anyhow};
129use bitcoin::bip32::DerivationPath;
130use miniscript::{Descriptor, DescriptorPublicKey};
131use sha2::{Digest, Sha256};
132
133use crate::payload::ToDescriptorTree;
134
135const V0: u8 = 0;
136const V1: u8 = 1;
137
138pub fn encrypt(desc: Descriptor<DescriptorPublicKey>) -> Result<Vec<u8>> {
141 encrypt_with_version(V0, desc)
142}
143
144pub fn encrypt_with_full_secrecy(desc: Descriptor<DescriptorPublicKey>) -> Result<Vec<u8>> {
152 encrypt_with_version(V1, desc)
153}
154
155fn encrypt_with_version(version: u8, desc: Descriptor<DescriptorPublicKey>) -> Result<Vec<u8>> {
156 let (template, payload) = template::encode(desc.clone());
157
158 let mut hasher = Sha256::new();
160 hasher.update(&template);
161 hasher.update(&payload);
162 let encryption_key = hasher.finalize();
163
164 let nonce = [0u8; 12];
166 let (encrypted_shares, encrypted_payload) = match version {
167 V0 => {
168 payload::encrypt_with_authenticated_shards(desc, encryption_key.into(), nonce, payload)?
169 }
170 V1 => payload::encrypt_with_full_secrecy(desc, encryption_key.into(), nonce, payload)?,
171 _ => return Err(anyhow!("Unsupported version: {}", version)),
172 };
173
174 Ok([
175 vec![version],
176 template,
177 encrypted_shares.concat(),
178 encrypted_payload,
179 ]
180 .concat())
181}
182
183pub fn decrypt(
185 data: &[u8],
186 pks: Vec<DescriptorPublicKey>,
187) -> Result<Descriptor<DescriptorPublicKey>> {
188 if data.is_empty() {
189 return Err(anyhow!("Empty data"));
190 }
191
192 let version = data[0];
193 let (data, share_size) = match version {
194 V0 => (&data[1..], 48_usize),
195 V1 => (&data[1..], 32_usize),
196 _ => return Err(anyhow!("Unsupported version: {}", version)),
197 };
198
199 let (template, size) = template::decode(data)?;
200
201 let num_keys = template.clone().to_tree().extract_keys().len();
202
203 if size + num_keys * 48 > data.len() {
204 return Err(anyhow!("Missing bytes"));
205 }
206
207 let encrypted_shares: Vec<Vec<u8>> = data[size..size + num_keys * share_size]
208 .chunks_exact(share_size)
209 .map(|chunk| chunk.to_vec())
210 .collect();
211
212 let encrypted_payload = &data[size + num_keys * share_size..];
213
214 let nonce = [0u8; 12];
215 let payload = match version {
216 V0 => payload::decrypt_with_authenticated_shards(
217 template.clone(),
218 encrypted_shares,
219 pks,
220 nonce,
221 encrypted_payload.to_vec(),
222 )?,
223 V1 => payload::decrypt_with_full_secrecy(
224 template.clone(),
225 encrypted_shares,
226 pks,
227 nonce,
228 encrypted_payload.to_vec(),
229 )?,
230 _ => unreachable!("unsupported version"),
231 };
232
233 let desc = template::decode_with_payload(data, &payload)?;
234
235 Ok(desc)
236}
237
238pub fn get_template(data: &[u8]) -> Result<Descriptor<DescriptorPublicKey>> {
240 if data.is_empty() {
241 return Err(anyhow!("Empty data"));
242 }
243
244 let data = match data[0] {
245 V0 => &data[1..],
246 _ => return Err(anyhow!("Unsupported version: {}", data[0])),
247 };
248
249 let (template, _) = template::decode(data)?;
250
251 Ok(template)
252}
253
254pub fn get_origin_derivation_paths(data: &[u8]) -> Result<Vec<DerivationPath>> {
256 if data.is_empty() {
257 return Err(anyhow!("Empty data"));
258 }
259
260 let data = match data[0] {
261 V0 => &data[1..],
262 _ => return Err(anyhow!("Unsupported version: {}", data[0])),
263 };
264
265 let (template, _) = template::decode(data)?;
266
267 let mut paths = Vec::new();
268 for key in template.clone().to_tree().extract_keys() {
269 let origin = match key {
270 DescriptorPublicKey::XPub(xpub) => xpub.origin,
271 DescriptorPublicKey::MultiXPub(xpub) => xpub.origin,
272 DescriptorPublicKey::Single(single) => single.origin,
273 };
274
275 if let Some((_, path)) = origin {
276 paths.push(path);
277 }
278 }
279
280 Ok(paths)
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286 use crate::payload::ToDescriptorTree;
287 use miniscript::{Descriptor, DescriptorPublicKey};
288 use std::str::FromStr;
289
290 #[test]
291 fn test_integration() {
292 let descriptors = vec![
293 "sh(sortedmulti(2,[2c49202a/45h/0h/0h/0]xpub6EigxozzGaNVWUwEFnbyX6oHPdpWTKgJgbfpRbAcdiGpGMrdpPinCoHBXehu35sqJHpgLDTxigAnFQG3opKjXQoSmGMrMNHz81ALZSBRCWw/0/*,[55b43a50/45h/0h/0h/0]xpub6EAtA5XJ6pwFQ7L32iAJMgiWQEcrwU75NNWQ6H6eavwznDFeGFzTbSFdDKNdbG2HQdZvzrXuCyEYSSJ4cGsmfoPkKUKQ6haNKMRqG4pD4xi/0/*,[35931b5e/0/0/0/0]xpub6EDykLBC5EfaDNC7Mpg2H8veCaJHDgxH2JQvRtxJrbyeAhXWV2jJzB9XL4jMiFN5TzQefYi4V4nDiH4bxhkrweQ3Smxc8uP4ux9HrMGV81P/0/*))#eqwew7sv",
294 "wsh(sortedmulti(2,[3abf21c8/48h/0h/0h/2h]xpub6DYotmPf2kXFYhJMFDpfydjiXG1RzmH1V7Fnn2Z38DgN2oSYruczMyTFZZPz6yXq47Re8anhXWGj4yMzPTA3bjPDdpA96TLUbMehrH3sBna/<0;1>/*,[a1a4bd46/48h/0h/0h/2h]xpub6DvXYo8BwnRACos42ME7tNL48JQhLMQ33ENfniLM9KZmeZGbBhyh1Jkfo3hUKmmjW92o3r7BprTPPdrTr4QLQR7aRnSBfz1UFMceW5ibhTc/<0;1>/*,[ed91913d/48h/0h/0h/2h]xpub6EQUho4Z4pwh2UQGdPjoPrbtjd6qqseKZCEBLcZbJ7y6c9XBWHRkhERiADJfwRcUs14nQsxF3hvx7aFkbk3tfp4dnKfkcns217kBTVVN5gY/<0;1>/*))#hpcyqx44",
295 "sh(wsh(sortedmulti(2,[2c49202a/45h/0h/0h/0]xpub6EigxozzGaNVWUwEFnbyX6oHPdpWTKgJgbfpRbAcdiGpGMrdpPinCoHBXehu35sqJHpgLDTxigAnFQG3opKjXQoSmGMrMNHz81ALZSBRCWw/0/*,[55b43a50/45h/0h/0h/0]xpub6EAtA5XJ6pwFQ7L32iAJMgiWQEcrwU75NNWQ6H6eavwznDFeGFzTbSFdDKNdbG2HQdZvzrXuCyEYSSJ4cGsmfoPkKUKQ6haNKMRqG4pD4xi/0/*,[35931b5e/0/0/0/0]xpub6EDykLBC5EfaDNC7Mpg2H8veCaJHDgxH2JQvRtxJrbyeAhXWV2jJzB9XL4jMiFN5TzQefYi4V4nDiH4bxhkrweQ3Smxc8uP4ux9HrMGV81P/0/*)))#xsfvldas",
296 "wsh(multi(2,02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556,023e9be8b82c7469c88b1912a61611dffb9f65bbf5a176952727e0046513eca0de))",
297 "wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)",
298 "sh(wsh(or_d(pk(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556),and_v(v:pk(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e),older(1000)))))",
299 "wsh(thresh(4,pk([7258e4f9/44h/1h/0h]tpubDCZrkQoEU3845aFKUu9VQBYWZtrTwxMzcxnBwKFCYXHD6gEXvtFcxddCCLFsEwmxQaG15izcHxj48SXg1QS5FQGMBx5Ak6deXKPAL7wauBU/0/*),s:pk([c80b1469/44h/1h/0h]tpubDD3UwwHoNUF4F3Vi5PiUVTc3ji1uThuRfFyBexTSHoAcHuWW2z8qEE2YujegcLtgthr3wMp3ZauvNG9eT9xfJyxXCfNty8h6rDBYU8UU1qq/0/*),s:pk([4e5024fe/44h/1h/0h]tpubDDLrpPymPLSCJyCMLQdmcWxrAWwsqqssm5NdxT2WSdEBPSXNXxwbeKtsHAyXPpLkhUyKovtZgCi47QxVpw9iVkg95UUgeevyAqtJ9dqBqa1/0/*),s:pk([3b1d1ee9/44h/1h/0h]tpubDCmDTANBWPzf6d8Ap1J5Ku7J1Ay92MpHMrEV7M5muWxCrTBN1g5f1NPcjMEL6dJHxbvEKNZtYCdowaSTN81DAyLsmv6w6xjJHCQNkxrsrfu/0/*),sln:after(840000),sln:after(1050000),sln:after(1260000)))#k28080kv",
300 "tr(c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5,{pk(fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556),pk(e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)})",
301 ];
302
303 for desc_str in descriptors {
304 let desc = Descriptor::<DescriptorPublicKey>::from_str(desc_str).unwrap();
305
306 let keys = desc.clone().to_tree().extract_keys();
307 let ciphertext = encrypt(desc.clone()).unwrap();
308 assert_eq!(desc, decrypt(&ciphertext, keys.clone()).unwrap());
309
310 let ciphertext = encrypt_with_full_secrecy(desc.clone()).unwrap();
311 assert_eq!(desc, decrypt(&ciphertext, keys).unwrap());
312 }
313 }
314
315 #[test]
316 fn test_unsupported_version() {
317 let desc_str = "wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)";
318 let desc = Descriptor::<DescriptorPublicKey>::from_str(desc_str).unwrap();
319
320 let mut encrypted_data = encrypt(desc.clone()).unwrap();
322 encrypted_data[0] = 0xFF;
323
324 let template_result = get_template(&encrypted_data);
325 assert!(
326 template_result
327 .unwrap_err()
328 .to_string()
329 .contains("Unsupported version: 255")
330 );
331
332 let paths_result = get_origin_derivation_paths(&encrypted_data);
333 assert!(
334 paths_result
335 .unwrap_err()
336 .to_string()
337 .contains("Unsupported version: 255")
338 );
339
340 let key = DescriptorPublicKey::from_str(
341 "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9",
342 )
343 .unwrap();
344
345 let decrypt_result = decrypt(&encrypted_data, vec![key]);
346 assert!(
347 decrypt_result
348 .unwrap_err()
349 .to_string()
350 .contains("Unsupported version: 255")
351 );
352 }
353
354 #[test]
355 fn test_empty() {
356 let empty_data: Vec<u8> = vec![];
357
358 let template_result = get_template(&empty_data);
359 assert!(
360 template_result
361 .unwrap_err()
362 .to_string()
363 .contains("Empty data")
364 );
365
366 let paths_result = get_origin_derivation_paths(&empty_data);
367 assert!(paths_result.unwrap_err().to_string().contains("Empty data"));
368
369 let decrypt_result = decrypt(
370 &empty_data,
371 vec![
372 DescriptorPublicKey::from_str(
373 "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9",
374 )
375 .unwrap(),
376 ],
377 );
378 assert!(
379 decrypt_result
380 .unwrap_err()
381 .to_string()
382 .contains("Empty data")
383 );
384 }
385}