zebra_chain/transaction/
hash.rs

1//! Transaction identifiers for Zcash.
2//!
3//! Zcash has two different transaction identifiers, with different widths:
4//! * [`struct@Hash`]: a 32-byte transaction ID, which uniquely identifies mined transactions
5//!   (transactions that have been committed to the blockchain in blocks), and
6//! * [`WtxId`]: a 64-byte witnessed transaction ID, which uniquely identifies unmined transactions
7//!   (transactions that are sent by wallets or stored in node mempools).
8//!
9//! Transaction version 5 uses both these unique identifiers:
10//! * [`struct@Hash`] uniquely identifies the effects of a v5 transaction (spends and outputs),
11//!   so it uniquely identifies the transaction's data after it has been mined into a block;
12//! * [`WtxId`] uniquely identifies the effects and authorizing data of a v5 transaction
13//!   (signatures, proofs, and scripts), so it uniquely identifies the transaction's data
14//!   outside a block. (For example, transactions produced by Zcash wallets, or in node mempools.)
15//!
16//! Transaction versions 1-4 are uniquely identified by legacy [`struct@Hash`] transaction IDs,
17//! whether they have been mined or not. So Zebra, and the Zcash network protocol,
18//! don't use witnessed transaction IDs for them.
19//!
20//! There is no unique identifier that only covers the effects of a v1-4 transaction,
21//! so their legacy IDs are malleable, if submitted with different authorizing data.
22//! So the same spends and outputs can have a completely different [`struct@Hash`].
23//!
24//! Zebra's [`UnminedTxId`][1] and [`UnminedTx`][1] enums provide the correct
25//! unique ID for unmined transactions. They can be used to handle transactions
26//! regardless of version, and get the [`WtxId`] or [`struct@Hash`] when
27//! required.
28//!
29//! [1]: crate::transaction::UnminedTx
30
31use std::{fmt, sync::Arc};
32
33#[cfg(any(test, feature = "proptest-impl"))]
34use proptest_derive::Arbitrary;
35
36use hex::{FromHex, ToHex};
37
38use crate::serialization::{
39    ReadZcashExt, SerializationError, WriteZcashExt, ZcashDeserialize, ZcashSerialize,
40};
41
42use super::{txid::TxIdBuilder, AuthDigest, Transaction};
43
44/// A transaction ID, which uniquely identifies mined v5 transactions,
45/// and all v1-v4 transactions.
46///
47/// Note: Zebra displays transaction and block hashes in big-endian byte-order,
48/// following the u256 convention set by Bitcoin and zcashd.
49///
50/// "The transaction ID of a version 4 or earlier transaction is the SHA-256d hash
51/// of the transaction encoding in the pre-v5 format described above.
52///
53/// The transaction ID of a version 5 transaction is as defined in [ZIP-244]."
54/// [Spec: Transaction Identifiers]
55///
56/// [ZIP-244]: https://zips.z.cash/zip-0244
57/// [Spec: Transaction Identifiers]: https://zips.z.cash/protocol/protocol.pdf#txnidentifiers
58#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
59#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
60pub struct Hash(pub [u8; 32]);
61
62impl From<Transaction> for Hash {
63    fn from(transaction: Transaction) -> Self {
64        // use the ref implementation, to avoid cloning the transaction
65        Hash::from(&transaction)
66    }
67}
68
69impl From<&Transaction> for Hash {
70    fn from(transaction: &Transaction) -> Self {
71        let hasher = TxIdBuilder::new(transaction);
72        hasher
73            .txid()
74            .expect("zcash_primitives and Zebra transaction formats must be compatible")
75    }
76}
77
78impl From<Arc<Transaction>> for Hash {
79    fn from(transaction: Arc<Transaction>) -> Self {
80        Hash::from(transaction.as_ref())
81    }
82}
83
84impl From<[u8; 32]> for Hash {
85    fn from(bytes: [u8; 32]) -> Self {
86        Self(bytes)
87    }
88}
89
90impl From<Hash> for [u8; 32] {
91    fn from(hash: Hash) -> Self {
92        hash.0
93    }
94}
95
96impl From<&Hash> for [u8; 32] {
97    fn from(hash: &Hash) -> Self {
98        (*hash).into()
99    }
100}
101
102impl Hash {
103    /// Return the hash bytes in big-endian byte-order suitable for printing out byte by byte.
104    ///
105    /// Zebra displays transaction and block hashes in big-endian byte-order,
106    /// following the u256 convention set by Bitcoin and zcashd.
107    pub fn bytes_in_display_order(&self) -> [u8; 32] {
108        let mut reversed_bytes = self.0;
109        reversed_bytes.reverse();
110        reversed_bytes
111    }
112
113    /// Convert bytes in big-endian byte-order into a [`transaction::Hash`](crate::transaction::Hash).
114    ///
115    /// Zebra displays transaction and block hashes in big-endian byte-order,
116    /// following the u256 convention set by Bitcoin and zcashd.
117    pub fn from_bytes_in_display_order(bytes_in_display_order: &[u8; 32]) -> Hash {
118        let mut internal_byte_order = *bytes_in_display_order;
119        internal_byte_order.reverse();
120
121        Hash(internal_byte_order)
122    }
123}
124
125impl ToHex for &Hash {
126    fn encode_hex<T: FromIterator<char>>(&self) -> T {
127        self.bytes_in_display_order().encode_hex()
128    }
129
130    fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
131        self.bytes_in_display_order().encode_hex_upper()
132    }
133}
134
135impl ToHex for Hash {
136    fn encode_hex<T: FromIterator<char>>(&self) -> T {
137        (&self).encode_hex()
138    }
139
140    fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
141        (&self).encode_hex_upper()
142    }
143}
144
145impl FromHex for Hash {
146    type Error = <[u8; 32] as FromHex>::Error;
147
148    fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
149        let mut hash = <[u8; 32]>::from_hex(hex)?;
150        hash.reverse();
151
152        Ok(hash.into())
153    }
154}
155
156impl fmt::Display for Hash {
157    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
158        f.write_str(&self.encode_hex::<String>())
159    }
160}
161
162impl fmt::Debug for Hash {
163    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
164        f.debug_tuple("transaction::Hash")
165            .field(&self.encode_hex::<String>())
166            .finish()
167    }
168}
169
170impl std::str::FromStr for Hash {
171    type Err = SerializationError;
172
173    fn from_str(s: &str) -> Result<Self, Self::Err> {
174        let mut bytes = [0; 32];
175        if hex::decode_to_slice(s, &mut bytes[..]).is_err() {
176            Err(SerializationError::Parse("hex decoding error"))
177        } else {
178            bytes.reverse();
179            Ok(Hash(bytes))
180        }
181    }
182}
183
184impl ZcashSerialize for Hash {
185    fn zcash_serialize<W: std::io::Write>(&self, mut writer: W) -> Result<(), std::io::Error> {
186        writer.write_32_bytes(&self.into())
187    }
188}
189
190impl ZcashDeserialize for Hash {
191    fn zcash_deserialize<R: std::io::Read>(mut reader: R) -> Result<Self, SerializationError> {
192        Ok(reader.read_32_bytes()?.into())
193    }
194}
195
196/// A witnessed transaction ID, which uniquely identifies unmined v5 transactions.
197///
198/// Witnessed transaction IDs are not used for transaction versions 1-4.
199///
200/// "A v5 transaction also has a wtxid (used for example in the peer-to-peer protocol)
201/// as defined in [ZIP-239]."
202/// [Spec: Transaction Identifiers]
203///
204/// [ZIP-239]: https://zips.z.cash/zip-0239
205/// [Spec: Transaction Identifiers]: https://zips.z.cash/protocol/protocol.pdf#txnidentifiers
206#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
207#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
208pub struct WtxId {
209    /// The non-malleable transaction ID for this transaction's effects.
210    pub id: Hash,
211
212    /// The authorizing data digest for this transactions signatures, proofs, and scripts.
213    pub auth_digest: AuthDigest,
214}
215
216impl WtxId {
217    /// Return this witnessed transaction ID as a serialized byte array.
218    pub fn as_bytes(&self) -> [u8; 64] {
219        <[u8; 64]>::from(self)
220    }
221}
222
223impl From<Transaction> for WtxId {
224    /// Computes the witnessed transaction ID for a transaction.
225    ///
226    /// # Panics
227    ///
228    /// If passed a pre-v5 transaction.
229    fn from(transaction: Transaction) -> Self {
230        // use the ref implementation, to avoid cloning the transaction
231        WtxId::from(&transaction)
232    }
233}
234
235impl From<&Transaction> for WtxId {
236    /// Computes the witnessed transaction ID for a transaction.
237    ///
238    /// # Panics
239    ///
240    /// If passed a pre-v5 transaction.
241    fn from(transaction: &Transaction) -> Self {
242        Self {
243            id: transaction.into(),
244            auth_digest: transaction.into(),
245        }
246    }
247}
248
249impl From<Arc<Transaction>> for WtxId {
250    /// Computes the witnessed transaction ID for a transaction.
251    ///
252    /// # Panics
253    ///
254    /// If passed a pre-v5 transaction.
255    fn from(transaction: Arc<Transaction>) -> Self {
256        transaction.as_ref().into()
257    }
258}
259
260impl From<[u8; 64]> for WtxId {
261    fn from(bytes: [u8; 64]) -> Self {
262        let id: [u8; 32] = bytes[0..32].try_into().expect("length is 64");
263        let auth_digest: [u8; 32] = bytes[32..64].try_into().expect("length is 64");
264
265        Self {
266            id: id.into(),
267            auth_digest: auth_digest.into(),
268        }
269    }
270}
271
272impl From<WtxId> for [u8; 64] {
273    fn from(wtx_id: WtxId) -> Self {
274        let mut bytes = [0; 64];
275        let (id, auth_digest) = bytes.split_at_mut(32);
276
277        id.copy_from_slice(&wtx_id.id.0);
278        auth_digest.copy_from_slice(&wtx_id.auth_digest.0);
279
280        bytes
281    }
282}
283
284impl From<&WtxId> for [u8; 64] {
285    fn from(wtx_id: &WtxId) -> Self {
286        (*wtx_id).into()
287    }
288}
289
290impl TryFrom<&[u8]> for WtxId {
291    type Error = SerializationError;
292
293    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
294        let bytes: [u8; 64] = bytes.try_into()?;
295
296        Ok(bytes.into())
297    }
298}
299
300impl TryFrom<Vec<u8>> for WtxId {
301    type Error = SerializationError;
302
303    fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
304        bytes.as_slice().try_into()
305    }
306}
307
308impl TryFrom<&Vec<u8>> for WtxId {
309    type Error = SerializationError;
310
311    fn try_from(bytes: &Vec<u8>) -> Result<Self, Self::Error> {
312        bytes.clone().try_into()
313    }
314}
315
316impl fmt::Display for WtxId {
317    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
318        f.write_str(&self.id.to_string())?;
319        f.write_str(&self.auth_digest.to_string())
320    }
321}
322
323impl std::str::FromStr for WtxId {
324    type Err = SerializationError;
325
326    fn from_str(s: &str) -> Result<Self, Self::Err> {
327        // we need to split using bytes,
328        // because str::split_at panics if it splits a UTF-8 codepoint
329        let s = s.as_bytes();
330
331        if s.len() == 128 {
332            let (id, auth_digest) = s.split_at(64);
333            let id = std::str::from_utf8(id)?;
334            let auth_digest = std::str::from_utf8(auth_digest)?;
335
336            Ok(Self {
337                id: id.parse()?,
338                auth_digest: auth_digest.parse()?,
339            })
340        } else {
341            Err(SerializationError::Parse(
342                "wrong length for WtxId hex string",
343            ))
344        }
345    }
346}
347
348impl ZcashSerialize for WtxId {
349    fn zcash_serialize<W: std::io::Write>(&self, mut writer: W) -> Result<(), std::io::Error> {
350        writer.write_64_bytes(&self.into())
351    }
352}
353
354impl ZcashDeserialize for WtxId {
355    fn zcash_deserialize<R: std::io::Read>(mut reader: R) -> Result<Self, SerializationError> {
356        Ok(reader.read_64_bytes()?.into())
357    }
358}