Skip to main content

mx_proto/
lib.rs

1#![doc = include_str!("../README.md")]
2
3#[allow(clippy::all)]
4#[allow(dead_code)]
5#[allow(missing_docs)]
6pub mod generated {
7    include!(concat!(env!("OUT_DIR"), "/mod.rs"));
8}
9
10#[cfg(feature = "serde")]
11pub mod json;
12
13use bech32::{Bech32, Hrp};
14use blake2::{
15    Blake2b,
16    digest::{Digest, consts::U32},
17};
18type Blake2b256 = Blake2b<U32>;
19#[cfg(feature = "serde")]
20pub use json::Transaction as JsonTransaction;
21pub use prost::Message;
22
23/// Human-readable part for `MultiversX` addresses.
24const ERD_HRP: Hrp = Hrp::parse_unchecked("erd");
25
26impl generated::proto::Transaction {
27    /// Length in bytes of the transaction hash computed by the network (BLAKE2b-256).
28    pub const HASH_SIZE: usize = 32;
29
30    /// Computes the canonical transaction hash by re-encoding the message and hashing it with
31    /// BLAKE2b-256, mirroring the logic from the Go implementation.
32    #[must_use]
33    pub fn compute_hash(&self) -> [u8; Self::HASH_SIZE] {
34        let encoded = self.encode_to_vec();
35        Self::hash_bytes(&encoded)
36    }
37
38    /// Computes the canonical transaction hash by re-encoding the message and hashing it with
39    /// BLAKE2b-256.
40    #[must_use]
41    pub fn get_tx_hash(&self) -> [u8; Self::HASH_SIZE] {
42        self.compute_hash()
43    }
44
45    /// Hashes an already serialized transaction buffer with BLAKE2b-256.
46    #[must_use]
47    pub fn hash_bytes(bytes: &[u8]) -> [u8; Self::HASH_SIZE] {
48        let digest = Blake2b256::digest(bytes);
49        digest.into()
50    }
51
52    fn bech32_address(bytes: &[u8]) -> Result<Option<String>, bech32::EncodeError> {
53        if bytes.is_empty() {
54            return Ok(None);
55        }
56
57        let encoded = bech32::encode::<Bech32>(ERD_HRP, bytes)?;
58        Ok(Some(encoded))
59    }
60
61    /// Returns the sender address encoded as an `erd` Bech32 string if present.
62    pub fn sender_address_bech32(&self) -> Result<Option<String>, bech32::EncodeError> {
63        Self::bech32_address(self.snd_addr.as_ref())
64    }
65
66    /// Returns the sender address encoded as an `erd` Bech32 string if present.
67    pub fn sender_bech32(&self) -> Result<Option<String>, bech32::EncodeError> {
68        self.sender_address_bech32()
69    }
70
71    /// Returns the receiver address encoded as an `erd` Bech32 string if present.
72    pub fn receiver_address_bech32(&self) -> Result<Option<String>, bech32::EncodeError> {
73        Self::bech32_address(self.rcv_addr.as_ref())
74    }
75
76    /// Returns the receiver address encoded as an `erd` Bech32 string if present.
77    pub fn receiver_bech32(&self) -> Result<Option<String>, bech32::EncodeError> {
78        self.receiver_address_bech32()
79    }
80
81    /// Returns the guardian address encoded as an `erd` Bech32 string if present.
82    pub fn guardian_address_bech32(&self) -> Result<Option<String>, bech32::EncodeError> {
83        Self::bech32_address(self.guardian_addr.as_ref())
84    }
85
86    /// Returns the guardian address encoded as an `erd` Bech32 string if present.
87    pub fn guardian_bech32(&self) -> Result<Option<String>, bech32::EncodeError> {
88        self.guardian_address_bech32()
89    }
90
91    /// Returns the relayer address encoded as an `erd` Bech32 string if present.
92    pub fn relayer_address_bech32(&self) -> Result<Option<String>, bech32::EncodeError> {
93        Self::bech32_address(self.relayer_addr.as_ref())
94    }
95
96    /// Returns the relayer address encoded as an `erd` Bech32 string if present.
97    pub fn relayer_bech32(&self) -> Result<Option<String>, bech32::EncodeError> {
98        self.relayer_address_bech32()
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::ERD_HRP;
105    use super::generated::proto::Transaction;
106    use bech32::Bech32;
107    use hex::FromHex;
108    use prost::Message;
109    use prost::bytes::Bytes;
110
111    #[test]
112    fn hash_bytes_matches_known_digests() {
113        let expected_empty = <[u8; Transaction::HASH_SIZE]>::from_hex(
114            "0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8",
115        )
116        .unwrap();
117        assert_eq!(Transaction::hash_bytes(b""), expected_empty);
118
119        let expected_hello = <[u8; Transaction::HASH_SIZE]>::from_hex(
120            "256c83b297114d201b30179f3f0ef0cace9783622da5974326b436178aeef610",
121        )
122        .unwrap();
123        assert_eq!(Transaction::hash_bytes(b"hello world"), expected_hello);
124    }
125
126    #[test]
127    fn compute_hash_reencodes_transaction_like_go() {
128        let tx = Transaction {
129            nonce: 1,
130            value: Bytes::new(),
131            rcv_addr: Bytes::new(),
132            rcv_user_name: Bytes::new(),
133            snd_addr: Bytes::new(),
134            snd_user_name: Bytes::new(),
135            gas_price: 0,
136            gas_limit: 0,
137            data: Bytes::new(),
138            chain_id: Bytes::new(),
139            version: 0,
140            signature: Bytes::new(),
141            options: 0,
142            guardian_addr: Bytes::new(),
143            guardian_signature: Bytes::new(),
144            relayer_addr: Bytes::new(),
145            relayer_signature: Bytes::new(),
146        };
147
148        let expected = <[u8; Transaction::HASH_SIZE]>::from_hex(
149            "890b1d2195b2db958c0b3c02d09997776a5f7c0fc2daf30f3bf8469b841c30e9",
150        )
151        .unwrap();
152
153        // Double-check that the encoded bytes match the manual expectation for field #1 (nonce).
154        let encoded = tx.encode_to_vec();
155        assert_eq!(encoded, [0x08, 0x01]);
156
157        assert_eq!(tx.compute_hash(), expected);
158    }
159
160    #[test]
161    fn bech32_address_helpers_encode_addresses() {
162        let sender_vec: Vec<u8> = (0..32).collect();
163        let receiver_vec: Vec<u8> = (32..64).collect();
164        let relayer_vec: Vec<u8> = (64..96).collect();
165
166        let tx = Transaction {
167            snd_addr: Bytes::from(sender_vec.clone()),
168            rcv_addr: Bytes::from(receiver_vec.clone()),
169            relayer_addr: Bytes::from(relayer_vec.clone()),
170            ..Default::default()
171        };
172
173        let expected_sender = bech32::encode::<Bech32>(ERD_HRP, &sender_vec).unwrap();
174        let expected_receiver = bech32::encode::<Bech32>(ERD_HRP, &receiver_vec).unwrap();
175        let expected_relayer = bech32::encode::<Bech32>(ERD_HRP, &relayer_vec).unwrap();
176
177        assert_eq!(tx.sender_address_bech32().unwrap(), Some(expected_sender));
178        assert_eq!(
179            tx.sender_bech32().unwrap(),
180            tx.sender_address_bech32().unwrap()
181        );
182        assert_eq!(
183            tx.receiver_address_bech32().unwrap(),
184            Some(expected_receiver)
185        );
186        assert_eq!(
187            tx.receiver_bech32().unwrap(),
188            tx.receiver_address_bech32().unwrap()
189        );
190        assert_eq!(tx.guardian_address_bech32().unwrap(), None);
191        assert_eq!(
192            tx.guardian_bech32().unwrap(),
193            tx.guardian_address_bech32().unwrap()
194        );
195        assert_eq!(tx.relayer_address_bech32().unwrap(), Some(expected_relayer));
196        assert_eq!(
197            tx.relayer_bech32().unwrap(),
198            tx.relayer_address_bech32().unwrap()
199        );
200    }
201
202    #[test]
203    fn test_hash_bytes_large_input() {
204        // Test with 10KB+ input to verify hashing handles large data
205        let large_input: Vec<u8> = (0..=255).cycle().take(10 * 1024).collect();
206        let hash = Transaction::hash_bytes(&large_input);
207
208        // Hash should be 32 bytes
209        assert_eq!(hash.len(), Transaction::HASH_SIZE);
210
211        // Hash should be deterministic
212        let hash2 = Transaction::hash_bytes(&large_input);
213        assert_eq!(hash, hash2);
214
215        // Hash should be non-zero
216        assert_ne!(hash, [0u8; 32]);
217    }
218
219    #[test]
220    fn test_hash_bytes_all_zeros() {
221        let zeros = vec![0u8; 1024];
222        let hash = Transaction::hash_bytes(&zeros);
223
224        // Hash should be 32 bytes
225        assert_eq!(hash.len(), Transaction::HASH_SIZE);
226
227        // Hash of all zeros should not be all zeros
228        assert_ne!(hash, [0u8; 32]);
229
230        // Verify determinism
231        assert_eq!(hash, Transaction::hash_bytes(&zeros));
232    }
233
234    #[test]
235    fn test_hash_bytes_all_ones() {
236        let ones = vec![0xFFu8; 1024];
237        let hash = Transaction::hash_bytes(&ones);
238
239        // Hash should be 32 bytes
240        assert_eq!(hash.len(), Transaction::HASH_SIZE);
241
242        // Hash of all 0xFF should not be all 0xFF
243        assert_ne!(hash, [0xFFu8; 32]);
244
245        // Verify determinism
246        assert_eq!(hash, Transaction::hash_bytes(&ones));
247
248        // Should differ from all zeros hash
249        let zeros = vec![0u8; 1024];
250        assert_ne!(hash, Transaction::hash_bytes(&zeros));
251    }
252
253    #[test]
254    fn test_compute_hash_empty_tx() {
255        let tx = Transaction::default();
256        let hash = tx.compute_hash();
257
258        // Hash should be 32 bytes
259        assert_eq!(hash.len(), Transaction::HASH_SIZE);
260
261        // Empty tx encodes to empty bytes, which has a known hash
262        let expected_empty = <[u8; Transaction::HASH_SIZE]>::from_hex(
263            "0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8",
264        )
265        .unwrap();
266        assert_eq!(hash, expected_empty);
267    }
268
269    #[test]
270    fn test_compute_hash_full_tx() {
271        // Transaction with all fields populated
272        let tx = Transaction {
273            nonce: 42,
274            value: Bytes::from(vec![0x01, 0x00, 0x00, 0x00]), // 1 EGLD in big-endian
275            rcv_addr: Bytes::from(vec![1u8; 32]),
276            rcv_user_name: Bytes::from("receiver_name"),
277            snd_addr: Bytes::from(vec![2u8; 32]),
278            snd_user_name: Bytes::from("sender_name"),
279            gas_price: 1_000_000_000,
280            gas_limit: 50_000,
281            data: Bytes::from("transfer@01"),
282            chain_id: Bytes::from("1"),
283            version: 2,
284            signature: Bytes::from(vec![3u8; 64]),
285            options: 1,
286            guardian_addr: Bytes::from(vec![4u8; 32]),
287            guardian_signature: Bytes::from(vec![5u8; 64]),
288            relayer_addr: Bytes::from(vec![6u8; 32]),
289            relayer_signature: Bytes::from(vec![7u8; 64]),
290        };
291
292        let hash = tx.compute_hash();
293
294        // Hash should be 32 bytes
295        assert_eq!(hash.len(), Transaction::HASH_SIZE);
296
297        // Hash should be non-zero
298        assert_ne!(hash, [0u8; 32]);
299
300        // Hash should differ from empty tx
301        assert_ne!(hash, Transaction::default().compute_hash());
302    }
303
304    #[test]
305    fn test_compute_hash_deterministic() {
306        let tx = Transaction {
307            nonce: 100,
308            value: Bytes::from(vec![0x0A]),
309            rcv_addr: Bytes::from(vec![0xAB; 32]),
310            snd_addr: Bytes::from(vec![0xCD; 32]),
311            gas_price: 500_000_000,
312            gas_limit: 100_000,
313            data: Bytes::from("test_data"),
314            chain_id: Bytes::from("T"),
315            version: 1,
316            ..Default::default()
317        };
318
319        // Compute hash multiple times
320        let hash1 = tx.compute_hash();
321        let hash2 = tx.compute_hash();
322        let hash3 = tx.compute_hash();
323
324        // All hashes should be identical
325        assert_eq!(hash1, hash2);
326        assert_eq!(hash2, hash3);
327
328        // Clone and verify cloned tx produces same hash
329        let tx_cloned = tx.clone();
330        assert_eq!(tx.compute_hash(), tx_cloned.compute_hash());
331        assert_eq!(tx.get_tx_hash(), tx.compute_hash());
332    }
333
334    #[test]
335    fn test_sender_address_empty() {
336        let tx = Transaction {
337            snd_addr: Bytes::new(),
338            ..Default::default()
339        };
340
341        let result = tx.sender_address_bech32().unwrap();
342        assert_eq!(result, None);
343    }
344
345    #[test]
346    fn test_receiver_address_empty() {
347        let tx = Transaction {
348            rcv_addr: Bytes::new(),
349            ..Default::default()
350        };
351
352        let result = tx.receiver_address_bech32().unwrap();
353        assert_eq!(result, None);
354    }
355
356    #[test]
357    fn test_guardian_address_populated() {
358        let guardian_bytes: Vec<u8> = (100..132).collect(); // 32 bytes
359        let tx = Transaction {
360            guardian_addr: Bytes::from(guardian_bytes.clone()),
361            ..Default::default()
362        };
363
364        let result = tx.guardian_address_bech32().unwrap();
365        assert!(result.is_some());
366
367        let expected = bech32::encode::<Bech32>(ERD_HRP, &guardian_bytes).unwrap();
368        assert_eq!(result.unwrap(), expected);
369        assert_eq!(
370            tx.guardian_bech32().unwrap(),
371            tx.guardian_address_bech32().unwrap()
372        );
373    }
374
375    #[test]
376    fn test_relayer_address_populated() {
377        let relayer_bytes: Vec<u8> = (150..182).collect(); // 32 bytes
378        let tx = Transaction {
379            relayer_addr: Bytes::from(relayer_bytes.clone()),
380            ..Default::default()
381        };
382
383        let result = tx.relayer_address_bech32().unwrap();
384        assert!(result.is_some());
385
386        let expected = bech32::encode::<Bech32>(ERD_HRP, &relayer_bytes).unwrap();
387        assert_eq!(result.unwrap(), expected);
388        assert_eq!(
389            tx.relayer_bech32().unwrap(),
390            tx.relayer_address_bech32().unwrap()
391        );
392    }
393
394    #[test]
395    fn test_all_addresses_format() {
396        let sender_bytes: Vec<u8> = (0..32).collect();
397        let receiver_bytes: Vec<u8> = (32..64).collect();
398        let guardian_bytes: Vec<u8> = (64..96).collect();
399        let relayer_bytes: Vec<u8> = (96..128).collect();
400
401        let tx = Transaction {
402            snd_addr: Bytes::from(sender_bytes),
403            rcv_addr: Bytes::from(receiver_bytes),
404            guardian_addr: Bytes::from(guardian_bytes),
405            relayer_addr: Bytes::from(relayer_bytes),
406            ..Default::default()
407        };
408
409        // All addresses should start with "erd1"
410        let sender = tx.sender_bech32().unwrap().unwrap();
411        let receiver = tx.receiver_bech32().unwrap().unwrap();
412        let guardian = tx.guardian_bech32().unwrap().unwrap();
413        let relayer = tx.relayer_bech32().unwrap().unwrap();
414
415        assert!(
416            sender.starts_with("erd1"),
417            "Sender should start with erd1, got: {sender}"
418        );
419        assert!(
420            receiver.starts_with("erd1"),
421            "Receiver should start with erd1, got: {receiver}"
422        );
423        assert!(
424            guardian.starts_with("erd1"),
425            "Guardian should start with erd1, got: {guardian}"
426        );
427        assert!(
428            relayer.starts_with("erd1"),
429            "Relayer should start with erd1, got: {relayer}"
430        );
431
432        // All addresses should be valid bech32 strings of expected length
433        // MultiversX addresses are 62 characters (erd1 + 58 chars)
434        assert_eq!(sender.len(), 62);
435        assert_eq!(receiver.len(), 62);
436        assert_eq!(guardian.len(), 62);
437        assert_eq!(relayer.len(), 62);
438    }
439}