bdk_wallet/descriptor/
checksum.rs

1// Bitcoin Dev Kit
2// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
3//
4// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
5//
6// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
7// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
8// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
9// You may not use this file except in accordance with one or both of these
10// licenses.
11
12//! Descriptor checksum
13//!
14//! This module contains a re-implementation of the function used by Bitcoin Core to calculate the
15//! checksum of a descriptor
16
17use crate::descriptor::DescriptorError;
18use alloc::string::String;
19
20use miniscript::descriptor::checksum::desc_checksum;
21
22/// Compute the checksum of a descriptor, excludes any existing checksum in the descriptor string
23/// from the calculation
24pub fn calc_checksum(desc: &str) -> Result<String, DescriptorError> {
25    if let Some(split) = desc.split_once('#') {
26        let og_checksum = split.1;
27        let checksum = desc_checksum(split.0)?;
28        if og_checksum != checksum {
29            return Err(DescriptorError::InvalidDescriptorChecksum);
30        }
31        Ok(checksum)
32    } else {
33        Ok(desc_checksum(desc)?)
34    }
35}
36
37#[cfg(test)]
38mod test {
39    use super::*;
40    use crate::descriptor::calc_checksum;
41    use assert_matches::assert_matches;
42
43    // test calc_checksum() function; it should return the same value as Bitcoin Core
44    #[test]
45    fn test_calc_checksum() {
46        let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)";
47        assert_eq!(calc_checksum(desc).unwrap(), "tqz0nc62");
48
49        let desc = "pkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/44'/1'/0'/0/*)";
50        assert_eq!(calc_checksum(desc).unwrap(), "lasegmfs");
51    }
52
53    // test calc_checksum() function; it should return the same value as Bitcoin Core even if the
54    // descriptor string includes a checksum hash
55    #[test]
56    fn test_calc_checksum_with_checksum_hash() {
57        let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#tqz0nc62";
58        assert_eq!(calc_checksum(desc).unwrap(), "tqz0nc62");
59
60        let desc = "pkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/44'/1'/0'/0/*)#lasegmfs";
61        assert_eq!(calc_checksum(desc).unwrap(), "lasegmfs");
62
63        let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#tqz0nc26";
64        assert_matches!(
65            calc_checksum(desc),
66            Err(DescriptorError::InvalidDescriptorChecksum)
67        );
68
69        let desc = "pkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/44'/1'/0'/0/*)#lasegmsf";
70        assert_matches!(
71            calc_checksum(desc),
72            Err(DescriptorError::InvalidDescriptorChecksum)
73        );
74    }
75
76    #[test]
77    fn test_calc_checksum_invalid_character() {
78        let sparkle_heart = unsafe { core::str::from_utf8_unchecked(&[240, 159, 146, 150]) };
79        let invalid_desc = format!("wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcL{sparkle_heart}fjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)");
80
81        assert_matches!(
82            calc_checksum(&invalid_desc),
83            Err(DescriptorError::Miniscript(miniscript::Error::BadDescriptor(e))) if e == format!("Invalid character in checksum: '{sparkle_heart}'")
84        );
85    }
86}