Skip to main content

neo_types/
hash.rs

1// Copyright (c) 2025-2026 R3E Network
2// Licensed under the MIT License
3
4use std::fmt;
5
6use crate::error::{NeoError, NeoResult};
7use crate::NeoByteString;
8
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12/// A 20-byte script hash used to identify accounts and contracts on Neo N3.
13///
14/// Hash160 is the standard identifier for Neo addresses and contract script hashes.
15/// It wraps exactly 20 bytes and provides validated construction.
16///
17/// # Byte order
18///
19/// All Neo hashes (Hash160/Hash256) are stored and transmitted in
20/// **little-endian** byte order, matching the Neo VM and on-wire protocol.
21/// When converting from a display string (big-endian hex), the bytes are
22/// reversed; the internal `[u8; 20]` is always little-endian.
23#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
24#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
25pub struct Hash160([u8; 20]);
26
27/// A 32-byte hash used for transaction and block identifiers on Neo N3.
28///
29/// Hash256 wraps exactly 32 bytes and provides validated construction.
30#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
31#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
32pub struct Hash256([u8; 32]);
33
34impl Hash160 {
35    /// The fixed byte length of a Hash160.
36    pub const LENGTH: usize = 20;
37
38    /// A zero-valued Hash160.
39    pub const ZERO: Self = Self([0u8; 20]);
40
41    /// Creates a Hash160 from a 20-byte array.
42    pub const fn from_bytes(bytes: [u8; 20]) -> Self {
43        Self(bytes)
44    }
45
46    /// Attempts to create a Hash160 from a byte slice.
47    ///
48    /// Returns `Err(NeoError::InvalidArgument)` if the slice length is not exactly 20.
49    pub fn try_from_slice(slice: &[u8]) -> NeoResult<Self> {
50        let bytes: [u8; 20] = slice.try_into().map_err(|_| {
51            NeoError::Custom(format!(
52                "Hash160 requires exactly 20 bytes, got {}",
53                slice.len()
54            ))
55        })?;
56        Ok(Self(bytes))
57    }
58
59    /// Returns the underlying 20 bytes as a slice.
60    pub fn as_bytes(&self) -> &[u8; 20] {
61        &self.0
62    }
63
64    /// Returns the underlying 20 bytes as a generic slice.
65    pub fn as_slice(&self) -> &[u8] {
66        &self.0
67    }
68
69    /// Returns `true` if all bytes are zero.
70    pub fn is_zero(&self) -> bool {
71        self.0 == [0u8; 20]
72    }
73
74    /// Converts this Hash160 into a `NeoByteString`.
75    pub fn to_byte_string(&self) -> NeoByteString {
76        NeoByteString::from_slice(&self.0)
77    }
78}
79
80impl Hash256 {
81    /// The fixed byte length of a Hash256.
82    pub const LENGTH: usize = 32;
83
84    /// A zero-valued Hash256.
85    pub const ZERO: Self = Self([0u8; 32]);
86
87    /// Creates a Hash256 from a 32-byte array.
88    pub const fn from_bytes(bytes: [u8; 32]) -> Self {
89        Self(bytes)
90    }
91
92    /// Attempts to create a Hash256 from a byte slice.
93    ///
94    /// Returns `Err(NeoError::InvalidArgument)` if the slice length is not exactly 32.
95    pub fn try_from_slice(slice: &[u8]) -> NeoResult<Self> {
96        let bytes: [u8; 32] = slice.try_into().map_err(|_| {
97            NeoError::Custom(format!(
98                "Hash256 requires exactly 32 bytes, got {}",
99                slice.len()
100            ))
101        })?;
102        Ok(Self(bytes))
103    }
104
105    /// Returns the underlying 32 bytes as a slice.
106    pub fn as_bytes(&self) -> &[u8; 32] {
107        &self.0
108    }
109
110    /// Returns the underlying 32 bytes as a generic slice.
111    pub fn as_slice(&self) -> &[u8] {
112        &self.0
113    }
114
115    /// Returns `true` if all bytes are zero.
116    pub fn is_zero(&self) -> bool {
117        self.0 == [0u8; 32]
118    }
119
120    /// Converts this Hash256 into a `NeoByteString`.
121    pub fn to_byte_string(&self) -> NeoByteString {
122        NeoByteString::from_slice(&self.0)
123    }
124}
125
126impl fmt::Display for Hash160 {
127    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128        write!(f, "0x")?;
129        // Internal bytes are little-endian; canonical Neo addresses/explorer
130        // hashes are big-endian, so emit in reverse (D9: was printing LE,
131        // reversed vs what RPC/explorers show).
132        for byte in self.0.iter().rev() {
133            write!(f, "{:02x}", byte)?;
134        }
135        Ok(())
136    }
137}
138
139impl fmt::Display for Hash256 {
140    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141        write!(f, "0x")?;
142        for byte in self.0.iter().rev() {
143            write!(f, "{:02x}", byte)?;
144        }
145        Ok(())
146    }
147}
148
149impl TryFrom<&[u8]> for Hash160 {
150    type Error = NeoError;
151    fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
152        Self::try_from_slice(slice)
153    }
154}
155
156impl TryFrom<Vec<u8>> for Hash160 {
157    type Error = NeoError;
158    fn try_from(vec: Vec<u8>) -> Result<Self, Self::Error> {
159        Self::try_from_slice(&vec)
160    }
161}
162
163impl TryFrom<NeoByteString> for Hash160 {
164    type Error = NeoError;
165    fn try_from(bs: NeoByteString) -> Result<Self, Self::Error> {
166        Self::try_from_slice(bs.as_slice())
167    }
168}
169
170impl From<Hash160> for NeoByteString {
171    fn from(h: Hash160) -> Self {
172        h.to_byte_string()
173    }
174}
175
176impl TryFrom<&[u8]> for Hash256 {
177    type Error = NeoError;
178    fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
179        Self::try_from_slice(slice)
180    }
181}
182
183impl TryFrom<Vec<u8>> for Hash256 {
184    type Error = NeoError;
185    fn try_from(vec: Vec<u8>) -> Result<Self, Self::Error> {
186        Self::try_from_slice(&vec)
187    }
188}
189
190impl TryFrom<NeoByteString> for Hash256 {
191    type Error = NeoError;
192    fn try_from(bs: NeoByteString) -> Result<Self, Self::Error> {
193        Self::try_from_slice(bs.as_slice())
194    }
195}
196
197impl From<Hash256> for NeoByteString {
198    fn from(h: Hash256) -> Self {
199        h.to_byte_string()
200    }
201}
202
203impl AsRef<[u8]> for Hash160 {
204    fn as_ref(&self) -> &[u8] {
205        &self.0
206    }
207}
208
209impl AsRef<[u8]> for Hash256 {
210    fn as_ref(&self) -> &[u8] {
211        &self.0
212    }
213}
214
215#[cfg(test)]
216mod tests {
217    use super::*;
218
219    #[test]
220    fn hash160_from_valid_bytes() {
221        let bytes = [0xABu8; 20];
222        let h = Hash160::from_bytes(bytes);
223        assert_eq!(h.as_bytes(), &[0xAB; 20]);
224    }
225
226    #[test]
227    fn hash160_try_from_wrong_length_fails() {
228        assert!(Hash160::try_from_slice(&[0u8; 19]).is_err());
229        assert!(Hash160::try_from_slice(&[0u8; 21]).is_err());
230        assert!(Hash160::try_from_slice(&[0u8; 0]).is_err());
231    }
232
233    #[test]
234    fn hash160_try_from_correct_length_succeeds() {
235        let h = Hash160::try_from_slice(&[0xFFu8; 20]).unwrap();
236        assert_eq!(h.as_bytes(), &[0xFF; 20]);
237    }
238
239    #[test]
240    fn hash160_zero() {
241        assert!(Hash160::ZERO.is_zero());
242        assert!(!Hash160::from_bytes([1; 20]).is_zero());
243    }
244
245    #[test]
246    fn hash256_from_valid_bytes() {
247        let bytes = [0xCDu8; 32];
248        let h = Hash256::from_bytes(bytes);
249        assert_eq!(h.as_bytes(), &[0xCD; 32]);
250    }
251
252    #[test]
253    fn hash256_try_from_wrong_length_fails() {
254        assert!(Hash256::try_from_slice(&[0u8; 31]).is_err());
255        assert!(Hash256::try_from_slice(&[0u8; 33]).is_err());
256    }
257
258    #[test]
259    fn hash256_try_from_correct_length_succeeds() {
260        let h = Hash256::try_from_slice(&[0xEEu8; 32]).unwrap();
261        assert_eq!(h.as_bytes(), &[0xEE; 32]);
262    }
263
264    #[test]
265    fn hash256_zero() {
266        assert!(Hash256::ZERO.is_zero());
267        assert!(!Hash256::from_bytes([1; 32]).is_zero());
268    }
269
270    #[test]
271    fn hash160_roundtrip_bytestring() {
272        let h = Hash160::from_bytes([0x42; 20]);
273        let bs: NeoByteString = h.clone().into();
274        let h2 = Hash160::try_from(bs).unwrap();
275        assert_eq!(h, h2);
276    }
277
278    #[test]
279    fn hash256_roundtrip_bytestring() {
280        let h = Hash256::from_bytes([0x42; 32]);
281        let bs: NeoByteString = h.clone().into();
282        let h2 = Hash256::try_from(bs).unwrap();
283        assert_eq!(h, h2);
284    }
285}