1use crate::primitives::public_key::PublicKey;
7use crate::primitives::utils::{base58_check_decode, base58_check_encode};
8use crate::script::error::ScriptError;
9use crate::script::locking_script::LockingScript;
10use crate::script::op::Op;
11use crate::script::script::Script;
12use crate::script::script_chunk::ScriptChunk;
13
14const MAINNET_PREFIX: u8 = 0x00;
16
17const TESTNET_PREFIX: u8 = 0x6f;
19
20#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct Address {
23 address_string: String,
24 public_key_hash: Vec<u8>,
25}
26
27impl Address {
28 pub fn from_public_key_hash(hash: &[u8; 20], mainnet: bool) -> Self {
33 let prefix_byte = if mainnet {
34 MAINNET_PREFIX
35 } else {
36 TESTNET_PREFIX
37 };
38 let address_string = base58_check_encode(hash, &[prefix_byte]);
39 Address {
40 address_string,
41 public_key_hash: hash.to_vec(),
42 }
43 }
44
45 pub fn from_public_key(pubkey: &PublicKey, mainnet: bool) -> Self {
50 let hash_vec = pubkey.to_hash();
51 let mut hash = [0u8; 20];
52 hash.copy_from_slice(&hash_vec);
53 Self::from_public_key_hash(&hash, mainnet)
54 }
55
56 pub fn from_string(s: &str) -> Result<Self, ScriptError> {
61 let (prefix, payload) =
62 base58_check_decode(s, 1).map_err(|e| ScriptError::InvalidAddress(e.to_string()))?;
63
64 if prefix.len() != 1 || (prefix[0] != MAINNET_PREFIX && prefix[0] != TESTNET_PREFIX) {
65 return Err(ScriptError::InvalidAddress(format!(
66 "unknown address prefix: 0x{:02x}",
67 prefix.first().copied().unwrap_or(0)
68 )));
69 }
70
71 if payload.len() != 20 {
72 return Err(ScriptError::InvalidAddress(format!(
73 "expected 20-byte hash, got {} bytes",
74 payload.len()
75 )));
76 }
77
78 Ok(Address {
79 address_string: s.to_string(),
80 public_key_hash: payload,
81 })
82 }
83
84 pub fn to_public_key_hash(&self) -> &[u8] {
86 &self.public_key_hash
87 }
88
89 pub fn is_mainnet(&self) -> bool {
91 if let Ok((prefix, _)) = base58_check_decode(&self.address_string, 1) {
93 prefix.first().copied() == Some(MAINNET_PREFIX)
94 } else {
95 false
96 }
97 }
98
99 pub fn to_locking_script(&self) -> LockingScript {
103 let chunks = vec![
104 ScriptChunk::new_opcode(Op::OpDup),
105 ScriptChunk::new_opcode(Op::OpHash160),
106 ScriptChunk::new_raw(
107 self.public_key_hash.len() as u8,
108 Some(self.public_key_hash.clone()),
109 ),
110 ScriptChunk::new_opcode(Op::OpEqualVerify),
111 ScriptChunk::new_opcode(Op::OpCheckSig),
112 ];
113 LockingScript::from_script(Script::from_chunks(chunks))
114 }
115}
116
117impl std::fmt::Display for Address {
118 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119 write!(f, "{}", self.address_string)
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126 #[test]
131 fn test_known_mainnet_address() {
132 let pubkey_hash =
133 crate::primitives::utils::from_hex("751e76e8199196d454941c45d1b3a323f1433bd6").unwrap();
134 let mut hash = [0u8; 20];
135 hash.copy_from_slice(&pubkey_hash);
136
137 let addr = Address::from_public_key_hash(&hash, true);
138 assert_eq!(addr.to_string(), "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH");
139 assert!(addr.is_mainnet());
140 }
141
142 #[test]
143 fn test_known_testnet_address() {
144 let pubkey_hash =
145 crate::primitives::utils::from_hex("751e76e8199196d454941c45d1b3a323f1433bd6").unwrap();
146 let mut hash = [0u8; 20];
147 hash.copy_from_slice(&pubkey_hash);
148
149 let addr = Address::from_public_key_hash(&hash, false);
150 assert!(!addr.is_mainnet());
152 let s = addr.to_string();
154 assert!(
155 s.starts_with('m') || s.starts_with('n'),
156 "testnet address should start with m or n, got: {}",
157 s
158 );
159 }
160
161 #[test]
162 fn test_roundtrip_from_hash_to_string_from_string() {
163 let pubkey_hash =
164 crate::primitives::utils::from_hex("751e76e8199196d454941c45d1b3a323f1433bd6").unwrap();
165 let mut hash = [0u8; 20];
166 hash.copy_from_slice(&pubkey_hash);
167
168 let addr = Address::from_public_key_hash(&hash, true);
169 let addr_str = addr.to_string();
170
171 let decoded = Address::from_string(&addr_str).unwrap();
172 assert_eq!(decoded.to_public_key_hash(), &pubkey_hash[..]);
173 assert_eq!(decoded.to_string(), addr_str);
174 }
175
176 #[test]
177 fn test_invalid_address_string() {
178 let result = Address::from_string("1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAM1");
180 assert!(result.is_err());
181 }
182
183 #[test]
184 fn test_to_locking_script_p2pkh() {
185 let pubkey_hash =
186 crate::primitives::utils::from_hex("751e76e8199196d454941c45d1b3a323f1433bd6").unwrap();
187 let mut hash = [0u8; 20];
188 hash.copy_from_slice(&pubkey_hash);
189
190 let addr = Address::from_public_key_hash(&hash, true);
191 let script = addr.to_locking_script();
192
193 let binary = script.to_binary();
195 assert_eq!(binary[0], 0x76, "OP_DUP");
196 assert_eq!(binary[1], 0xa9, "OP_HASH160");
197 assert_eq!(binary[2], 0x14, "push 20 bytes");
198 assert_eq!(&binary[3..23], &pubkey_hash[..], "pubkey hash");
199 assert_eq!(binary[23], 0x88, "OP_EQUALVERIFY");
200 assert_eq!(binary[24], 0xac, "OP_CHECKSIG");
201 assert_eq!(binary.len(), 25);
202 }
203
204 #[test]
205 fn test_from_public_key() {
206 use crate::primitives::private_key::PrivateKey;
208
209 let pk = PrivateKey::from_bytes(&{
210 let mut buf = [0u8; 32];
211 buf[31] = 1;
212 buf
213 })
214 .unwrap();
215 let pubkey = pk.to_public_key();
216 let addr = Address::from_public_key(&pubkey, true);
217 assert_eq!(addr.to_string(), "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH");
218 }
219
220 #[test]
221 fn test_display_impl() {
222 let pubkey_hash =
223 crate::primitives::utils::from_hex("751e76e8199196d454941c45d1b3a323f1433bd6").unwrap();
224 let mut hash = [0u8; 20];
225 hash.copy_from_slice(&pubkey_hash);
226
227 let addr = Address::from_public_key_hash(&hash, true);
228 let display_str = format!("{}", addr);
229 assert_eq!(display_str, "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH");
230 }
231}