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        for byte in &self.0 {
130            write!(f, "{:02x}", byte)?;
131        }
132        Ok(())
133    }
134}
135
136impl fmt::Display for Hash256 {
137    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138        write!(f, "0x")?;
139        for byte in &self.0 {
140            write!(f, "{:02x}", byte)?;
141        }
142        Ok(())
143    }
144}
145
146impl TryFrom<&[u8]> for Hash160 {
147    type Error = NeoError;
148    fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
149        Self::try_from_slice(slice)
150    }
151}
152
153impl TryFrom<Vec<u8>> for Hash160 {
154    type Error = NeoError;
155    fn try_from(vec: Vec<u8>) -> Result<Self, Self::Error> {
156        Self::try_from_slice(&vec)
157    }
158}
159
160impl TryFrom<NeoByteString> for Hash160 {
161    type Error = NeoError;
162    fn try_from(bs: NeoByteString) -> Result<Self, Self::Error> {
163        Self::try_from_slice(bs.as_slice())
164    }
165}
166
167impl From<Hash160> for NeoByteString {
168    fn from(h: Hash160) -> Self {
169        h.to_byte_string()
170    }
171}
172
173impl TryFrom<&[u8]> for Hash256 {
174    type Error = NeoError;
175    fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
176        Self::try_from_slice(slice)
177    }
178}
179
180impl TryFrom<Vec<u8>> for Hash256 {
181    type Error = NeoError;
182    fn try_from(vec: Vec<u8>) -> Result<Self, Self::Error> {
183        Self::try_from_slice(&vec)
184    }
185}
186
187impl TryFrom<NeoByteString> for Hash256 {
188    type Error = NeoError;
189    fn try_from(bs: NeoByteString) -> Result<Self, Self::Error> {
190        Self::try_from_slice(bs.as_slice())
191    }
192}
193
194impl From<Hash256> for NeoByteString {
195    fn from(h: Hash256) -> Self {
196        h.to_byte_string()
197    }
198}
199
200impl AsRef<[u8]> for Hash160 {
201    fn as_ref(&self) -> &[u8] {
202        &self.0
203    }
204}
205
206impl AsRef<[u8]> for Hash256 {
207    fn as_ref(&self) -> &[u8] {
208        &self.0
209    }
210}
211
212#[cfg(test)]
213mod tests {
214    use super::*;
215
216    #[test]
217    fn hash160_from_valid_bytes() {
218        let bytes = [0xABu8; 20];
219        let h = Hash160::from_bytes(bytes);
220        assert_eq!(h.as_bytes(), &[0xAB; 20]);
221    }
222
223    #[test]
224    fn hash160_try_from_wrong_length_fails() {
225        assert!(Hash160::try_from_slice(&[0u8; 19]).is_err());
226        assert!(Hash160::try_from_slice(&[0u8; 21]).is_err());
227        assert!(Hash160::try_from_slice(&[0u8; 0]).is_err());
228    }
229
230    #[test]
231    fn hash160_try_from_correct_length_succeeds() {
232        let h = Hash160::try_from_slice(&[0xFFu8; 20]).unwrap();
233        assert_eq!(h.as_bytes(), &[0xFF; 20]);
234    }
235
236    #[test]
237    fn hash160_zero() {
238        assert!(Hash160::ZERO.is_zero());
239        assert!(!Hash160::from_bytes([1; 20]).is_zero());
240    }
241
242    #[test]
243    fn hash256_from_valid_bytes() {
244        let bytes = [0xCDu8; 32];
245        let h = Hash256::from_bytes(bytes);
246        assert_eq!(h.as_bytes(), &[0xCD; 32]);
247    }
248
249    #[test]
250    fn hash256_try_from_wrong_length_fails() {
251        assert!(Hash256::try_from_slice(&[0u8; 31]).is_err());
252        assert!(Hash256::try_from_slice(&[0u8; 33]).is_err());
253    }
254
255    #[test]
256    fn hash256_try_from_correct_length_succeeds() {
257        let h = Hash256::try_from_slice(&[0xEEu8; 32]).unwrap();
258        assert_eq!(h.as_bytes(), &[0xEE; 32]);
259    }
260
261    #[test]
262    fn hash256_zero() {
263        assert!(Hash256::ZERO.is_zero());
264        assert!(!Hash256::from_bytes([1; 32]).is_zero());
265    }
266
267    #[test]
268    fn hash160_roundtrip_bytestring() {
269        let h = Hash160::from_bytes([0x42; 20]);
270        let bs: NeoByteString = h.clone().into();
271        let h2 = Hash160::try_from(bs).unwrap();
272        assert_eq!(h, h2);
273    }
274
275    #[test]
276    fn hash256_roundtrip_bytestring() {
277        let h = Hash256::from_bytes([0x42; 32]);
278        let bs: NeoByteString = h.clone().into();
279        let h2 = Hash256::try_from(bs).unwrap();
280        assert_eq!(h, h2);
281    }
282}