Skip to content

Commit 95c99fc

Browse files
darosiorafilini
andcommitted
descriptor: add Alekos' checksum implementation
This is a translation of bitcoind's checksum function for descriptors. This file is entirely taken from Magical Bitcoin / Bitcoindevkit, and was written by Alekos Filini. The MIT license was stripped with Alekos' agreement, see #195 (comment), in order to make it compatible with rust-miniscript's license. Co-Authored-by: Alekos Filini <[email protected]> Signed-off-by: Antoine Poinsot <[email protected]>
1 parent 33411b4 commit 95c99fc

File tree

1 file changed

+102
-0
lines changed

1 file changed

+102
-0
lines changed

src/descriptor/checksum.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
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 crate::descriptor::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+
c = ((c & 0x7ffffffff) << 5) ^ val;
16+
if c0 & 1 > 0 {
17+
c ^= 0xf5dee51989
18+
};
19+
if c0 & 2 > 0 {
20+
c ^= 0xa9fdca3312
21+
};
22+
if c0 & 4 > 0 {
23+
c ^= 0x1bab10e32d
24+
};
25+
if c0 & 8 > 0 {
26+
c ^= 0x3706b1677a
27+
};
28+
if c0 & 16 > 0 {
29+
c ^= 0x644d626ffd
30+
};
31+
32+
c
33+
}
34+
35+
/// Compute the checksum of a descriptor
36+
pub fn get_checksum(desc: &str) -> Result<String, Error> {
37+
let mut c = 1;
38+
let mut cls = 0;
39+
let mut clscount = 0;
40+
for ch in desc.chars() {
41+
let pos = INPUT_CHARSET
42+
.find(ch)
43+
.ok_or(Error::InvalidDescriptorCharacter(ch))? as u64;
44+
c = poly_mod(c, pos & 31);
45+
cls = cls * 3 + (pos >> 5);
46+
clscount += 1;
47+
if clscount == 3 {
48+
c = poly_mod(c, cls);
49+
cls = 0;
50+
clscount = 0;
51+
}
52+
}
53+
if clscount > 0 {
54+
c = poly_mod(c, cls);
55+
}
56+
(0..8).for_each(|_| c = poly_mod(c, 0));
57+
c ^= 1;
58+
59+
let mut chars = Vec::with_capacity(8);
60+
for j in 0..8 {
61+
chars.push(
62+
CHECKSUM_CHARSET
63+
.chars()
64+
.nth(((c >> (5 * (7 - j))) & 31) as usize)
65+
.unwrap(),
66+
);
67+
}
68+
69+
Ok(String::from_iter(chars))
70+
}
71+
72+
#[cfg(test)]
73+
mod test {
74+
use super::*;
75+
use crate::descriptor::get_checksum;
76+
77+
// test get_checksum() function; it should return the same value as Bitcoin Core
78+
#[test]
79+
fn test_get_checksum() {
80+
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)";
81+
assert_eq!(get_checksum(desc).unwrap(), "tqz0nc62");
82+
83+
let desc = "pkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/44'/1'/0'/0/*)";
84+
assert_eq!(get_checksum(desc).unwrap(), "lasegmfs");
85+
}
86+
87+
#[test]
88+
fn test_get_checksum_invalid_character() {
89+
let sparkle_heart = vec![240, 159, 146, 150];
90+
let sparkle_heart = std::str::from_utf8(&sparkle_heart)
91+
.unwrap()
92+
.chars()
93+
.next()
94+
.unwrap();
95+
let invalid_desc = format!("wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcL{}fjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)", sparkle_heart);
96+
97+
assert!(matches!(
98+
get_checksum(&invalid_desc).err(),
99+
Some(Error::InvalidDescriptorCharacter(invalid_char)) if invalid_char == sparkle_heart
100+
));
101+
}
102+
}

0 commit comments

Comments
 (0)