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