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