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 mut script = Vec::with_capacity(34);
178 script.push(0x51); script.push(0x20); script.extend_from_slice(&xonly);
181 Ok(script)
182 }
183 Descriptor::Raw(script) => Ok(script.clone()),
184 Descriptor::OpReturn(data) => {
185 let mut script = Vec::with_capacity(2 + data.len());
186 script.push(0x6a); if data.len() <= 75 {
188 script.push(data.len() as u8);
189 }
190 script.extend_from_slice(data);
191 Ok(script)
192 }
193 }
194 }
195
196 pub fn address(&self, hrp: &str) -> Result<String, SignerError> {
198 match self {
199 Descriptor::Pkh(key) => {
200 let hash = key.hash160().ok_or(SignerError::ParseError(
201 "pkh requires compressed key".into(),
202 ))?;
203 let prefix = if hrp == "bc" || hrp == "mainnet" {
204 0x00u8
205 } else {
206 0x6Fu8
207 };
208 Ok(encoding::base58check_encode(prefix, &hash))
209 }
210 Descriptor::Wpkh(key) => {
211 let hash = key.hash160().ok_or(SignerError::ParseError(
212 "wpkh requires compressed key".into(),
213 ))?;
214 encoding::bech32_encode(hrp, 0, &hash)
215 }
216 Descriptor::ShWpkh(_) => {
217 let script = self.script_pubkey()?;
218 let hash = &script[2..22];
219 let prefix = if hrp == "bc" || hrp == "mainnet" {
220 0x05u8
221 } else {
222 0xC4u8
223 };
224 Ok(encoding::base58check_encode(prefix, hash))
225 }
226 Descriptor::Tr(key) => {
227 let xonly = match key {
228 DescriptorKey::XOnly(k) => *k,
229 DescriptorKey::Compressed(k) => {
230 let mut xo = [0u8; 32];
231 xo.copy_from_slice(&k[1..]);
232 xo
233 }
234 };
235 encoding::bech32_encode(hrp, 1, &xonly)
236 }
237 Descriptor::Raw(_) | Descriptor::OpReturn(_) => Err(SignerError::EncodingError(
238 "raw/op_return descriptors have no address".into(),
239 )),
240 }
241 }
242
243 pub fn to_string_repr(&self) -> String {
245 match self {
246 Descriptor::Pkh(key) => format!("pkh({})", key_to_hex(key)),
247 Descriptor::Wpkh(key) => format!("wpkh({})", key_to_hex(key)),
248 Descriptor::ShWpkh(key) => format!("sh(wpkh({}))", key_to_hex(key)),
249 Descriptor::Tr(key) => format!("tr({})", key_to_hex(key)),
250 Descriptor::Raw(script) => format!("raw({})", hex::encode(script)),
251 Descriptor::OpReturn(data) => format!("raw(6a{})", hex::encode(data)),
252 }
253 }
254
255 pub fn checksum(&self) -> String {
257 let desc_str = self.to_string_repr();
258 descriptor_checksum(&desc_str)
259 }
260
261 pub fn to_string_with_checksum(&self) -> String {
263 let desc = self.to_string_repr();
264 let checksum = descriptor_checksum(&desc);
265 format!("{desc}#{checksum}")
266 }
267}
268
269pub fn parse(descriptor: &str) -> Result<Descriptor, SignerError> {
275 let desc = if let Some(pos) = descriptor.find('#') {
277 &descriptor[..pos]
278 } else {
279 descriptor
280 };
281
282 if let Some(inner) = strip_wrapper(desc, "pkh(", ")") {
283 let key = DescriptorKey::from_hex(inner)?;
284 Ok(Descriptor::pkh(key))
285 } else if let Some(inner) = strip_wrapper(desc, "wpkh(", ")") {
286 let key = DescriptorKey::from_hex(inner)?;
287 Ok(Descriptor::wpkh(key))
288 } else if let Some(inner) = strip_wrapper(desc, "sh(wpkh(", "))") {
289 let key = DescriptorKey::from_hex(inner)?;
290 Ok(Descriptor::sh_wpkh(key))
291 } else if let Some(inner) = strip_wrapper(desc, "tr(", ")") {
292 let key = DescriptorKey::from_hex(inner)?;
293 Ok(Descriptor::tr(key))
294 } else if let Some(inner) = strip_wrapper(desc, "raw(", ")") {
295 let bytes = hex::decode(inner).map_err(|e| SignerError::ParseError(format!("hex: {e}")))?;
296 Ok(Descriptor::Raw(bytes))
297 } else {
298 Err(SignerError::ParseError(format!(
299 "unsupported descriptor: {desc}"
300 )))
301 }
302}
303
304fn strip_wrapper<'a>(s: &'a str, prefix: &str, suffix: &str) -> Option<&'a str> {
306 s.strip_prefix(prefix).and_then(|s| s.strip_suffix(suffix))
307}
308
309fn descriptor_checksum(desc: &str) -> String {
315 const INPUT_CHARSET: &str = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ ";
316 const CHECKSUM_CHARSET: &[u8] = b"qpzry9x8gf2tvdw0s3jn54khce6mua7l";
317
318 fn polymod(c: u64, val: u64) -> u64 {
319 let c0 = c >> 35;
320 let mut c = ((c & 0x7FFFFFFFF) << 5) ^ val;
321 if c0 & 1 != 0 {
322 c ^= 0xf5dee51989;
323 }
324 if c0 & 2 != 0 {
325 c ^= 0xa9fdca3312;
326 }
327 if c0 & 4 != 0 {
328 c ^= 0x1bab10e32d;
329 }
330 if c0 & 8 != 0 {
331 c ^= 0x3706b1677a;
332 }
333 if c0 & 16 != 0 {
334 c ^= 0x644d626ffd;
335 }
336 c
337 }
338
339 let mut c = 1u64;
340 let mut cls = 0u64;
341 let mut clscount = 0u64;
342
343 for ch in desc.chars() {
344 if let Some(pos) = INPUT_CHARSET.find(ch) {
345 c = polymod(c, pos as u64 & 31);
346 cls = cls * 3 + (pos as u64 >> 5);
347 clscount += 1;
348 if clscount == 3 {
349 c = polymod(c, cls);
350 cls = 0;
351 clscount = 0;
352 }
353 }
354 }
355 if clscount > 0 {
356 c = polymod(c, cls);
357 }
358 for _ in 0..8 {
359 c = polymod(c, 0);
360 }
361 c ^= 1;
362
363 let mut result = String::with_capacity(8);
364 for j in 0..8 {
365 result.push(CHECKSUM_CHARSET[((c >> (5 * (7 - j))) & 31) as usize] as char);
366 }
367 result
368}
369
370fn key_to_hex(key: &DescriptorKey) -> String {
373 match key {
374 DescriptorKey::Compressed(k) => hex::encode(k),
375 DescriptorKey::XOnly(k) => hex::encode(k),
376 }
377}
378
379#[cfg(test)]
382#[allow(clippy::unwrap_used, clippy::expect_used)]
383mod tests {
384 use super::*;
385
386 const TEST_PUBKEY: &str = "0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798";
387
388 fn test_key() -> DescriptorKey {
389 DescriptorKey::from_hex(TEST_PUBKEY).expect("valid key")
390 }
391
392 #[test]
393 fn test_descriptor_key_from_hex_compressed() {
394 let key = test_key();
395 assert!(key.compressed_bytes().is_some());
396 assert!(key.x_only_bytes().is_none());
397 }
398
399 #[test]
400 fn test_descriptor_key_from_hex_xonly() {
401 let key = DescriptorKey::from_hex(
402 "79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
403 )
404 .expect("valid");
405 assert!(key.x_only_bytes().is_some());
406 }
407
408 #[test]
409 fn test_descriptor_key_from_hex_invalid() {
410 assert!(DescriptorKey::from_hex("0102").is_err());
411 assert!(DescriptorKey::from_hex("invalid").is_err());
412 }
413
414 #[test]
415 fn test_descriptor_key_hash160() {
416 let key = test_key();
417 let h = key.hash160().expect("compressed key");
418 assert_eq!(h.len(), 20);
419 assert_eq!(hex::encode(h), "751e76e8199196d454941c45d1b3a323f1433bd6");
421 }
422
423 #[test]
424 fn test_descriptor_pkh_script_pubkey() {
425 let key = test_key();
426 let desc = Descriptor::pkh(key);
427 let script = desc.script_pubkey().expect("ok");
428 assert_eq!(script.len(), 25);
429 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); }
435
436 #[test]
437 fn test_descriptor_wpkh_script_pubkey() {
438 let key = test_key();
439 let desc = Descriptor::wpkh(key);
440 let script = desc.script_pubkey().expect("ok");
441 assert_eq!(script.len(), 22);
442 assert_eq!(script[0], 0x00); assert_eq!(script[1], 0x14); }
445
446 #[test]
447 fn test_descriptor_sh_wpkh_script_pubkey() {
448 let key = test_key();
449 let desc = Descriptor::sh_wpkh(key);
450 let script = desc.script_pubkey().expect("ok");
451 assert_eq!(script.len(), 23);
452 assert_eq!(script[0], 0xa9); assert_eq!(script[1], 0x14); assert_eq!(script[22], 0x87); }
456
457 #[test]
458 fn test_descriptor_tr_script_pubkey() {
459 let key = DescriptorKey::from_hex(
460 "79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
461 )
462 .expect("valid");
463 let desc = Descriptor::tr(key);
464 let script = desc.script_pubkey().expect("ok");
465 assert_eq!(script.len(), 34);
466 assert_eq!(script[0], 0x51); assert_eq!(script[1], 0x20); }
469
470 #[test]
471 fn test_descriptor_pkh_address_mainnet() {
472 let key = test_key();
473 let desc = Descriptor::pkh(key);
474 let addr = desc.address("bc").expect("ok");
475 assert_eq!(addr, "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH");
477 }
478
479 #[test]
480 fn test_descriptor_wpkh_address_mainnet() {
481 let key = test_key();
482 let desc = Descriptor::wpkh(key);
483 let addr = desc.address("bc").expect("ok");
484 assert!(addr.starts_with("bc1q"));
485 assert_eq!(addr, "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4");
487 }
488
489 #[test]
490 fn test_descriptor_sh_wpkh_address() {
491 let key = test_key();
492 let desc = Descriptor::sh_wpkh(key);
493 let addr = desc.address("bc").expect("ok");
494 assert!(addr.starts_with('3'));
495 }
496
497 #[test]
498 fn test_descriptor_tr_address() {
499 let key = DescriptorKey::from_hex(
500 "79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
501 )
502 .expect("valid");
503 let desc = Descriptor::tr(key);
504 let addr = desc.address("bc").expect("ok");
505 assert!(addr.starts_with("bc1p"));
506 }
507
508 #[test]
509 fn test_descriptor_parse_pkh() {
510 let desc = parse(&format!("pkh({TEST_PUBKEY})")).expect("ok");
511 assert!(matches!(desc, Descriptor::Pkh(_)));
512 }
513
514 #[test]
515 fn test_descriptor_parse_wpkh() {
516 let desc = parse(&format!("wpkh({TEST_PUBKEY})")).expect("ok");
517 assert!(matches!(desc, Descriptor::Wpkh(_)));
518 }
519
520 #[test]
521 fn test_descriptor_parse_sh_wpkh() {
522 let desc = parse(&format!("sh(wpkh({TEST_PUBKEY}))")).expect("ok");
523 assert!(matches!(desc, Descriptor::ShWpkh(_)));
524 }
525
526 #[test]
527 fn test_descriptor_parse_tr() {
528 let xonly = "79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798";
529 let desc = parse(&format!("tr({xonly})")).expect("ok");
530 assert!(matches!(desc, Descriptor::Tr(_)));
531 }
532
533 #[test]
534 fn test_descriptor_parse_raw() {
535 let desc = parse("raw(6a0568656c6c6f)").expect("ok");
536 assert!(matches!(desc, Descriptor::Raw(_)));
537 }
538
539 #[test]
540 fn test_descriptor_parse_with_checksum() {
541 let desc_str = format!("pkh({TEST_PUBKEY})#something");
542 let desc = parse(&desc_str).expect("ok");
543 assert!(matches!(desc, Descriptor::Pkh(_)));
544 }
545
546 #[test]
547 fn test_descriptor_parse_invalid() {
548 assert!(parse("unknown(key)").is_err());
549 }
550
551 #[test]
552 fn test_descriptor_to_string_roundtrip() {
553 let key = test_key();
554 let desc = Descriptor::pkh(key);
555 let s = desc.to_string_repr();
556 let reparsed = parse(&s).expect("roundtrip");
557 assert!(matches!(reparsed, Descriptor::Pkh(_)));
558 }
559
560 #[test]
561 fn test_descriptor_checksum_length() {
562 let key = test_key();
563 let desc = Descriptor::pkh(key);
564 let cs = desc.checksum();
565 assert_eq!(cs.len(), 8);
566 assert!(cs.chars().all(|c| c.is_alphanumeric()));
567 }
568
569 #[test]
570 fn test_descriptor_with_checksum() {
571 let key = test_key();
572 let desc = Descriptor::wpkh(key);
573 let full = desc.to_string_with_checksum();
574 assert!(full.contains('#'));
575 let parts: Vec<&str> = full.split('#').collect();
576 assert_eq!(parts.len(), 2);
577 assert_eq!(parts[1].len(), 8);
578 }
579
580 #[test]
581 fn test_descriptor_testnet_address() {
582 let key = test_key();
583 let mainnet = Descriptor::pkh(key.clone()).address("bc").expect("ok");
584 let testnet = Descriptor::pkh(key).address("tb").expect("ok");
585 assert_ne!(mainnet, testnet);
586 assert!(testnet.starts_with('m') || testnet.starts_with('n'));
587 }
588
589 #[test]
590 fn test_descriptor_op_return() {
591 let data = vec![0x01, 0x02, 0x03];
592 let desc = Descriptor::OpReturn(data);
593 let script = desc.script_pubkey().expect("ok");
594 assert_eq!(script[0], 0x6a); assert!(desc.address("bc").is_err()); }
597
598 #[test]
599 fn test_descriptor_xonly_key_no_hash160() {
600 let key = DescriptorKey::from_hex(
601 "79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
602 )
603 .expect("valid");
604 assert!(key.hash160().is_none()); assert!(Descriptor::pkh(key).script_pubkey().is_err()); }
607}