Skip to main content

bsv_primitives/chainhash/
mod.rs

1//! Chain hash type for transaction and block identification.
2//!
3//! Provides a `Hash` type — a 32-byte array displayed as byte-reversed hex,
4//! matching Bitcoin's convention for transaction IDs and block hashes.
5//! Ported from the Go BSV SDK (`chainhash` package).
6
7use crate::hash::sha256;
8use crate::PrimitivesError;
9use serde::{Deserialize, Deserializer, Serialize, Serializer};
10use std::fmt;
11use std::str::FromStr;
12
13/// Size of a Hash in bytes.
14pub const HASH_SIZE: usize = 32;
15
16/// Maximum hex string length for a Hash (64 hex characters).
17pub const MAX_HASH_STRING_SIZE: usize = HASH_SIZE * 2;
18
19/// A 32-byte hash used for transaction IDs, block hashes, and merkle trees.
20///
21/// When displayed as a string, the bytes are reversed to match Bitcoin's
22/// standard representation (little-endian internal, big-endian display).
23#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
24pub struct Hash([u8; HASH_SIZE]);
25
26impl Hash {
27    /// Create a Hash from a raw 32-byte array.
28    ///
29    /// The bytes are stored as-is (internal byte order).
30    ///
31    /// # Arguments
32    /// * `bytes` - The 32 bytes in internal (little-endian) order.
33    ///
34    /// # Returns
35    /// A new `Hash`.
36    pub fn new(bytes: [u8; HASH_SIZE]) -> Self {
37        Hash(bytes)
38    }
39
40    /// Create a Hash from a byte slice.
41    ///
42    /// # Arguments
43    /// * `bytes` - A slice that must be exactly 32 bytes.
44    ///
45    /// # Returns
46    /// `Ok(Hash)` if the slice is 32 bytes, or an error otherwise.
47    pub fn from_bytes(bytes: &[u8]) -> Result<Self, PrimitivesError> {
48        if bytes.len() != HASH_SIZE {
49            return Err(PrimitivesError::InvalidHash(format!(
50                "invalid hash length of {}, want {}",
51                bytes.len(),
52                HASH_SIZE
53            )));
54        }
55        let mut arr = [0u8; HASH_SIZE];
56        arr.copy_from_slice(bytes);
57        Ok(Hash(arr))
58    }
59
60    /// Create a Hash from a byte-reversed hex string.
61    ///
62    /// This matches the Go SDK's `NewHashFromHex` / `Decode` function.
63    /// The hex string represents bytes in display order (reversed from
64    /// internal storage). Short strings are zero-padded on the high end.
65    ///
66    /// # Arguments
67    /// * `hex_str` - A hex string of up to 64 characters.
68    ///
69    /// # Returns
70    /// `Ok(Hash)` on success, or an error for invalid input.
71    pub fn from_hex(hex_str: &str) -> Result<Self, PrimitivesError> {
72        if hex_str.is_empty() {
73            return Ok(Hash::default());
74        }
75        if hex_str.len() > MAX_HASH_STRING_SIZE {
76            return Err(PrimitivesError::InvalidHash(format!(
77                "max hash string length is {} bytes",
78                MAX_HASH_STRING_SIZE
79            )));
80        }
81
82        // Pad to even length if needed.
83        let padded = if !hex_str.len().is_multiple_of(2) {
84            format!("0{}", hex_str)
85        } else {
86            hex_str.to_string()
87        };
88
89        // Decode hex into a temporary buffer, right-aligned in a 32-byte array.
90        let decoded = hex::decode(&padded)?;
91        let mut reversed_hash = [0u8; HASH_SIZE];
92        let offset = HASH_SIZE - decoded.len();
93        reversed_hash[offset..].copy_from_slice(&decoded);
94
95        // Reverse to get internal byte order.
96        let mut dst = [0u8; HASH_SIZE];
97        for i in 0..HASH_SIZE {
98            dst[i] = reversed_hash[HASH_SIZE - 1 - i];
99        }
100
101        Ok(Hash(dst))
102    }
103
104    /// Return a copy of the internal bytes.
105    ///
106    /// # Returns
107    /// A `Vec<u8>` containing the 32 hash bytes in internal order.
108    pub fn clone_bytes(&self) -> Vec<u8> {
109        self.0.to_vec()
110    }
111
112    /// Set the hash bytes from a slice.
113    ///
114    /// # Arguments
115    /// * `bytes` - A slice that must be exactly 32 bytes.
116    ///
117    /// # Returns
118    /// `Ok(())` on success, or an error if the length is wrong.
119    pub fn set_bytes(&mut self, bytes: &[u8]) -> Result<(), PrimitivesError> {
120        if bytes.len() != HASH_SIZE {
121            return Err(PrimitivesError::InvalidHash(format!(
122                "invalid hash length of {}, want {}",
123                bytes.len(),
124                HASH_SIZE
125            )));
126        }
127        self.0.copy_from_slice(bytes);
128        Ok(())
129    }
130
131    /// Check equality with another Hash reference.
132    ///
133    /// Handles the None/null case like the Go SDK's `IsEqual` method.
134    ///
135    /// # Arguments
136    /// * `other` - An optional reference to another Hash.
137    ///
138    /// # Returns
139    /// `true` if both hashes are equal.
140    pub fn is_equal(&self, other: Option<&Hash>) -> bool {
141        match other {
142            Some(h) => self.0 == h.0,
143            None => false,
144        }
145    }
146
147    /// Access the internal byte array as a reference.
148    ///
149    /// # Returns
150    /// A reference to the 32-byte internal array.
151    pub fn as_bytes(&self) -> &[u8; HASH_SIZE] {
152        &self.0
153    }
154
155    /// Return the size of the hash in bytes.
156    ///
157    /// # Returns
158    /// Always returns 32.
159    pub fn size(&self) -> usize {
160        HASH_SIZE
161    }
162}
163
164/// Display the hash as byte-reversed hex (Bitcoin convention).
165///
166/// Internal bytes `[0x06, 0xe5, ...]` display as `"...e506"`.
167impl fmt::Display for Hash {
168    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169        let mut reversed = self.0;
170        reversed.reverse();
171        write!(f, "{}", hex::encode(reversed))
172    }
173}
174
175/// Parse a byte-reversed hex string into a Hash.
176///
177/// Equivalent to `Hash::from_hex`.
178impl FromStr for Hash {
179    type Err = PrimitivesError;
180
181    fn from_str(s: &str) -> Result<Self, Self::Err> {
182        Hash::from_hex(s)
183    }
184}
185
186/// Serialize as a hex string in JSON.
187impl Serialize for Hash {
188    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
189        serializer.serialize_str(&self.to_string())
190    }
191}
192
193/// Deserialize from a hex string in JSON.
194impl<'de> Deserialize<'de> for Hash {
195    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
196        let s = String::deserialize(deserializer)?;
197        Hash::from_hex(&s).map_err(serde::de::Error::custom)
198    }
199}
200
201/// Compute SHA-256 of the input and return the result as a Hash.
202///
203/// Equivalent to the Go SDK's `chainhash.HashH`.
204///
205/// # Arguments
206/// * `data` - Byte slice to hash.
207///
208/// # Returns
209/// A `Hash` containing the raw SHA-256 digest.
210pub fn hash_h(data: &[u8]) -> Hash {
211    Hash(sha256(data))
212}
213
214/// Compute double SHA-256 of the input and return the result as a Hash.
215///
216/// Equivalent to the Go SDK's `chainhash.DoubleHashH`.
217///
218/// # Arguments
219/// * `data` - Byte slice to hash.
220///
221/// # Returns
222/// A `Hash` containing the double SHA-256 digest.
223pub fn double_hash_h(data: &[u8]) -> Hash {
224    Hash(crate::hash::sha256d(data))
225}
226
227#[cfg(test)]
228mod tests {
229    use super::*;
230
231    /// Genesis block hash bytes in internal (little-endian) order.
232    const MAIN_NET_GENESIS_HASH: Hash = Hash([
233        0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72, 0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7,
234        0x4f, 0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c, 0x68, 0xd6, 0x19, 0x00, 0x00, 0x00,
235        0x00, 0x00,
236    ]);
237
238    #[test]
239    fn test_hash_api() {
240        // Hash of block 234439 (short hex string).
241        let block_hash_str = "14a0810ac680a3eb3f82edc878cea25ec41d6b790744e5daeef";
242        let block_hash = Hash::from_hex(block_hash_str).unwrap();
243
244        // Hash of block 234440 as raw bytes.
245        let buf: [u8; 32] = [
246            0x79, 0xa6, 0x1a, 0xdb, 0xc6, 0xe5, 0xa2, 0xe1, 0x39, 0xd2, 0x71, 0x3a, 0x54, 0x6e,
247            0xc7, 0xc8, 0x75, 0x63, 0x2e, 0x75, 0xf1, 0xdf, 0x9c, 0x3f, 0xa6, 0x01, 0x00, 0x00,
248            0x00, 0x00, 0x00, 0x00,
249        ];
250
251        let hash = Hash::from_bytes(&buf).unwrap();
252
253        // Ensure proper size.
254        assert_eq!(hash.size(), HASH_SIZE);
255
256        // Ensure contents match.
257        assert_eq!(hash.as_bytes(), &buf);
258
259        // Block 234440 should not equal block 234439.
260        assert!(!hash.is_equal(Some(&block_hash)));
261
262        // Set hash from cloned bytes of block_hash.
263        let mut hash2 = hash;
264        hash2.set_bytes(&block_hash.clone_bytes()).unwrap();
265        assert!(hash2.is_equal(Some(&block_hash)));
266
267        // is_equal with None returns false for non-default hash.
268        assert!(!hash.is_equal(None));
269
270        // Invalid size for set_bytes.
271        let mut h = Hash::default();
272        assert!(h.set_bytes(&[0x00]).is_err());
273
274        // Invalid size for from_bytes.
275        let invalid = vec![0u8; HASH_SIZE + 1];
276        assert!(Hash::from_bytes(&invalid).is_err());
277    }
278
279    #[test]
280    fn test_hash_string() {
281        // Block 100000 hash in internal byte order.
282        let hash = Hash::new([
283            0x06, 0xe5, 0x33, 0xfd, 0x1a, 0xda, 0x86, 0x39, 0x1f, 0x3f, 0x6c, 0x34, 0x32, 0x04,
284            0xb0, 0xd2, 0x78, 0xd4, 0xaa, 0xec, 0x1c, 0x0b, 0x20, 0xaa, 0x27, 0xba, 0x03, 0x00,
285            0x00, 0x00, 0x00, 0x00,
286        ]);
287        assert_eq!(
288            hash.to_string(),
289            "000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506"
290        );
291    }
292
293    #[test]
294    fn test_new_hash_from_hex() {
295        // Genesis hash from hex string.
296        let result =
297            Hash::from_hex("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")
298                .unwrap();
299        assert_eq!(result, MAIN_NET_GENESIS_HASH);
300
301        // Genesis hash with stripped leading zeros.
302        let result =
303            Hash::from_hex("19d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f").unwrap();
304        assert_eq!(result, MAIN_NET_GENESIS_HASH);
305
306        // Empty string -> zero hash.
307        let result = Hash::from_hex("").unwrap();
308        assert_eq!(result, Hash::default());
309
310        // Single digit.
311        let result = Hash::from_hex("1").unwrap();
312        let expected = Hash::new([
313            0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
314            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
315            0x00, 0x00, 0x00, 0x00,
316        ]);
317        assert_eq!(result, expected);
318
319        // Block 203707 with stripped leading zeros.
320        let result = Hash::from_hex("3264bc2ac36a60840790ba1d475d01367e7c723da941069e9dc").unwrap();
321        let expected = Hash::new([
322            0xdc, 0xe9, 0x69, 0x10, 0x94, 0xda, 0x23, 0xc7, 0xe7, 0x67, 0x13, 0xd0, 0x75, 0xd4,
323            0xa1, 0x0b, 0x79, 0x40, 0x08, 0xa6, 0x36, 0xac, 0xc2, 0x4b, 0x26, 0x03, 0x00, 0x00,
324            0x00, 0x00, 0x00, 0x00,
325        ]);
326        assert_eq!(result, expected);
327
328        // String too long.
329        let result =
330            Hash::from_hex("01234567890123456789012345678901234567890123456789012345678912345");
331        assert!(result.is_err());
332
333        // Invalid hex character.
334        let result = Hash::from_hex("abcdefg");
335        assert!(result.is_err());
336    }
337
338    #[test]
339    fn test_marshalling() {
340        /// Helper struct for JSON round-trip testing.
341        #[derive(Serialize, Deserialize)]
342        struct TestData {
343            hash: Hash,
344        }
345
346        // HashH("hello") should match Go SDK.
347        let data = TestData {
348            hash: hash_h(b"hello"),
349        };
350        assert_eq!(
351            data.hash.to_string(),
352            "24988b93623304735e42a71f5c1e161b9ee2b9c52a3be8260ea3b05fba4df22c"
353        );
354
355        // Serialize to JSON.
356        let json = serde_json::to_string(&data).unwrap();
357        assert_eq!(
358            json,
359            r#"{"hash":"24988b93623304735e42a71f5c1e161b9ee2b9c52a3be8260ea3b05fba4df22c"}"#
360        );
361
362        // Deserialize back.
363        let data2: TestData = serde_json::from_str(&json).unwrap();
364        assert_eq!(
365            data2.hash.to_string(),
366            "24988b93623304735e42a71f5c1e161b9ee2b9c52a3be8260ea3b05fba4df22c"
367        );
368    }
369}