neo3/neo_types/
address.rs

1// This module demonstrates extensions for blockchain address manipulation, focusing on converting between addresses, script hashes,
2// and handling various formats like Base58 and hexadecimal strings. It leverages cryptographic functions, serialization, and
3// deserialization to work with blockchain-specific data types.
4
5use std::{fmt, str::FromStr};
6
7use primitive_types::H160;
8use rand::Rng;
9use serde::{Deserialize, Serialize};
10
11use crate::{
12	crypto::{base58check_decode, base58check_encode, HashableForVec, Secp256r1PublicKey},
13	neo_crypto::utils::{FromHexString, ToHexString},
14	neo_error::NeoError,
15	neo_types::{ScriptHash, ScriptHashExtension, StringExt, TypeError},
16	prelude::Bytes,
17};
18
19pub type Address = String;
20
21#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
22pub enum NameOrAddress {
23	Name(String),
24	Address(Address),
25}
26
27// Implementations below provide concrete behavior for the `AddressExtension` trait,
28// applicable to `String` and `&str` types.
29pub trait AddressExtension {
30	/// Converts a Base58-encoded address (common in many blockchain systems) to a `ScriptHash`.
31	///
32	/// # Examples
33	///
34	/// Basic usage:
35	///
36	/// ```
37	/// use neo3::neo_types::AddressExtension;
38	/// // Example with a valid Neo N3 address format
39	/// let address = "NNLi44dJNXtDNSBkofB48aTVYtb1zZrNEs";
40	/// let result = address.address_to_script_hash();
41	/// // This demonstrates the function's capability to convert addresses
42	/// ```
43	fn address_to_script_hash(&self) -> Result<ScriptHash, TypeError>;
44
45	/// Decodes a hex-encoded script into a `ScriptHash`, demonstrating error handling for invalid hex strings.
46	///
47	/// # Examples
48	///
49	/// Basic usage:
50	///
51	/// ```
52	/// use neo3::neo_types::AddressExtension;
53	/// let script = "abcdef1234567890";
54	/// let script_hash = script.script_to_script_hash().unwrap();
55	/// ```
56	fn script_to_script_hash(&self) -> Result<ScriptHash, TypeError>;
57
58	/// Validates a hex string and converts it to a `ScriptHash`, showcasing error handling for non-hex strings.
59	///
60	/// # Examples
61	///
62	/// Basic usage:
63	///
64	/// ```
65	/// use neo3::neo_types::AddressExtension;
66	/// let hex_string = "abcdef1234567890abcdef1234567890abcdef12";
67	/// let script_hash = hex_string.hex_to_script_hash().unwrap();
68	/// ```
69	fn hex_to_script_hash(&self) -> Result<ScriptHash, TypeError>;
70
71	/// Generates a random address using cryptographic-safe random number generation, ideal for creating new wallet addresses.
72	///
73	/// # Examples
74	///
75	/// Basic usage:
76	///
77	/// ```
78	/// use neo3::neo_types::AddressExtension;
79	/// let random_address = String::random();
80	/// ```
81	fn random() -> Self;
82}
83
84impl AddressExtension for String {
85	fn address_to_script_hash(&self) -> Result<ScriptHash, TypeError> {
86		// Base58-decode the address
87		let decoded_data = match bs58::decode(self).into_vec() {
88			Ok(data) => data,
89			Err(_) => return Err(TypeError::InvalidAddress),
90		};
91		let data_payload = decoded_data[1..decoded_data.len() - 4].to_vec();
92		Ok(H160::from_slice(data_payload.as_slice()))
93	}
94
95	fn script_to_script_hash(&self) -> Result<ScriptHash, TypeError> {
96		self.from_hex_string()
97			.map(|data| ScriptHash::from_script(data.as_slice()))
98			.map_err(|_| TypeError::InvalidScript("Invalid hex string".to_string()))
99	}
100
101	fn hex_to_script_hash(&self) -> Result<ScriptHash, TypeError> {
102		if self.is_valid_hex() {
103			ScriptHash::from_hex(self.as_str())
104				.map_err(|_| TypeError::InvalidFormat("Invalid hex format".to_string()))
105		} else {
106			Err(TypeError::InvalidFormat("Invalid hex format".to_string()))
107		}
108	}
109
110	fn random() -> Self {
111		let mut rng = rand::thread_rng();
112		let mut bytes = [0u8; 20];
113		rng.fill(&mut bytes);
114		let script_hash = bytes.sha256_ripemd160();
115		let mut data = vec![0x17];
116		data.extend_from_slice(&script_hash);
117		let sha = &data.hash256().hash256();
118		data.extend_from_slice(&sha[..4]);
119		bs58::encode(data).into_string()
120	}
121}
122
123impl AddressExtension for &str {
124	fn address_to_script_hash(&self) -> Result<ScriptHash, TypeError> {
125		self.to_string().address_to_script_hash()
126	}
127
128	fn script_to_script_hash(&self) -> Result<ScriptHash, TypeError> {
129		self.to_string().script_to_script_hash()
130	}
131
132	fn hex_to_script_hash(&self) -> Result<ScriptHash, TypeError> {
133		self.to_string().hex_to_script_hash()
134	}
135
136	fn random() -> Self {
137		// This implementation is not feasible for &str as it requires returning a borrowed string
138		// Users should use String::random() instead
139		""
140	}
141}
142
143#[cfg(test)]
144mod tests {
145	use super::*;
146
147	#[test]
148	fn test_address_to_script_hash() {
149		// Test case 1: Valid N3 address
150		let n3_address = "NTGYC16CN5QheM4ZwfhUp9JKq8bMjWtcAp";
151		let expected_script_hash_hex = "50acc01271492d7b0e264ace0d60d572e66bc087";
152		let result = n3_address
153			.address_to_script_hash()
154			.expect("Should be able to convert valid N3 address to script hash");
155		assert_eq!(hex::encode(result), expected_script_hash_hex);
156
157		// Test case 3: Invalid N3 address
158		let n3_address = "Invalid_Address";
159		let result = n3_address.to_string().address_to_script_hash();
160		assert!(result.is_err());
161	}
162}
163
164pub fn from_script_hash(script_hash: &H160) -> Result<String, NeoError> {
165	Err(NeoError::UnsupportedOperation(
166		"Address conversion from script hash requires comprehensive cryptographic implementation"
167			.to_string(),
168	))
169}