Skip to main content

zingolabs_zewif/
block_hash.rs

1use crate::HexParseError;
2use anyhow::{Context, Result};
3use bc_envelope::prelude::*;
4use std::{
5    fmt,
6    io::{self, Read, Write},
7};
8
9/// A transaction identifier (BlockHash) represented as a 32-byte hash.
10///
11/// `BlockHash` is a specialized wrapper around a 32-byte array representing a block's
12/// unique identifier in the Zcash blockchain.
13///
14/// # Zcash Concept Relation
15/// In Zcash (and Bitcoin-derived cryptocurrencies), transaction IDs are critical identifiers
16/// used to reference transactions throughout the protocol:
17/// - In transaction inputs to reference previous outputs being spent
18/// - In block data structures to identify included transactions
19/// - In client APIs and explorers to look up transaction details
20///
21/// Block hashes are displayed in reverse byte order by convention (to match Bitcoin's historical
22/// display format), while stored internally in little-endian order.
23///
24/// # Data Preservation
25/// The `BlockHash` type preserves the exact 32-byte transaction identifier as found in wallet
26/// data files, ensuring that transaction references maintain their cryptographic integrity
27/// during wallet migrations.
28///
29/// # Examples
30/// ```
31/// # use zewif::BlockHash;
32/// // Create a BlockHash from a byte array
33/// let tx_bytes = [0u8; 32];
34/// let txid = BlockHash::from_bytes(tx_bytes);
35///
36/// // Display the BlockHash in the conventional reversed format used by explorers
37/// // Note: this would display as a string of 64 hex characters (zeros in this example)
38/// println!("Block Hash: {}", txid);
39/// ```
40#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
41pub struct BlockHash([u8; 32]);
42
43impl fmt::Debug for BlockHash {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        write!(f, "BlockHash({})", self)
46    }
47}
48
49impl fmt::Display for BlockHash {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        // The (byte-flipped) hex string is more useful than the raw bytes, because we can
52        // look that up in RPC methods and block explorers.
53        let mut data = self.0;
54        data.reverse();
55        f.write_str(&hex::encode(data))
56    }
57}
58
59impl AsRef<[u8; 32]> for BlockHash {
60    fn as_ref(&self) -> &[u8; 32] {
61        &self.0
62    }
63}
64
65impl From<BlockHash> for [u8; 32] {
66    fn from(value: BlockHash) -> Self {
67        value.0
68    }
69}
70
71impl BlockHash {
72    /// Creates a new `BlockHash` from a 32-byte array.
73    ///
74    /// This is the primary constructor for `BlockHash` when you have the raw transaction
75    /// hash available.
76    ///
77    /// # Examples
78    /// ```
79    /// # use zewif::BlockHash;
80    /// // Usually this would be a real transaction hash
81    /// let bytes = [0u8; 32];
82    /// let txid = BlockHash::from_bytes(bytes);
83    /// ```
84    pub fn from_bytes(bytes: [u8; 32]) -> Self {
85        BlockHash(bytes)
86    }
87
88    /// Parses a `BlockHash` from a canonically-encoded (byte-reversed) hexadecimal string.
89    ///
90    /// # Examples
91    /// ```
92    /// # use zewif::BlockHash;
93    ///
94    /// let hex = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f";
95    /// let block_hash = BlockHash::from_hex(hex).unwrap();
96    /// assert_eq!(block_hash.as_ref()[0], 0x6f);
97    /// assert_eq!(format!("{}", block_hash), hex);
98    /// ```
99    pub fn from_hex(hex: &str) -> Result<Self, HexParseError> {
100        let mut data = hex::decode(hex).map_err(crate::HexParseError::HexInvalid)?;
101        data.reverse();
102
103        Ok(Self(<[u8; 32]>::try_from(&data[..]).map_err(|_| {
104            crate::HexParseError::SliceInvalid {
105                expected: 64,
106                actual: hex.len(),
107            }
108        })?))
109    }
110
111    /// Reads a `BlockHash` from any source implementing the `Read` trait.
112    ///
113    /// This method is useful when reading transaction IDs directly from files
114    /// or other byte streams.
115    ///
116    /// # Errors
117    /// Returns an IO error if reading fails or if there aren't enough bytes available.
118    ///
119    /// # Examples
120    /// ```no_run
121    /// # use std::io::Cursor;
122    /// # use zewif::BlockHash;
123    /// # use anyhow::Result;
124    /// #
125    /// # fn example() -> Result<()> {
126    /// // Create a cursor with 32 bytes
127    /// let data = vec![0u8; 32];
128    /// let mut cursor = Cursor::new(data);
129    ///
130    /// // Read a BlockHash from the cursor
131    /// let txid = BlockHash::read(&mut cursor)?;
132    /// # Ok(())
133    /// # }
134    /// ```
135    pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
136        let mut hash = [0u8; 32];
137        reader.read_exact(&mut hash)?;
138        Ok(BlockHash::from_bytes(hash))
139    }
140
141    /// Writes a `BlockHash` to any destination implementing the `Write` trait.
142    ///
143    /// This method is useful when serializing transaction IDs to files or
144    /// other byte streams.
145    ///
146    /// # Errors
147    /// Returns an IO error if writing fails.
148    ///
149    /// # Examples
150    /// ```no_run
151    /// # use std::io::Cursor;
152    /// # use zewif::BlockHash;
153    /// # use anyhow::Result;
154    /// #
155    /// # fn example() -> Result<()> {
156    /// let txid = BlockHash::from_bytes([0u8; 32]);
157    /// let mut buffer = Vec::new();
158    ///
159    /// // Write the BlockHash to the buffer
160    /// txid.write(&mut buffer)?;
161    ///
162    /// // The buffer now contains the 32-byte transaction ID
163    /// assert_eq!(buffer.len(), 32);
164    /// # Ok(())
165    /// # }
166    /// ```
167    pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
168        writer.write_all(&self.0)?;
169        Ok(())
170    }
171}
172
173impl From<BlockHash> for CBOR {
174    fn from(value: BlockHash) -> Self {
175        CBOR::to_byte_string(value.0)
176    }
177}
178
179impl From<&BlockHash> for CBOR {
180    fn from(value: &BlockHash) -> Self {
181        CBOR::to_byte_string(value.0)
182    }
183}
184
185impl TryFrom<CBOR> for BlockHash {
186    type Error = dcbor::Error;
187
188    fn try_from(cbor: CBOR) -> dcbor::Result<Self> {
189        let bytes = cbor.try_into_byte_string()?;
190        if bytes.len() != 32 {
191            return Err(format!(
192                "Invalid BlockHash length: expected 32 bytes, got {}",
193                bytes.len()
194            )
195            .into());
196        }
197        let mut hash = [0u8; 32];
198        hash.copy_from_slice(&bytes);
199        Ok(BlockHash::from_bytes(hash))
200    }
201}
202
203impl From<BlockHash> for Envelope {
204    fn from(value: BlockHash) -> Self {
205        Envelope::new(CBOR::from(value))
206    }
207}
208
209impl TryFrom<Envelope> for BlockHash {
210    type Error = anyhow::Error;
211
212    fn try_from(envelope: Envelope) -> Result<Self, Self::Error> {
213        envelope.extract_subject().context("BlockHash")
214    }
215}
216
217#[cfg(test)]
218mod tests {
219    use crate::{test_cbor_roundtrip, test_envelope_roundtrip};
220
221    use super::BlockHash;
222
223    impl crate::RandomInstance for BlockHash {
224        fn random() -> Self {
225            let mut rng = bc_rand::thread_rng();
226            Self(bc_rand::rng_random_array(&mut rng))
227        }
228    }
229
230    test_cbor_roundtrip!(BlockHash);
231    test_envelope_roundtrip!(BlockHash);
232}