1use crate::crypto;
19use crate::encoding;
20use crate::error::SignerError;
21
22#[derive(Clone, Debug, PartialEq, Eq)]
26pub enum DescriptorKey {
27 Compressed([u8; 33]),
29 XOnly([u8; 32]),
31}
32
33impl DescriptorKey {
34 pub fn from_hex(hex_str: &str) -> Result<Self, SignerError> {
36 let bytes =
37 hex::decode(hex_str).map_err(|e| SignerError::ParseError(format!("hex: {e}")))?;
38 match bytes.len() {
39 33 => {
40 let mut key = [0u8; 33];
41 key.copy_from_slice(&bytes);
42 Ok(DescriptorKey::Compressed(key))
43 }
44 32 => {
45 let mut key = [0u8; 32];
46 key.copy_from_slice(&bytes);
47 Ok(DescriptorKey::XOnly(key))
48 }
49 _ => Err(SignerError::ParseError(format!(
50 "invalid key length: {}",
51 bytes.len()
52 ))),
53 }
54 }
55
56 pub fn compressed_bytes(&self) -> Option<&[u8; 33]> {
58 match self {
59 DescriptorKey::Compressed(k) => Some(k),
60 DescriptorKey::XOnly(_) => None,
61 }
62 }
63
64 pub fn x_only_bytes(&self) -> Option<&[u8; 32]> {
66 match self {
67 DescriptorKey::XOnly(k) => Some(k),
68 DescriptorKey::Compressed(_) => None,
69 }
70 }
71
72 pub fn hash160(&self) -> Option<[u8; 20]> {
74 match self {
75 DescriptorKey::Compressed(key) => Some(crypto::hash160(key)),
76 DescriptorKey::XOnly(_) => None,
77 }
78 }
79}
80
81#[derive(Clone, Debug)]
85pub enum Descriptor {
86 Pkh(DescriptorKey),
88 Wpkh(DescriptorKey),
90 ShWpkh(DescriptorKey),
92 Tr(DescriptorKey),
94 Raw(Vec<u8>),
96 OpReturn(Vec<u8>),
98}
99
100impl Descriptor {
101 pub fn pkh(key: DescriptorKey) -> Self {
103 Descriptor::Pkh(key)
104 }
105
106 pub fn wpkh(key: DescriptorKey) -> Self {
108 Descriptor::Wpkh(key)
109 }
110
111 pub fn sh_wpkh(key: DescriptorKey) -> Self {
113 Descriptor::ShWpkh(key)
114 }
115
116 pub fn tr(key: DescriptorKey) -> Self {
118 Descriptor::Tr(key)
119 }
120
121 pub fn script_pubkey(&self) -> Result<Vec<u8>, SignerError> {
123 match self {
124 Descriptor::Pkh(key) => {
125 let hash = key.hash160().ok_or(SignerError::ParseError(
126 "pkh requires compressed key".into(),
127 ))?;
128 let mut script = Vec::with_capacity(25);
130 script.push(0x76); script.push(0xa9); script.push(0x14); script.extend_from_slice(&hash);
134 script.push(0x88); script.push(0xac); Ok(script)
137 }
138 Descriptor::Wpkh(key) => {
139 let hash = key.hash160().ok_or(SignerError::ParseError(
140 "wpkh requires compressed key".into(),
141 ))?;
142 let mut script = Vec::with_capacity(22);
144 script.push(0x00); script.push(0x14); script.extend_from_slice(&hash);
147 Ok(script)
148 }
149 Descriptor::ShWpkh(key) => {
150 let hash = key.hash160().ok_or(SignerError::ParseError(
151 "sh(wpkh) requires compressed key".into(),
152 ))?;
153 let mut witness_script = Vec::with_capacity(22);
155 witness_script.push(0x00);
156 witness_script.push(0x14);
157 witness_script.extend_from_slice(&hash);
158 let script_hash = crypto::hash160(&witness_script);
160 let mut script = Vec::with_capacity(23);
161 script.push(0xa9); script.push(0x14); script.extend_from_slice(&script_hash);
164 script.push(0x87); Ok(script)
166 }
167 Descriptor::Tr(key) => {
168 let xonly = match key {
169 DescriptorKey::XOnly(k) => *k,
170 DescriptorKey::Compressed(k) => {
171 let mut xonly = [0u8; 32];
172 xonly.copy_from_slice(&k[1..]);
173 xonly
174 }
175 };
176 let (output_key, _parity) = super::taproot::taproot_tweak(&xonly, None)?;
177 let mut script = Vec::with_capacity(34);
179 script.push(0x51); script.push(0x20); script.extend_from_slice(&output_key);
182 Ok(script)
183 }
184 Descriptor::Raw(script) => Ok(script.clone()),
185 Descriptor::OpReturn(data) => {
186 let mut script = Vec::with_capacity(6 + data.len());
187 script.push(0x6a); if data.len() <= 75 {
189 script.push(data.len() as u8);
190 } else if data.len() <= 0xFF {
191 script.push(0x4c); script.push(data.len() as u8);
193 } else if data.len() <= 0xFFFF {
194 script.push(0x4d); script.extend_from_slice(&(data.len() as u16).to_le_bytes());
196 } else if data.len() <= 0xFFFF_FFFF {
197 script.push(0x4e); script.extend_from_slice(&(data.len() as u32).to_le_bytes());
199 } else {
200 return Err(SignerError::EncodingError(
201 "op_return data length exceeds script push limit".into(),
202 ));
203 }
204 script.extend_from_slice(data);
205 Ok(script)
206 }
207 }
208 }
209
210 pub fn address(&self, hrp: &str) -> Result<String, SignerError> {
212 match self {
213 Descriptor::Pkh(key) => {
214 let hash = key.hash160().ok_or(SignerError::ParseError(
215 "pkh requires compressed key".into(),
216 ))?;
217 let prefix = if hrp == "bc" || hrp == "mainnet" {
218 0x00u8
219 } else {
220 0x6Fu8
221 };
222 Ok(encoding::base58check_encode(prefix, &hash))
223 }
224 Descriptor::Wpkh(key) => {
225 let hash = key.hash160().ok_or(SignerError::ParseError(
226 "wpkh requires compressed key".into(),
227 ))?;
228 encoding::bech32_encode(hrp, 0, &hash)
229 }
230 Descriptor::ShWpkh(_) => {
231 let script = self.script_pubkey()?;
232 let hash = &script[2..22];
233 let prefix = if hrp == "bc" || hrp == "mainnet" {
234 0x05u8
235 } else {
236 0xC4u8
237 };
238 Ok(encoding::base58check_encode(prefix, hash))
239 }
240 Descriptor::Tr(key) => {
241 let xonly = match key {
242 DescriptorKey::XOnly(k) => *k,
243 DescriptorKey::Compressed(k) => {
244 let mut xo = [0u8; 32];
245 xo.copy_from_slice(&k[1..]);
246 xo
247 }
248 };
249 let (output_key, _parity) = super::taproot::taproot_tweak(&xonly, None)?;
250 encoding::bech32_encode(hrp, 1, &output_key)
251 }
252 Descriptor::Raw(_) | Descriptor::OpReturn(_) => Err(SignerError::EncodingError(
253 "raw/op_return descriptors have no address".into(),
254 )),
255 }
256 }
257
258 pub fn to_string_repr(&self) -> String {
260 match self {
261 Descriptor::Pkh(key) => format!("pkh({})", key_to_hex(key)),
262 Descriptor::Wpkh(key) => format!("wpkh({})", key_to_hex(key)),
263 Descriptor::ShWpkh(key) => format!("sh(wpkh({}))", key_to_hex(key)),
264 Descriptor::Tr(key) => format!("tr({})", key_to_hex(key)),
265 Descriptor::Raw(script) => format!("raw({})", hex::encode(script)),
266 Descriptor::OpReturn(data) => format!("raw(6a{})", hex::encode(data)),
267 }
268 }
269
270 pub fn checksum(&self) -> String {
272 let desc_str = self.to_string_repr();
273 descriptor_checksum(&desc_str)
274 }
275
276 pub fn to_string_with_checksum(&self) -> String {
278 let desc = self.to_string_repr();
279 let checksum = descriptor_checksum(&desc);
280 format!("{desc}#{checksum}")
281 }
282}
283
284pub fn parse(descriptor: &str) -> Result<Descriptor, SignerError> {
290 let desc = if let Some((payload, checksum)) = descriptor.rsplit_once('#') {
292 if checksum.len() != 8 {
293 return Err(SignerError::ParseError(
294 "invalid descriptor checksum length".into(),
295 ));
296 }
297 let expected = descriptor_checksum(payload);
298 if checksum != expected {
299 return Err(SignerError::ParseError(
300 "invalid descriptor checksum".into(),
301 ));
302 }
303 payload
304 } else {
305 descriptor
306 };
307
308 if let Some(inner) = strip_wrapper(desc, "pkh(", ")") {
309 let key = DescriptorKey::from_hex(inner)?;
310 Ok(Descriptor::pkh(key))
311 } else if let Some(inner) = strip_wrapper(desc, "wpkh(", ")") {
312 let key = DescriptorKey::from_hex(inner)?;
313 Ok(Descriptor::wpkh(key))
314 } else if let Some(inner) = strip_wrapper(desc, "sh(wpkh(", "))") {
315 let key = DescriptorKey::from_hex(inner)?;
316 Ok(Descriptor::sh_wpkh(key))
317 } else if let Some(inner) = strip_wrapper(desc, "tr(", ")") {
318 let key = DescriptorKey::from_hex(inner)?;
319 Ok(Descriptor::tr(key))
320 } else if let Some(inner) = strip_wrapper(desc, "raw(", ")") {
321 let bytes = hex::decode(inner).map_err(|e| SignerError::ParseError(format!("hex: {e}")))?;
322 Ok(Descriptor::Raw(bytes))
323 } else {
324 Err(SignerError::ParseError(format!(
325 "unsupported descriptor: {desc}"
326 )))
327 }
328}
329
330fn strip_wrapper<'a>(s: &'a str, prefix: &str, suffix: &str) -> Option<&'a str> {
332 s.strip_prefix(prefix).and_then(|s| s.strip_suffix(suffix))
333}
334
335fn descriptor_checksum(desc: &str) -> String {
341 const INPUT_CHARSET: &str = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ ";
342 const CHECKSUM_CHARSET: &[u8] = b"qpzry9x8gf2tvdw0s3jn54khce6mua7l";
343
344 fn polymod(c: u64, val: u64) -> u64 {
345 let c0 = c >> 35;
346 let mut c = ((c & 0x7FFFFFFFF) << 5) ^ val;
347 if c0 & 1 != 0 {
348 c ^= 0xf5dee51989;
349 }
350 if c0 & 2 != 0 {
351 c ^= 0xa9fdca3312;
352 }
353 if c0 & 4 != 0 {
354 c ^= 0x1bab10e32d;
355 }
356 if c0 & 8 != 0 {
357 c ^= 0x3706b1677a;
358 }
359 if c0 & 16 != 0 {
360 c ^= 0x644d626ffd;
361 }
362 c
363 }
364
365 let mut c = 1u64;
366 let mut cls = 0u64;
367 let mut clscount = 0u64;
368
369 for ch in desc.chars() {
370 if let Some(pos) = INPUT_CHARSET.find(ch) {
371 c = polymod(c, pos as u64 & 31);
372 cls = cls * 3 + (pos as u64 >> 5);
373 clscount += 1;
374 if clscount == 3 {
375 c = polymod(c, cls);
376 cls = 0;
377 clscount = 0;
378 }
379 }
380 }
381 if clscount > 0 {
382 c = polymod(c, cls);
383 }
384 for _ in 0..8 {
385 c = polymod(c, 0);
386 }
387 c ^= 1;
388
389 let mut result = String::with_capacity(8);
390 for j in 0..8 {
391 result.push(CHECKSUM_CHARSET[((c >> (5 * (7 - j))) & 31) as usize] as char);
392 }
393 result
394}
395
396fn key_to_hex(key: &DescriptorKey) -> String {
399 match key {
400 DescriptorKey::Compressed(k) => hex::encode(k),
401 DescriptorKey::XOnly(k) => hex::encode(k),
402 }
403}
404
405#[cfg(test)]
408#[allow(clippy::unwrap_used, clippy::expect_used)]
409mod tests {
410 use super::*;
411
412 const TEST_PUBKEY: &str = "0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798";
413
414 fn test_key() -> DescriptorKey {
415 DescriptorKey::from_hex(TEST_PUBKEY).expect("valid key")
416 }
417
418 #[test]
419 fn test_descriptor_key_from_hex_compressed() {
420 let key = test_key();
421 assert!(key.compressed_bytes().is_some());
422 assert!(key.x_only_bytes().is_none());
423 }
424
425 #[test]
426 fn test_descriptor_key_from_hex_xonly() {
427 let key = DescriptorKey::from_hex(
428 "79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
429 )
430 .expect("valid");
431 assert!(key.x_only_bytes().is_some());
432 }
433
434 #[test]
435 fn test_descriptor_key_from_hex_invalid() {
436 assert!(DescriptorKey::from_hex("0102").is_err());
437 assert!(DescriptorKey::from_hex("invalid").is_err());
438 }
439
440 #[test]
441 fn test_descriptor_key_hash160() {
442 let key = test_key();
443 let h = key.hash160().expect("compressed key");
444 assert_eq!(h.len(), 20);
445 assert_eq!(hex::encode(h), "751e76e8199196d454941c45d1b3a323f1433bd6");
447 }
448
449 #[test]
450 fn test_descriptor_pkh_script_pubkey() {
451 let key = test_key();
452 let desc = Descriptor::pkh(key);
453 let script = desc.script_pubkey().expect("ok");
454 assert_eq!(script.len(), 25);
455 assert_eq!(script[0], 0x76); assert_eq!(script[1], 0xa9); assert_eq!(script[2], 0x14); assert_eq!(script[23], 0x88); assert_eq!(script[24], 0xac); }
461
462 #[test]
463 fn test_descriptor_wpkh_script_pubkey() {
464 let key = test_key();
465 let desc = Descriptor::wpkh(key);
466 let script = desc.script_pubkey().expect("ok");
467 assert_eq!(script.len(), 22);
468 assert_eq!(script[0], 0x00); assert_eq!(script[1], 0x14); }
471
472 #[test]
473 fn test_descriptor_sh_wpkh_script_pubkey() {
474 let key = test_key();
475 let desc = Descriptor::sh_wpkh(key);
476 let script = desc.script_pubkey().expect("ok");
477 assert_eq!(script.len(), 23);
478 assert_eq!(script[0], 0xa9); assert_eq!(script[1], 0x14); assert_eq!(script[22], 0x87); }
482
483 #[test]
484 fn test_descriptor_tr_script_pubkey() {
485 let key = DescriptorKey::from_hex(
486 "79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
487 )
488 .expect("valid");
489 let desc = Descriptor::tr(key);
490 let script = desc.script_pubkey().expect("ok");
491 assert_eq!(script.len(), 34);
492 assert_eq!(script[0], 0x51); assert_eq!(script[1], 0x20); }
495
496 #[test]
497 fn test_descriptor_tr_script_pubkey_uses_tweaked_output_key() {
498 let xonly = [
499 0x79, 0xBE, 0x66, 0x7E, 0xF9, 0xDC, 0xBB, 0xAC, 0x55, 0xA0, 0x62, 0x95, 0xCE, 0x87,
500 0x0B, 0x07, 0x02, 0x9B, 0xFC, 0xDB, 0x2D, 0xCE, 0x28, 0xD9, 0x59, 0xF2, 0x81, 0x5B,
501 0x16, 0xF8, 0x17, 0x98,
502 ];
503 let desc = Descriptor::tr(DescriptorKey::XOnly(xonly));
504 let script = desc.script_pubkey().expect("ok");
505 let (tweaked, _) = crate::bitcoin::taproot::taproot_tweak(&xonly, None).expect("tweak");
506 assert_eq!(&script[2..], &tweaked);
507 assert_ne!(&script[2..], &xonly);
508 }
509
510 #[test]
511 fn test_descriptor_pkh_address_mainnet() {
512 let key = test_key();
513 let desc = Descriptor::pkh(key);
514 let addr = desc.address("bc").expect("ok");
515 assert_eq!(addr, "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH");
517 }
518
519 #[test]
520 fn test_descriptor_wpkh_address_mainnet() {
521 let key = test_key();
522 let desc = Descriptor::wpkh(key);
523 let addr = desc.address("bc").expect("ok");
524 assert!(addr.starts_with("bc1q"));
525 assert_eq!(addr, "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4");
527 }
528
529 #[test]
530 fn test_descriptor_sh_wpkh_address() {
531 let key = test_key();
532 let desc = Descriptor::sh_wpkh(key);
533 let addr = desc.address("bc").expect("ok");
534 assert!(addr.starts_with('3'));
535 }
536
537 #[test]
538 fn test_descriptor_tr_address() {
539 let key = DescriptorKey::from_hex(
540 "79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
541 )
542 .expect("valid");
543 let desc = Descriptor::tr(key);
544 let addr = desc.address("bc").expect("ok");
545 assert!(addr.starts_with("bc1p"));
546 let xonly = [
547 0x79, 0xBE, 0x66, 0x7E, 0xF9, 0xDC, 0xBB, 0xAC, 0x55, 0xA0, 0x62, 0x95, 0xCE, 0x87,
548 0x0B, 0x07, 0x02, 0x9B, 0xFC, 0xDB, 0x2D, 0xCE, 0x28, 0xD9, 0x59, 0xF2, 0x81, 0x5B,
549 0x16, 0xF8, 0x17, 0x98,
550 ];
551 let expected = crate::bitcoin::taproot::taproot_address(&xonly, None, "bc").expect("addr");
552 assert_eq!(addr, expected);
553 }
554
555 #[test]
556 fn test_descriptor_parse_pkh() {
557 let desc = parse(&format!("pkh({TEST_PUBKEY})")).expect("ok");
558 assert!(matches!(desc, Descriptor::Pkh(_)));
559 }
560
561 #[test]
562 fn test_descriptor_parse_wpkh() {
563 let desc = parse(&format!("wpkh({TEST_PUBKEY})")).expect("ok");
564 assert!(matches!(desc, Descriptor::Wpkh(_)));
565 }
566
567 #[test]
568 fn test_descriptor_parse_sh_wpkh() {
569 let desc = parse(&format!("sh(wpkh({TEST_PUBKEY}))")).expect("ok");
570 assert!(matches!(desc, Descriptor::ShWpkh(_)));
571 }
572
573 #[test]
574 fn test_descriptor_parse_tr() {
575 let xonly = "79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798";
576 let desc = parse(&format!("tr({xonly})")).expect("ok");
577 assert!(matches!(desc, Descriptor::Tr(_)));
578 }
579
580 #[test]
581 fn test_descriptor_parse_raw() {
582 let desc = parse("raw(6a0568656c6c6f)").expect("ok");
583 assert!(matches!(desc, Descriptor::Raw(_)));
584 }
585
586 #[test]
587 fn test_descriptor_parse_with_checksum() {
588 let payload = format!("pkh({TEST_PUBKEY})");
589 let desc_str = format!("{payload}#{}", descriptor_checksum(&payload));
590 let desc = parse(&desc_str).expect("ok");
591 assert!(matches!(desc, Descriptor::Pkh(_)));
592 }
593
594 #[test]
595 fn test_descriptor_parse_with_invalid_checksum_fails() {
596 let desc_str = format!("pkh({TEST_PUBKEY})#aaaaaaaa");
597 assert!(parse(&desc_str).is_err());
598 }
599
600 #[test]
601 fn test_descriptor_parse_with_short_checksum_fails() {
602 let desc_str = format!("pkh({TEST_PUBKEY})#abc");
603 assert!(parse(&desc_str).is_err());
604 }
605
606 #[test]
607 fn test_descriptor_parse_invalid() {
608 assert!(parse("unknown(key)").is_err());
609 }
610
611 #[test]
612 fn test_descriptor_to_string_roundtrip() {
613 let key = test_key();
614 let desc = Descriptor::pkh(key);
615 let s = desc.to_string_repr();
616 let reparsed = parse(&s).expect("roundtrip");
617 assert!(matches!(reparsed, Descriptor::Pkh(_)));
618 }
619
620 #[test]
621 fn test_descriptor_checksum_length() {
622 let key = test_key();
623 let desc = Descriptor::pkh(key);
624 let cs = desc.checksum();
625 assert_eq!(cs.len(), 8);
626 assert!(cs.chars().all(|c| c.is_alphanumeric()));
627 }
628
629 #[test]
630 fn test_descriptor_with_checksum() {
631 let key = test_key();
632 let desc = Descriptor::wpkh(key);
633 let full = desc.to_string_with_checksum();
634 assert!(full.contains('#'));
635 let parts: Vec<&str> = full.split('#').collect();
636 assert_eq!(parts.len(), 2);
637 assert_eq!(parts[1].len(), 8);
638 }
639
640 #[test]
641 fn test_descriptor_testnet_address() {
642 let key = test_key();
643 let mainnet = Descriptor::pkh(key.clone()).address("bc").expect("ok");
644 let testnet = Descriptor::pkh(key).address("tb").expect("ok");
645 assert_ne!(mainnet, testnet);
646 assert!(testnet.starts_with('m') || testnet.starts_with('n'));
647 }
648
649 #[test]
650 fn test_descriptor_op_return() {
651 let data = vec![0x01, 0x02, 0x03];
652 let desc = Descriptor::OpReturn(data);
653 let script = desc.script_pubkey().expect("ok");
654 assert_eq!(script[0], 0x6a); assert!(desc.address("bc").is_err()); }
657
658 #[test]
659 fn test_descriptor_op_return_pushdata1() {
660 let data = vec![0xAA; 80];
661 let desc = Descriptor::OpReturn(data.clone());
662 let script = desc.script_pubkey().expect("ok");
663 assert_eq!(script[0], 0x6a); assert_eq!(script[1], 0x4c); assert_eq!(script[2], 80);
666 assert_eq!(&script[3..], &data);
667 }
668
669 #[test]
670 fn test_descriptor_op_return_pushdata2() {
671 let data = vec![0x55; 300];
672 let desc = Descriptor::OpReturn(data.clone());
673 let script = desc.script_pubkey().expect("ok");
674 assert_eq!(script[0], 0x6a); assert_eq!(script[1], 0x4d); assert_eq!(&script[2..4], &300u16.to_le_bytes());
677 assert_eq!(&script[4..], &data);
678 }
679
680 #[test]
681 fn test_descriptor_xonly_key_no_hash160() {
682 let key = DescriptorKey::from_hex(
683 "79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
684 )
685 .expect("valid");
686 assert!(key.hash160().is_none()); assert!(Descriptor::pkh(key).script_pubkey().is_err()); }
689}