neo3/neo_types/
script_hash.rs

1use byte_slice_cast::AsByteSlice;
2use primitive_types::H160;
3use serde::{Deserialize, Serialize};
4use std::{fmt, str::FromStr};
5
6use crate::{
7	config::DEFAULT_ADDRESS_VERSION,
8	crypto::{base58check_decode, base58check_encode, HashableForVec, Secp256r1PublicKey},
9	neo_crypto::utils::ToHexString,
10	neo_types::{address::Address, TypeError},
11};
12
13pub type ScriptHash = H160;
14
15/// Trait that provides additional methods for types related to `ScriptHash`.
16pub trait ScriptHashExtension
17where
18	Self: Sized,
19{
20	/// Returns a string representation of the object.
21	fn to_bs58_string(&self) -> String;
22
23	/// Creates an instance for a zero-value hash.
24	/// Returns a zero-value hash
25	fn zero() -> Self;
26
27	/// Creates an instance from a byte slice.
28	///
29	/// # Errors
30	///
31	/// Returns an error if the slice has an invalid length.
32	fn from_slice(slice: &[u8]) -> Result<Self, TypeError>;
33
34	/// Creates an instance from a hex string.
35	///
36	/// # Errors
37	///
38	/// Returns an error if the hex string is invalid.
39	fn from_hex(hex: &str) -> Result<Self, hex::FromHexError>;
40
41	/// Creates an instance from an address string representation.
42	///
43	/// # Errors
44	///
45	/// Returns an error if the address is invalid.
46	fn from_address(address: &str) -> Result<Self, TypeError>;
47
48	/// Converts the object into its address string representation.
49	fn to_address(&self) -> String;
50
51	/// Converts the object into its hex string representation.
52	fn to_hex(&self) -> String;
53
54	/// Converts the object into its hex string representation.
55	fn to_hex_big_endian(&self) -> String;
56
57	/// Converts the object into a byte vector.
58	fn to_vec(&self) -> Vec<u8>;
59
60	/// Converts the object into a little-endian byte vector.
61	fn to_le_vec(&self) -> Vec<u8>;
62
63	/// Creates an instance from a script byte slice.
64	fn from_script(script: &[u8]) -> Self;
65
66	fn from_public_key(public_key: &[u8]) -> Result<Self, TypeError>;
67}
68
69impl ScriptHashExtension for H160 {
70	fn to_bs58_string(&self) -> String {
71		bs58::encode(self.0).into_string()
72	}
73
74	fn zero() -> Self {
75		let arr = [0u8; 20];
76		Self(arr)
77	}
78
79	fn from_slice(slice: &[u8]) -> Result<Self, TypeError> {
80		if slice.len() != 20 {
81			return Err(TypeError::InvalidAddress);
82		}
83
84		let mut arr = [0u8; 20];
85		arr.copy_from_slice(slice);
86		Ok(Self(arr))
87	}
88
89	//Performs different behavior compared to from_str, should be noticed
90	fn from_hex(hex: &str) -> Result<Self, hex::FromHexError> {
91		if hex.starts_with("0x") {
92			let mut bytes = hex::decode(&hex[2..])?;
93			bytes.reverse();
94			<Self as ScriptHashExtension>::from_slice(&bytes)
95				.map_err(|_| hex::FromHexError::InvalidHexCharacter { c: '0', index: 0 })
96		} else {
97			let bytes = hex::decode(hex)?;
98			<Self as ScriptHashExtension>::from_slice(&bytes)
99				.map_err(|_| hex::FromHexError::InvalidHexCharacter { c: '0', index: 0 })
100		}
101	}
102
103	fn from_address(address: &str) -> Result<Self, TypeError> {
104		let bytes = match bs58::decode(address).into_vec() {
105			Ok(bytes) => bytes,
106			Err(_) => return Err(TypeError::InvalidAddress),
107		};
108
109		let _salt = bytes[0];
110		let hash = &bytes[1..21];
111		let checksum = &bytes[21..25];
112		let sha = &bytes[..21].hash256().hash256();
113		let check = &sha[..4];
114		if checksum != check {
115			return Err(TypeError::InvalidAddress);
116		}
117
118		let mut rev = [0u8; 20];
119		rev.clone_from_slice(hash);
120		rev.reverse();
121		<Self as ScriptHashExtension>::from_slice(&rev)
122	}
123
124	fn to_address(&self) -> String {
125		let mut data = vec![DEFAULT_ADDRESS_VERSION];
126		let mut reversed_bytes = self.as_bytes().to_vec();
127		reversed_bytes.reverse();
128		//data.extend_from_slice(&self.as_bytes());
129		data.extend_from_slice(&reversed_bytes);
130		let sha = &data.hash256().hash256();
131		data.extend_from_slice(&sha[..4]);
132		bs58::encode(data).into_string()
133	}
134
135	fn to_hex(&self) -> String {
136		hex::encode(self.0)
137	}
138
139	fn to_hex_big_endian(&self) -> String {
140		let mut cloned = self.0.clone();
141		cloned.reverse();
142		"0x".to_string() + &hex::encode(cloned)
143	}
144
145	fn to_vec(&self) -> Vec<u8> {
146		self.0.to_vec()
147	}
148
149	fn to_le_vec(&self) -> Vec<u8> {
150		let vec = self.0.to_vec();
151		vec
152	}
153
154	fn from_script(script: &[u8]) -> Self {
155		let mut hash: [u8; 20] = script
156			.sha256_ripemd160()
157			.as_byte_slice()
158			.try_into()
159			.expect("script does not have exactly 20 elements");
160		hash.reverse();
161		Self(hash)
162	}
163
164	fn from_public_key(public_key: &[u8]) -> Result<Self, TypeError> {
165		// Create a proper verification script for the public key
166		// Format: PushData1 + length + public_key + Syscall + SystemCryptoCheckSig.hash()
167		let mut script = Vec::new();
168
169		// PushData1 opcode (0x0c)
170		script.push(0x0c);
171
172		// Length of public key (33 bytes for compressed key)
173		script.push(public_key.len() as u8);
174
175		// Public key bytes
176		script.extend_from_slice(public_key);
177
178		// Syscall opcode (0x41)
179		script.push(0x41);
180
181		// SystemCryptoCheckSig hash (4 bytes)
182		let interop_hash = "System.Crypto.CheckSig".as_bytes().hash256();
183		script.extend_from_slice(&interop_hash[..4]);
184
185		// Hash the script to get the script hash
186		Ok(Self::from_script(&script))
187	}
188}
189
190#[cfg(test)]
191mod tests {
192	use std::str::FromStr;
193
194	use crate::{
195		neo_builder::InteropService,
196		neo_codec::{Encoder, NeoSerializable},
197		neo_config::TestConstants,
198		neo_types::op_code::OpCode,
199	};
200
201	use super::*;
202
203	#[test]
204	fn test_from_valid_hash() {
205		assert_eq!(
206			H160::from_hex("23ba2703c53263e8d6e522dc32203339dcd8eee9")
207				.unwrap()
208				.as_bytes()
209				.to_hex_string(),
210			"23ba2703c53263e8d6e522dc32203339dcd8eee9".to_string()
211		);
212
213		assert_eq!(
214			H160::from_hex("0x23ba2703c53263e8d6e522dc32203339dcd8eee9")
215				.unwrap()
216				.as_bytes()
217				.to_hex_string(),
218			"e9eed8dc39332032dc22e5d6e86332c50327ba23".to_string()
219		);
220	}
221
222	#[test]
223	fn test_creation_failures() {
224		// Test odd length hex string
225		assert!(H160::from_hex("23ba2703c53263e8d6e522dc32203339dcd8eee").is_err());
226		// Test invalid hex character
227		assert!(H160::from_hex("g3ba2703c53263e8d6e522dc32203339dcd8eee9").is_err());
228		// Test too short hex string
229		assert!(H160::from_hex("23ba2703c53263e8d6e522dc32203339dcd8ee").is_err());
230		// Test too long hex string
231		assert!(H160::from_hex("c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b")
232			.is_err());
233	}
234
235	#[test]
236	fn test_to_array() {
237		let hash = H160::from_str("23ba2703c53263e8d6e522dc32203339dcd8eee9").unwrap();
238		assert_eq!(hash.to_vec(), hex::decode("23ba2703c53263e8d6e522dc32203339dcd8eee9").unwrap());
239	}
240
241	#[test]
242	fn test_serialize_and_deserialize() {
243		let hex_str = "23ba2703c53263e8d6e522dc32203339dcd8eee9";
244		let data = hex::decode(hex_str).unwrap();
245
246		let mut buffer = Encoder::new();
247		H160::from_hex(hex_str).unwrap().encode(&mut buffer);
248
249		assert_eq!(buffer.to_bytes(), data);
250		assert_eq!(
251			<H160 as ScriptHashExtension>::from_slice(&data)
252				.unwrap()
253				.as_bytes()
254				.to_hex_string(),
255			hex_str
256		);
257	}
258
259	#[test]
260	fn test_equals() {
261		let hash1 = H160::from_script(&hex::decode("01a402d8").unwrap());
262		let hash2 = H160::from_script(&hex::decode("d802a401").unwrap());
263		assert_ne!(hash1, hash2);
264		assert_eq!(hash1, hash1);
265	}
266
267	#[test]
268	fn test_from_address() {
269		let hash = H160::from_address("NeE8xcV4ohHi9rjyj4nPdCYTGyXnWZ79UU").unwrap();
270		let mut expected = hex::decode(
271			"2102208aea0068c429a03316e37be0e3e8e21e6cda5442df4c5914a19b3a9b6de37568747476aa",
272		)
273		.unwrap()
274		.sha256_ripemd160();
275		expected.reverse();
276		assert_eq!(hash.to_le_vec(), expected);
277	}
278
279	#[test]
280	// #[should_panic]
281	fn test_from_invalid_address() {
282		// assert that this should return Err
283		assert_eq!(
284			H160::from_address("NLnyLtep7jwyq1qhNPkwXbJpurC4jUT8keas"),
285			Err(TypeError::InvalidAddress)
286		);
287	}
288
289	#[test]
290	fn test_from_public_key_bytes() {
291		let public_key = "035fdb1d1f06759547020891ae97c729327853aeb1256b6fe0473bc2e9fa42ff50";
292		let script = format!(
293			"{}21{}{}{}",
294			OpCode::PushData1.to_hex_string(),
295			public_key,
296			OpCode::Syscall.to_hex_string(),
297			InteropService::SystemCryptoCheckSig.hash()
298		);
299
300		let hash = H160::from_public_key(&hex::decode(public_key).unwrap()).unwrap();
301		let mut hash = hash.to_array();
302		let mut expected = hex::decode(&script).unwrap().sha256_ripemd160();
303		expected.reverse();
304		assert_eq!(hash, expected);
305	}
306
307	#[test]
308	fn test_to_address() {
309		let mut script_hash = hex::decode(
310			"0c2102249425a06b5a1f8e6133fc79afa2c2b8430bf9327297f176761df79e8d8929c50b4195440d78",
311		)
312		.unwrap()
313		.sha256_ripemd160();
314		script_hash.reverse();
315		let hash = H160::from_hex(&hex::encode(script_hash)).unwrap();
316		let address = hash.to_address();
317		assert_eq!(address, "NLnyLtep7jwyq1qhNPkwXbJpurC4jUT8ke".to_string());
318	}
319}