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