Skip to content

Commit de1a5b2

Browse files
authored
Merge pull request #197 from darosior/descriptor_checksum
Descriptor checksum
2 parents e9732e4 + cd1fcfa commit de1a5b2

File tree

6 files changed

+957
-756
lines changed

6 files changed

+957
-756
lines changed

examples/htlc.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ fn main() {
4141
assert!(htlc_descriptor.sanity_check().is_ok());
4242
assert_eq!(
4343
format!("{}", htlc_descriptor),
44-
"wsh(andor(pk(022222222222222222222222222222222222222222222222222222222222222222),sha256(1111111111111111111111111111111111111111111111111111111111111111),and_v(v:pkh(51814f108670aced2d77c1805ddd6634bc9d4731),older(4444))))"
44+
"wsh(andor(pk(022222222222222222222222222222222222222222222222222222222222222222),sha256(1111111111111111111111111111111111111111111111111111111111111111),and_v(v:pkh(51814f108670aced2d77c1805ddd6634bc9d4731),older(4444))))#s0qq76ng"
4545
);
4646

4747
assert_eq!(

fuzz/fuzz_targets/roundtrip_descriptor.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::str::FromStr;
88
fn do_test(data: &[u8]) {
99
let s = String::from_utf8_lossy(data);
1010
if let Ok(desc) = Descriptor::<DummyKey>::from_str(&s) {
11-
let output = desc.to_string();
11+
let output = format!("{:?}", desc);
1212

1313
let multi_wrap_pk_re = Regex::new("([a-z]+)c:pk_k\\(").unwrap();
1414
let multi_wrap_pkh_re = Regex::new("([a-z]+)c:pk_h\\(").unwrap();

src/descriptor/checksum.rs

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
//! Descriptor checksum
2+
//!
3+
//! This module contains a re-implementation of the function used by Bitcoin Core to calculate the
4+
//! checksum of a descriptor
5+
6+
use std::iter::FromIterator;
7+
8+
use Error;
9+
10+
const INPUT_CHARSET: &str = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ ";
11+
const CHECKSUM_CHARSET: &str = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
12+
13+
fn poly_mod(mut c: u64, val: u64) -> u64 {
14+
let c0 = c >> 35;
15+
16+
c = ((c & 0x7ffffffff) << 5) ^ val;
17+
if c0 & 1 > 0 {
18+
c ^= 0xf5dee51989
19+
};
20+
if c0 & 2 > 0 {
21+
c ^= 0xa9fdca3312
22+
};
23+
if c0 & 4 > 0 {
24+
c ^= 0x1bab10e32d
25+
};
26+
if c0 & 8 > 0 {
27+
c ^= 0x3706b1677a
28+
};
29+
if c0 & 16 > 0 {
30+
c ^= 0x644d626ffd
31+
};
32+
33+
c
34+
}
35+
36+
/// Compute the checksum of a descriptor
37+
pub fn desc_checksum(desc: &str) -> Result<String, Error> {
38+
let mut c = 1;
39+
let mut cls = 0;
40+
let mut clscount = 0;
41+
42+
for ch in desc.chars() {
43+
let pos = INPUT_CHARSET.find(ch).ok_or(Error::BadDescriptor(format!(
44+
"Invalid character in checksum: '{}'",
45+
ch
46+
)))? as u64;
47+
c = poly_mod(c, pos & 31);
48+
cls = cls * 3 + (pos >> 5);
49+
clscount += 1;
50+
if clscount == 3 {
51+
c = poly_mod(c, cls);
52+
cls = 0;
53+
clscount = 0;
54+
}
55+
}
56+
if clscount > 0 {
57+
c = poly_mod(c, cls);
58+
}
59+
(0..8).for_each(|_| c = poly_mod(c, 0));
60+
c ^= 1;
61+
62+
let mut chars = Vec::with_capacity(8);
63+
for j in 0..8 {
64+
chars.push(
65+
CHECKSUM_CHARSET
66+
.chars()
67+
.nth(((c >> (5 * (7 - j))) & 31) as usize)
68+
.unwrap(),
69+
);
70+
}
71+
72+
Ok(String::from_iter(chars))
73+
}
74+
75+
#[cfg(test)]
76+
mod test {
77+
use super::*;
78+
use std::str;
79+
80+
macro_rules! check_expected {
81+
($desc: expr, $checksum: expr) => {
82+
assert_eq!(desc_checksum($desc).unwrap(), $checksum);
83+
};
84+
}
85+
86+
#[test]
87+
fn test_valid_descriptor_checksum() {
88+
check_expected!(
89+
"wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)",
90+
"tqz0nc62"
91+
);
92+
check_expected!(
93+
"pkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/44'/1'/0'/0/*)",
94+
"lasegmfs"
95+
);
96+
97+
// https://github.com/bitcoin/bitcoin/blob/7ae86b3c6845873ca96650fc69beb4ae5285c801/src/test/descriptor_tests.cpp#L352-L354
98+
check_expected!(
99+
"sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))",
100+
"ggrsrxfy"
101+
);
102+
check_expected!(
103+
"sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))",
104+
"tjg09x5t"
105+
);
106+
}
107+
108+
#[test]
109+
fn test_desc_checksum_invalid_character() {
110+
let sparkle_heart = vec![240, 159, 146, 150];
111+
let sparkle_heart = str::from_utf8(&sparkle_heart)
112+
.unwrap()
113+
.chars()
114+
.next()
115+
.unwrap();
116+
let invalid_desc = format!("wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcL{}fjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)", sparkle_heart);
117+
118+
assert_eq!(
119+
desc_checksum(&invalid_desc).err().unwrap().to_string(),
120+
format!(
121+
"Invalid descriptor: Invalid character in checksum: '{}'",
122+
sparkle_heart
123+
)
124+
);
125+
}
126+
}

0 commit comments

Comments
 (0)