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}