2021-03-03 13:22:05 -08:00
// Bitcoin Dev Kit
// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
2020-08-31 11:26:36 +02:00
//
2021-03-03 13:22:05 -08:00
// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
2020-08-31 11:26:36 +02:00
//
2021-03-03 13:22:05 -08:00
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// You may not use this file except in accordance with one or both of these
// licenses.
2020-08-31 11:26:36 +02:00
2020-09-04 11:44:49 +02:00
//! Descriptor checksum
//!
//! This module contains a re-implementation of the function used by Bitcoin Core to calculate the
//! checksum of a descriptor
2021-01-11 13:12:01 +01:00
use crate ::descriptor ::DescriptorError ;
2020-02-15 21:27:51 +01:00
2022-07-16 20:01:54 +08:00
const INPUT_CHARSET : & [ u8 ] = b " 0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`# \" \\ " ;
const CHECKSUM_CHARSET : & [ u8 ] = b " qpzry9x8gf2tvdw0s3jn54khce6mua7l " ;
2020-02-15 21:27:51 +01:00
fn poly_mod ( mut c : u64 , val : u64 ) -> u64 {
let c0 = c > > 35 ;
c = ( ( c & 0x7ffffffff ) < < 5 ) ^ val ;
if c0 & 1 > 0 {
c ^ = 0xf5dee51989
} ;
if c0 & 2 > 0 {
c ^ = 0xa9fdca3312
} ;
if c0 & 4 > 0 {
c ^ = 0x1bab10e32d
} ;
if c0 & 8 > 0 {
c ^ = 0x3706b1677a
} ;
if c0 & 16 > 0 {
c ^ = 0x644d626ffd
} ;
c
}
2022-09-29 13:06:03 +08:00
/// Computes the checksum bytes of a descriptor.
/// `exclude_hash = true` ignores all data after the first '#' (inclusive).
2022-10-24 12:05:49 -05:00
pub ( crate ) fn calc_checksum_bytes_internal (
mut desc : & str ,
exclude_hash : bool ,
) -> Result < [ u8 ; 8 ] , DescriptorError > {
2020-02-15 21:27:51 +01:00
let mut c = 1 ;
let mut cls = 0 ;
let mut clscount = 0 ;
2022-07-16 20:01:54 +08:00
2022-09-29 13:06:03 +08:00
let mut original_checksum = None ;
if exclude_hash {
if let Some ( split ) = desc . split_once ( '#' ) {
desc = split . 0 ;
original_checksum = Some ( split . 1 ) ;
}
}
2022-07-16 20:01:54 +08:00
for ch in desc . as_bytes ( ) {
2020-02-15 21:27:51 +01:00
let pos = INPUT_CHARSET
2022-07-16 20:01:54 +08:00
. iter ( )
. position ( | b | b = = ch )
. ok_or ( DescriptorError ::InvalidDescriptorCharacter ( * ch ) ) ? as u64 ;
2020-02-15 21:27:51 +01:00
c = poly_mod ( c , pos & 31 ) ;
cls = cls * 3 + ( pos > > 5 ) ;
clscount + = 1 ;
if clscount = = 3 {
c = poly_mod ( c , cls ) ;
cls = 0 ;
clscount = 0 ;
}
}
if clscount > 0 {
c = poly_mod ( c , cls ) ;
}
( 0 .. 8 ) . for_each ( | _ | c = poly_mod ( c , 0 ) ) ;
c ^ = 1 ;
2022-07-16 20:01:54 +08:00
let mut checksum = [ 0_ u8 ; 8 ] ;
2020-02-15 21:27:51 +01:00
for j in 0 .. 8 {
2022-07-16 20:01:54 +08:00
checksum [ j ] = CHECKSUM_CHARSET [ ( ( c > > ( 5 * ( 7 - j ) ) ) & 31 ) as usize ] ;
2020-02-15 21:27:51 +01:00
}
2022-09-29 13:06:03 +08:00
// if input data already had a checksum, check calculated checksum against original checksum
if let Some ( original_checksum ) = original_checksum {
2022-09-29 14:24:28 +08:00
if original_checksum . as_bytes ( ) ! = checksum {
2022-09-29 13:06:03 +08:00
return Err ( DescriptorError ::InvalidDescriptorChecksum ) ;
}
}
2022-07-16 20:01:54 +08:00
Ok ( checksum )
}
2022-10-25 11:20:22 -05:00
/// Compute the checksum bytes of a descriptor, excludes any existing checksum in the descriptor string from the calculation
2022-10-24 12:05:49 -05:00
pub fn calc_checksum_bytes ( desc : & str ) -> Result < [ u8 ; 8 ] , DescriptorError > {
calc_checksum_bytes_internal ( desc , true )
}
2022-10-25 11:20:22 -05:00
/// Compute the checksum of a descriptor, excludes any existing checksum in the descriptor string from the calculation
2022-10-24 12:05:49 -05:00
pub fn calc_checksum ( desc : & str ) -> Result < String , DescriptorError > {
// unsafe is okay here as the checksum only uses bytes in `CHECKSUM_CHARSET`
calc_checksum_bytes_internal ( desc , true )
. map ( | b | unsafe { String ::from_utf8_unchecked ( b . to_vec ( ) ) } )
}
// TODO in release 0.25.0, remove get_checksum_bytes and get_checksum
// TODO in release 0.25.0, consolidate calc_checksum_bytes_internal into calc_checksum_bytes
/// Compute the checksum bytes of a descriptor
#[ deprecated(
since = " 0.24.0 " ,
2022-10-25 11:20:22 -05:00
note = " Use new `calc_checksum_bytes` function which excludes any existing checksum in the descriptor string before calculating the checksum hash bytes. See https://github.com/bitcoindevkit/bdk/pull/765. "
2022-10-24 12:05:49 -05:00
) ]
pub fn get_checksum_bytes ( desc : & str ) -> Result < [ u8 ; 8 ] , DescriptorError > {
calc_checksum_bytes_internal ( desc , false )
}
2022-07-16 20:01:54 +08:00
/// Compute the checksum of a descriptor
2022-10-24 12:05:49 -05:00
#[ deprecated(
since = " 0.24.0 " ,
2022-10-25 11:20:22 -05:00
note = " Use new `calc_checksum` function which excludes any existing checksum in the descriptor string before calculating the checksum hash. See https://github.com/bitcoindevkit/bdk/pull/765. "
2022-10-24 12:05:49 -05:00
) ]
2022-07-16 20:01:54 +08:00
pub fn get_checksum ( desc : & str ) -> Result < String , DescriptorError > {
// unsafe is okay here as the checksum only uses bytes in `CHECKSUM_CHARSET`
2022-10-24 12:05:49 -05:00
calc_checksum_bytes_internal ( desc , false )
. map ( | b | unsafe { String ::from_utf8_unchecked ( b . to_vec ( ) ) } )
2020-02-15 21:27:51 +01:00
}
2020-10-03 14:48:13 -07:00
#[ cfg(test) ]
mod test {
use super ::* ;
2022-10-24 12:05:49 -05:00
use crate ::descriptor ::calc_checksum ;
2022-12-13 07:55:32 +10:00
use assert_matches ::assert_matches ;
2020-10-03 14:48:13 -07:00
2022-10-24 12:05:49 -05:00
// test calc_checksum() function; it should return the same value as Bitcoin Core
2020-10-03 14:48:13 -07:00
#[ test ]
2022-10-24 12:05:49 -05:00
fn test_calc_checksum ( ) {
2020-10-03 14:48:13 -07:00
let desc = " wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*) " ;
2022-10-24 12:05:49 -05:00
assert_eq! ( calc_checksum ( desc ) . unwrap ( ) , " tqz0nc62 " ) ;
2020-10-03 14:48:13 -07:00
let desc = " pkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/44'/1'/0'/0/*) " ;
2022-10-24 12:05:49 -05:00
assert_eq! ( calc_checksum ( desc ) . unwrap ( ) , " lasegmfs " ) ;
}
// test calc_checksum() function; it should return the same value as Bitcoin Core even if the
// descriptor string includes a checksum hash
#[ test ]
fn test_calc_checksum_with_checksum_hash ( ) {
let desc = " wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#tqz0nc62 " ;
assert_eq! ( calc_checksum ( desc ) . unwrap ( ) , " tqz0nc62 " ) ;
let desc = " pkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/44'/1'/0'/0/*)#lasegmfs " ;
assert_eq! ( calc_checksum ( desc ) . unwrap ( ) , " lasegmfs " ) ;
2022-10-25 11:20:22 -05:00
let desc = " wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#tqz0nc26 " ;
2022-12-13 07:55:32 +10:00
assert_matches! (
calc_checksum ( desc ) ,
Err ( DescriptorError ::InvalidDescriptorChecksum )
) ;
2022-10-25 11:20:22 -05:00
let desc = " pkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/44'/1'/0'/0/*)#lasegmsf " ;
2022-12-13 07:55:32 +10:00
assert_matches! (
calc_checksum ( desc ) ,
Err ( DescriptorError ::InvalidDescriptorChecksum )
) ;
2020-10-03 14:48:13 -07:00
}
#[ test ]
2022-10-24 12:05:49 -05:00
fn test_calc_checksum_invalid_character ( ) {
2022-07-16 20:01:54 +08:00
let sparkle_heart = unsafe { std ::str ::from_utf8_unchecked ( & [ 240 , 159 , 146 , 150 ] ) } ;
2020-10-03 14:48:13 -07:00
let invalid_desc = format! ( " wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcL {} fjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*) " , sparkle_heart ) ;
2022-12-13 07:55:32 +10:00
assert_matches! (
calc_checksum ( & invalid_desc ) ,
Err ( DescriptorError ::InvalidDescriptorCharacter ( invalid_char ) ) if invalid_char = = sparkle_heart . as_bytes ( ) [ 0 ]
) ;
2020-10-03 14:48:13 -07:00
}
}