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: {s}"
157 );
158 }
159
160 #[test]
161 fn test_roundtrip_from_hash_to_string_from_string() {
162 let pubkey_hash =
163 crate::primitives::utils::from_hex("751e76e8199196d454941c45d1b3a323f1433bd6").unwrap();
164 let mut hash = [0u8; 20];
165 hash.copy_from_slice(&pubkey_hash);
166
167 let addr = Address::from_public_key_hash(&hash, true);
168 let addr_str = addr.to_string();
169
170 let decoded = Address::from_string(&addr_str).unwrap();
171 assert_eq!(decoded.to_public_key_hash(), &pubkey_hash[..]);
172 assert_eq!(decoded.to_string(), addr_str);
173 }
174
175 #[test]
176 fn test_invalid_address_string() {
177 let result = Address::from_string("1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAM1");
179 assert!(result.is_err());
180 }
181
182 #[test]
183 fn test_to_locking_script_p2pkh() {
184 let pubkey_hash =
185 crate::primitives::utils::from_hex("751e76e8199196d454941c45d1b3a323f1433bd6").unwrap();
186 let mut hash = [0u8; 20];
187 hash.copy_from_slice(&pubkey_hash);
188
189 let addr = Address::from_public_key_hash(&hash, true);
190 let script = addr.to_locking_script();
191
192 let binary = script.to_binary();
194 assert_eq!(binary[0], 0x76, "OP_DUP");
195 assert_eq!(binary[1], 0xa9, "OP_HASH160");
196 assert_eq!(binary[2], 0x14, "push 20 bytes");
197 assert_eq!(&binary[3..23], &pubkey_hash[..], "pubkey hash");
198 assert_eq!(binary[23], 0x88, "OP_EQUALVERIFY");
199 assert_eq!(binary[24], 0xac, "OP_CHECKSIG");
200 assert_eq!(binary.len(), 25);
201 }
202
203 #[test]
204 fn test_from_public_key() {
205 use crate::primitives::private_key::PrivateKey;
207
208 let pk = PrivateKey::from_bytes(&{
209 let mut buf = [0u8; 32];
210 buf[31] = 1;
211 buf
212 })
213 .unwrap();
214 let pubkey = pk.to_public_key();
215 let addr = Address::from_public_key(&pubkey, true);
216 assert_eq!(addr.to_string(), "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH");
217 }
218
219 #[test]
220 fn test_display_impl() {
221 let pubkey_hash =
222 crate::primitives::utils::from_hex("751e76e8199196d454941c45d1b3a323f1433bd6").unwrap();
223 let mut hash = [0u8; 20];
224 hash.copy_from_slice(&pubkey_hash);
225
226 let addr = Address::from_public_key_hash(&hash, true);
227 let display_str = format!("{addr}");
228 assert_eq!(display_str, "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH");
229 }
230}