diff --git a/Cargo.lock b/Cargo.lock --- a/Cargo.lock +++ b/Cargo.lock @@ -5,3 +5,19 @@ [[package]] name = "bitcoinsuite-core" version = "0.1.0" +dependencies = [ + "hex", + "hex-literal", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" diff --git a/chronik/bitcoinsuite-core/Cargo.toml b/chronik/bitcoinsuite-core/Cargo.toml --- a/chronik/bitcoinsuite-core/Cargo.toml +++ b/chronik/bitcoinsuite-core/Cargo.toml @@ -6,3 +6,11 @@ edition = "2021" rust-version = "1.61.0" license = "MIT" + +[dependencies] +# En-/decode byte strings from/to hex +hex = "0.4" + +[dev-dependencies] +# hex!() macro for byte array hex literals +hex-literal = "0.3" diff --git a/chronik/bitcoinsuite-core/src/hash.rs b/chronik/bitcoinsuite-core/src/hash.rs new file mode 100644 --- /dev/null +++ b/chronik/bitcoinsuite-core/src/hash.rs @@ -0,0 +1,330 @@ +// Copyright (c) 2022 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +//! Module containing a [`Hashed`] trait to abstract over different hashes, plus +//! implementations for a bunch of hashes. + +use std::{cmp::Ordering, fmt::Debug, hash::Hash}; + +/// Trait for structs containing the result of a cryptographic hash function, +/// like SHA-256, RIPEMD-160 etc. With this trait, we can abstractly write code +/// that varies for different hash functions. +/// +/// Allows type-safe handling of hashes, so e.g. [`Sha256`] and [`Sha256d`] +/// can't be mixed. +/// +/// It is designed to force making endianness explicit as much as possible, as +/// the different protocols used with Bitcoin ABC use varying endianness: +/// - In the Bitcoin protocol: +/// - Byte order is little-endian +/// - Hex representation is big-endian +/// - In the SLP protocol: +/// - Byte order is big-endian +/// - Hex representation is big-endian +/// +/// Internally, bytes are required to be represented little-endian, which is why +/// [`Hashed::as_le_bytes`] exists, but `as_be_bytes` does not. +/// +/// Naming mimicks functions like [`i32::from_be_bytes`], [`i32::to_le_bytes`] +/// etc. +pub trait Hashed +where + Self: AsRef<[u8]> + + Clone + + Debug + + Eq + + Hash + + Ord + + PartialEq + + PartialOrd + + Sized, +{ + /// Size of the hash, in bytes. + /// + /// ``` + /// # use bitcoinsuite_core::hash::{ + /// # Hashed, Ripemd160, Sha256, Sha256d, ShaRmd160, + /// # }; + /// assert_eq!(Ripemd160::SIZE, 20); + /// assert_eq!(Sha256::SIZE, 32); + /// assert_eq!(Sha256d::SIZE, 32); + /// assert_eq!(ShaRmd160::SIZE, 20); + /// ``` + const SIZE: usize; + + /// Byte array storing the hash. Usually just `[u8; Self::SIZE]`. + /// + /// ``` + /// # use bitcoinsuite_core::hash::{ + /// # Hashed, Ripemd160, Sha256, Sha256d, ShaRmd160, + /// # }; + /// let array: ::Array = [0; 20]; + /// let array: ::Array = [0; 32]; + /// let array: ::Array = [0; 32]; + /// let array: ::Array = [0; 20]; + /// ``` + type Array: AsRef<[u8]> + AsMut<[u8]> + Clone + Default; + + /// Create hash from a little-endian array. + /// + /// ``` + /// # use bitcoinsuite_core::hash::{Ripemd160, Hashed}; + /// # use hex_literal::hex; + /// let arr_le = hex!("0011223344556677889900112233445566778899"); + /// assert_eq!(Ripemd160::from_le_bytes(arr_le), Ripemd160(arr_le)); + /// ``` + fn from_le_bytes(arr: Self::Array) -> Self; + + /// A reference to the byte array of this hash, in little-endian. + /// + /// ``` + /// # use bitcoinsuite_core::hash::{Ripemd160, Hashed}; + /// # use hex_literal::hex; + /// let arr_le = hex!("0011223344556677889900112233445566778899"); + /// assert_eq!(Ripemd160(arr_le).as_le_bytes(), &arr_le); + /// ``` + fn as_le_bytes(&self) -> &Self::Array; + + /// Create hash from a big-endian array. + /// + /// ``` + /// # use bitcoinsuite_core::hash::{Ripemd160, Hashed}; + /// # use hex_literal::hex; + /// let arr_le = hex!("0011223344556677889900112233445566778899"); + /// let arr_be = hex!("9988776655443322110099887766554433221100"); + /// assert_eq!(Ripemd160::from_be_bytes(arr_be), Ripemd160(arr_le)); + /// ``` + fn from_be_bytes(mut arr: Self::Array) -> Self { + // convert to little-endian + arr.as_mut().reverse(); + Self::from_le_bytes(arr) + } + + /// Little-endian byte array of this hash. + /// + /// ``` + /// # use bitcoinsuite_core::hash::{Ripemd160, Hashed}; + /// # use hex_literal::hex; + /// let arr_le = hex!("0011223344556677889900112233445566778899"); + /// assert_eq!(Ripemd160(arr_le).to_le_bytes(), arr_le); + /// ``` + fn to_le_bytes(&self) -> Self::Array { + self.as_le_bytes().clone() + } + + /// Big-endian byte array of this hash. + /// + /// ``` + /// # use bitcoinsuite_core::hash::{Ripemd160, Hashed}; + /// # use hex_literal::hex; + /// let arr_le = hex!("0011223344556677889900112233445566778899"); + /// let arr_be = hex!("9988776655443322110099887766554433221100"); + /// assert_eq!(Ripemd160(arr_le).to_be_bytes(), arr_be); + /// ``` + fn to_be_bytes(&self) -> Self::Array { + let mut arr = self.to_le_bytes(); + arr.as_mut().reverse(); + arr + } + + /// Hex encoding of this hash, in little-endian. + /// + /// Intended to display the "internals" of an object, as this is the byte + /// order used in the Bitcoin wire protocol. + /// + /// ``` + /// # use bitcoinsuite_core::hash::{Hashed, Sha256d}; + /// # use hex_literal::hex; + /// // Genesis hash + /// let hash = Sha256d(hex!( + /// "6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000" + /// )); + /// assert_eq!( + /// hash.hex_le(), + /// "6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000", + /// ); + /// ``` + fn hex_le(&self) -> String { + hex::encode(self.as_le_bytes()) + } + + /// Hex encoding of this hash, in big-endian. + /// + /// Block hashes, tx hashes, txids and SLP token IDs are displayed this way + /// to the user, as it is the way hexadecimal numbers are usually + /// represented by humans. + /// + /// ``` + /// # use bitcoinsuite_core::hash::{Sha256d, Hashed}; + /// # use hex_literal::hex; + /// // Genesis hash + /// let hash = Sha256d(hex!( + /// "6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000" + /// )); + /// assert_eq!( + /// hash.hex_be(), + /// "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", + /// ); + /// ``` + fn hex_be(&self) -> String { + hex::encode(&self.to_be_vec()) + } + + /// [`Vec`] of this hash, in little-endian. + /// + /// ``` + /// # use bitcoinsuite_core::hash::{Sha256d, Hashed}; + /// # use hex_literal::hex; + /// let genesis_hash_le = hex!( + /// "6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000" + /// ); + /// let hash = Sha256d(genesis_hash_le); + /// assert_eq!(hash.to_le_vec(), genesis_hash_le.to_vec()); + /// ``` + fn to_le_vec(&self) -> Vec { + self.as_le_bytes().as_ref().to_vec() + } + + /// [`Vec`] of this hash, in big-endian. + /// + /// See [`Hashed::hex_be`] for an explanation. + /// + /// ``` + /// # use bitcoinsuite_core::hash::{Sha256d, Hashed}; + /// # use hex_literal::hex; + /// let hash = Sha256d(hex!( + /// "6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000" + /// )); + /// let genesis_hash_be = hex!( + /// "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" + /// ); + /// assert_eq!(hash.to_be_vec(), genesis_hash_be.to_vec()); + /// ``` + fn to_be_vec(&self) -> Vec { + self.to_be_bytes().as_ref().to_vec() + } +} + +macro_rules! hash_algo { + ( + $(#[$attrs:meta])* + pub struct $ALGO_NAME: ident(pub [u8; $SIZE: literal]); + ) => { + $(#[$attrs])* + #[derive(Clone, Copy, Default, Eq, Hash, PartialEq)] + pub struct $ALGO_NAME(pub [u8; $SIZE]); + + impl Hashed for $ALGO_NAME { + const SIZE: usize = $SIZE; + type Array = [u8; $SIZE]; + + fn from_le_bytes(array: Self::Array) -> Self { + $ALGO_NAME(array) + } + + fn as_le_bytes(&self) -> &Self::Array { + &self.0 + } + } + + impl Debug for $ALGO_NAME { + fn fmt( + &self, + fmt: &mut std::fmt::Formatter<'_>, + ) -> std::result::Result<(), std::fmt::Error> { + write!( + fmt, + "{}(hex!({:?}))", + stringify!($ALGO_NAME), + self.hex_le(), + ) + } + } + + impl AsRef<[u8]> for $ALGO_NAME { + fn as_ref(&self) -> &[u8] { + &self.0 + } + } + + impl From<$ALGO_NAME> for [u8; $SIZE] { + fn from(hash: $ALGO_NAME) -> Self { + hash.0 + } + } + + impl PartialOrd for $ALGO_NAME { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + + impl Ord for $ALGO_NAME { + fn cmp(&self, other: &Self) -> Ordering { + // Hashes are little-endian, but [`std::cmp::Ord`] for `[u8; _]` + // compares in big-endian, therefore we have to reverse byte + // ordering when comparing. + self.to_be_bytes().cmp(&other.to_be_bytes()) + } + } + }; +} + +hash_algo! { + /// Hash of the SHA-256 algorithm as defined by NIST. See [`Hashed`]. + /// + /// Occasionally used in Bitcoin, generally [`Sha256d`] is used more often, + /// especially for block hashes, tx hashes etc. + pub struct Sha256(pub [u8; 32]); +} + +hash_algo! { + /// SHA-256 algorithm applied twice (i.e. sha256(sha256(x))), see + /// [`Sha256`]. See [`Hashed`]. + /// + /// Most commonly used hash in Bitcoin, especially for block hashes, tx + /// hashes and txids. + pub struct Sha256d(pub [u8; 32]); +} + +hash_algo! { + /// Hash of the RIPEMD-160 algorithm as certified by CRYPTREC. See + /// [`Hashed`]. + /// + /// Rarely used directly in Bitcoin, generally a combination of SHA-256 and + /// RIPEMD-160 is used more often (see [`ShaRmd160`]). + pub struct Ripemd160(pub [u8; 20]); +} + +hash_algo! { + /// SHA-256 algorithm followed by RIPEMD-160, see [`Sha256`] and + /// [`Ripemd160`]. See [`Hashed`]. + /// + /// Bitcoin uses public keys hashed by SHA-256 followed by RIPEMD-160 to + /// generate addresses. + pub struct ShaRmd160(pub [u8; 20]); +} + +#[cfg(test)] +mod tests { + use hex_literal::hex; + + use crate::hash::Ripemd160; + + #[test] + fn test_ord() { + let small = Ripemd160(hex!("0100000000000000000000000000000000000000")); + let big = Ripemd160(hex!("0000000000000000000000000000000000000001")); + assert!(small < big); + } + + #[test] + fn test_debug() { + let hash = Ripemd160(hex!("0123456789012345678901234567890123456789")); + assert_eq!( + format!("{:?}", hash), + "Ripemd160(hex!(\"0123456789012345678901234567890123456789\"))", + ); + } +} diff --git a/chronik/bitcoinsuite-core/src/lib.rs b/chronik/bitcoinsuite-core/src/lib.rs --- a/chronik/bitcoinsuite-core/src/lib.rs +++ b/chronik/bitcoinsuite-core/src/lib.rs @@ -33,3 +33,5 @@ //! //! Note: This is a general purpose library, but has been optimized for the //! usage in Chronik, an indexer for Bitcoin ABC. + +pub mod hash;