Skip to main content

bsv_rs/script/
address.rs

1//! Bitcoin address (P2PKH) representation.
2//!
3//! This module provides the [`Address`] struct for working with Bitcoin P2PKH addresses,
4//! including parsing from strings, creating from public keys or hashes, and encoding
5//! to Base58Check format.
6//!
7//! # Example
8//!
9//! ```rust
10//! use bsv_rs::script::Address;
11//! use bsv_rs::primitives::ec::PrivateKey;
12//!
13//! let private_key = PrivateKey::from_hex(
14//!     "0000000000000000000000000000000000000000000000000000000000000001",
15//! ).unwrap();
16//! let address = Address::new_from_public_key(&private_key.public_key(), true).unwrap();
17//! assert_eq!(address.to_string(), "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH");
18//! ```
19
20use std::fmt;
21use std::str::FromStr;
22
23use crate::primitives::ec::PublicKey;
24use crate::primitives::encoding::{from_base58_check, to_base58_check};
25use crate::primitives::hash::hash160;
26use crate::{Error, Result};
27
28/// Mainnet P2PKH address version byte.
29const MAINNET_PREFIX: u8 = 0x00;
30
31/// Testnet P2PKH address version byte.
32const TESTNET_PREFIX: u8 = 0x6f;
33
34/// A Bitcoin P2PKH address.
35///
36/// Contains the 20-byte public key hash and the network prefix byte.
37/// Supports both mainnet (prefix `0x00`, addresses start with `1`) and
38/// testnet (prefix `0x6f`, addresses start with `m` or `n`).
39///
40/// # Cross-SDK Compatibility
41///
42/// This implementation is compatible with the Go SDK's `script.Address` and
43/// the TypeScript SDK's address handling. The same address strings are produced
44/// for the same public keys across all three SDKs.
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct Address {
47    /// The 20-byte RIPEMD160(SHA256(pubkey)) hash.
48    pub_key_hash: [u8; 20],
49    /// The network version prefix byte (0x00 for mainnet, 0x6f for testnet).
50    prefix: u8,
51}
52
53impl Address {
54    /// Creates an Address from a Base58Check encoded address string.
55    ///
56    /// Validates the checksum and ensures the address uses a supported version
57    /// prefix (mainnet `0x00` or testnet `0x6f`).
58    ///
59    /// # Arguments
60    ///
61    /// * `address` - A P2PKH address string
62    ///
63    /// # Returns
64    ///
65    /// The parsed Address, or an error if the string is invalid.
66    ///
67    /// # Example
68    ///
69    /// ```rust
70    /// use bsv_rs::script::Address;
71    ///
72    /// let addr = Address::new_from_string("1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH").unwrap();
73    /// assert_eq!(addr.to_string(), "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH");
74    /// ```
75    pub fn new_from_string(address: &str) -> Result<Self> {
76        let (version, payload) = from_base58_check(address)?;
77
78        if version.len() != 1 {
79            return Err(Error::InvalidAddress(format!(
80                "invalid address version length for '{}'",
81                address
82            )));
83        }
84
85        let prefix = version[0];
86        match prefix {
87            MAINNET_PREFIX | TESTNET_PREFIX => {}
88            _ => {
89                return Err(Error::UnsupportedAddress(address.to_string()));
90            }
91        }
92
93        if payload.len() != 20 {
94            return Err(Error::InvalidAddressLength(address.to_string()));
95        }
96
97        let mut pub_key_hash = [0u8; 20];
98        pub_key_hash.copy_from_slice(&payload);
99
100        Ok(Self {
101            pub_key_hash,
102            prefix,
103        })
104    }
105
106    /// Creates an Address from a raw 20-byte public key hash.
107    ///
108    /// # Arguments
109    ///
110    /// * `hash` - The 20-byte public key hash (RIPEMD160(SHA256(pubkey)))
111    /// * `mainnet` - If true, creates a mainnet address; otherwise testnet
112    ///
113    /// # Returns
114    ///
115    /// The Address, or an error if the hash is not 20 bytes.
116    ///
117    /// # Example
118    ///
119    /// ```rust
120    /// use bsv_rs::script::Address;
121    ///
122    /// let hash = [0u8; 20];
123    /// let addr = Address::new_from_public_key_hash(&hash, true).unwrap();
124    /// ```
125    pub fn new_from_public_key_hash(hash: &[u8], mainnet: bool) -> Result<Self> {
126        if hash.len() != 20 {
127            return Err(Error::InvalidDataLength {
128                expected: 20,
129                actual: hash.len(),
130            });
131        }
132
133        let mut pub_key_hash = [0u8; 20];
134        pub_key_hash.copy_from_slice(hash);
135
136        let prefix = if mainnet {
137            MAINNET_PREFIX
138        } else {
139            TESTNET_PREFIX
140        };
141
142        Ok(Self {
143            pub_key_hash,
144            prefix,
145        })
146    }
147
148    /// Creates an Address from a PublicKey.
149    ///
150    /// Computes the hash160 (RIPEMD160(SHA256(compressed_pubkey))) and creates
151    /// a mainnet or testnet address.
152    ///
153    /// # Arguments
154    ///
155    /// * `public_key` - The public key to derive the address from
156    /// * `mainnet` - If true, creates a mainnet address; otherwise testnet
157    ///
158    /// # Returns
159    ///
160    /// The Address.
161    ///
162    /// # Example
163    ///
164    /// ```rust
165    /// use bsv_rs::script::Address;
166    /// use bsv_rs::primitives::ec::PrivateKey;
167    ///
168    /// let key = PrivateKey::random();
169    /// let addr = Address::new_from_public_key(&key.public_key(), true).unwrap();
170    /// ```
171    pub fn new_from_public_key(public_key: &PublicKey, mainnet: bool) -> Result<Self> {
172        let h = hash160(&public_key.to_compressed());
173        Self::new_from_public_key_hash(&h, mainnet)
174    }
175
176    /// Returns the 20-byte public key hash.
177    ///
178    /// This is the RIPEMD160(SHA256(compressed_pubkey)) value that is the same
179    /// regardless of the network type (mainnet or testnet).
180    pub fn public_key_hash(&self) -> &[u8] {
181        &self.pub_key_hash
182    }
183
184    /// Returns the network prefix byte.
185    ///
186    /// - `0x00` for mainnet
187    /// - `0x6f` for testnet
188    pub fn prefix(&self) -> u8 {
189        self.prefix
190    }
191
192    /// Returns true if this is a mainnet address.
193    pub fn is_mainnet(&self) -> bool {
194        self.prefix == MAINNET_PREFIX
195    }
196
197    /// Validates whether a string is a valid P2PKH address.
198    ///
199    /// Returns true if the address can be parsed successfully with a valid
200    /// checksum and a supported version prefix.
201    ///
202    /// # Arguments
203    ///
204    /// * `address` - The address string to validate
205    ///
206    /// # Example
207    ///
208    /// ```rust
209    /// use bsv_rs::script::Address;
210    ///
211    /// assert!(Address::is_valid_address("1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH"));
212    /// assert!(!Address::is_valid_address("invalid"));
213    /// ```
214    pub fn is_valid_address(address: &str) -> bool {
215        Self::new_from_string(address).is_ok()
216    }
217}
218
219impl fmt::Display for Address {
220    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221        write!(f, "{}", to_base58_check(&self.pub_key_hash, &[self.prefix]))
222    }
223}
224
225impl FromStr for Address {
226    type Err = Error;
227
228    fn from_str(s: &str) -> Result<Self> {
229        Self::new_from_string(s)
230    }
231}
232
233#[cfg(test)]
234mod tests {
235    use super::*;
236    use crate::primitives::ec::PrivateKey;
237    use crate::primitives::encoding::{from_hex, to_hex};
238
239    const TEST_PUBLIC_KEY_HEX: &str =
240        "026cf33373a9f3f6c676b75b543180703df225f7f8edbffedc417718a8ad4e89ce";
241    const TEST_PUBLIC_KEY_HASH: &str = "00ac6144c4db7b5790f343cf0477a65fb8a02eb7";
242
243    #[test]
244    fn test_new_from_string_mainnet() {
245        let address_str = "1E7ucTTWRTahCyViPhxSMor2pj4VGQdFMr";
246        let addr = Address::new_from_string(address_str).unwrap();
247
248        assert_eq!(
249            to_hex(addr.public_key_hash()),
250            "8fe80c75c9560e8b56ed64ea3c26e18d2c52211b"
251        );
252        assert_eq!(addr.to_string(), address_str);
253        assert!(addr.is_mainnet());
254    }
255
256    #[test]
257    fn test_new_from_string_testnet() {
258        let address_str = "mtdruWYVEV1wz5yL7GvpBj4MgifCB7yhPd";
259        let addr = Address::new_from_string(address_str).unwrap();
260
261        assert_eq!(
262            to_hex(addr.public_key_hash()),
263            "8fe80c75c9560e8b56ed64ea3c26e18d2c52211b"
264        );
265        assert_eq!(addr.to_string(), address_str);
266        assert!(!addr.is_mainnet());
267    }
268
269    #[test]
270    fn test_new_from_string_short_address() {
271        let result = Address::new_from_string("ADD8E55");
272        assert!(result.is_err());
273    }
274
275    #[test]
276    fn test_new_from_string_unsupported_address() {
277        let result = Address::new_from_string("27BvY7rFguYQvEL872Y7Fo77Y3EBApC2EK");
278        assert!(result.is_err());
279    }
280
281    #[test]
282    fn test_new_from_public_key_hash_mainnet() {
283        let hash = from_hex(TEST_PUBLIC_KEY_HASH).unwrap();
284        let addr = Address::new_from_public_key_hash(&hash, true).unwrap();
285
286        assert_eq!(to_hex(addr.public_key_hash()), TEST_PUBLIC_KEY_HASH);
287        assert_eq!(addr.to_string(), "114ZWApV4EEU8frr7zygqQcB1V2BodGZuS");
288    }
289
290    #[test]
291    fn test_new_from_public_key_hash_testnet() {
292        let hash = from_hex(TEST_PUBLIC_KEY_HASH).unwrap();
293        let addr = Address::new_from_public_key_hash(&hash, false).unwrap();
294
295        assert_eq!(to_hex(addr.public_key_hash()), TEST_PUBLIC_KEY_HASH);
296        assert_eq!(addr.to_string(), "mfaWoDuTsFfiunLTqZx4fKpVsUctiDV9jk");
297    }
298
299    #[test]
300    fn test_new_from_public_key_hash_invalid_length() {
301        let result = Address::new_from_public_key_hash(&[0u8; 19], true);
302        assert!(result.is_err());
303
304        let result = Address::new_from_public_key_hash(&[0u8; 21], true);
305        assert!(result.is_err());
306    }
307
308    #[test]
309    fn test_new_from_public_key_mainnet() {
310        let pubkey = PublicKey::from_hex(TEST_PUBLIC_KEY_HEX).unwrap();
311        let addr = Address::new_from_public_key(&pubkey, true).unwrap();
312
313        assert_eq!(to_hex(addr.public_key_hash()), TEST_PUBLIC_KEY_HASH);
314        assert_eq!(addr.to_string(), "114ZWApV4EEU8frr7zygqQcB1V2BodGZuS");
315    }
316
317    #[test]
318    fn test_new_from_public_key_testnet() {
319        let pubkey = PublicKey::from_hex(TEST_PUBLIC_KEY_HEX).unwrap();
320        let addr = Address::new_from_public_key(&pubkey, false).unwrap();
321
322        assert_eq!(to_hex(addr.public_key_hash()), TEST_PUBLIC_KEY_HASH);
323        assert_eq!(addr.to_string(), "mfaWoDuTsFfiunLTqZx4fKpVsUctiDV9jk");
324    }
325
326    #[test]
327    fn test_roundtrip_mainnet() {
328        let address_str = "114ZWApV4EEU8frr7zygqQcB1V2BodGZuS";
329        let addr = Address::new_from_string(address_str).unwrap();
330        assert_eq!(addr.to_string(), address_str);
331    }
332
333    #[test]
334    fn test_roundtrip_testnet() {
335        let address_str = "mfaWoDuTsFfiunLTqZx4fKpVsUctiDV9jk";
336        let addr = Address::new_from_string(address_str).unwrap();
337        assert_eq!(addr.to_string(), address_str);
338    }
339
340    #[test]
341    fn test_from_str_trait() {
342        let addr: Address = "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH".parse().unwrap();
343        assert!(addr.is_mainnet());
344    }
345
346    #[test]
347    fn test_display_trait() {
348        let addr = Address::new_from_string("1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH").unwrap();
349        let displayed = format!("{}", addr);
350        assert_eq!(displayed, "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH");
351    }
352
353    #[test]
354    fn test_is_valid_address() {
355        assert!(Address::is_valid_address(
356            "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH"
357        ));
358        assert!(Address::is_valid_address(
359            "114ZWApV4EEU8frr7zygqQcB1V2BodGZuS"
360        ));
361        assert!(Address::is_valid_address(
362            "mfaWoDuTsFfiunLTqZx4fKpVsUctiDV9jk"
363        ));
364        assert!(!Address::is_valid_address("invalid"));
365        assert!(!Address::is_valid_address(""));
366    }
367
368    #[test]
369    fn test_invalid_checksum() {
370        // Modify last character to break checksum
371        let result = Address::new_from_string("1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN3");
372        assert!(result.is_err());
373    }
374
375    #[test]
376    fn test_generator_point_address() {
377        // Known test vector: generator point compressed public key
378        let pubkey = PublicKey::from_hex(
379            "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
380        )
381        .unwrap();
382        let addr = Address::new_from_public_key(&pubkey, true).unwrap();
383        assert_eq!(addr.to_string(), "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH");
384    }
385
386    #[test]
387    fn test_private_key_address_consistency() {
388        // Verify Address matches PublicKey::to_address()
389        let private_key = PrivateKey::from_hex(
390            "0000000000000000000000000000000000000000000000000000000000000001",
391        )
392        .unwrap();
393        let pubkey = private_key.public_key();
394
395        let addr_from_address = Address::new_from_public_key(&pubkey, true).unwrap();
396        let addr_from_pubkey = pubkey.to_address();
397
398        assert_eq!(addr_from_address.to_string(), addr_from_pubkey);
399    }
400
401    #[test]
402    fn test_locking_script_to_address() {
403        use crate::script::template::ScriptTemplate;
404        use crate::script::templates::P2PKH;
405
406        let private_key = PrivateKey::from_hex(
407            "0000000000000000000000000000000000000000000000000000000000000001",
408        )
409        .unwrap();
410        let pubkey = private_key.public_key();
411        let pubkey_hash = pubkey.hash160();
412
413        // Create P2PKH locking script
414        let locking = P2PKH::new().lock(&pubkey_hash).unwrap();
415
416        // Extract address from locking script
417        let addr = locking.to_address();
418        assert!(addr.is_some());
419        let addr = addr.unwrap();
420        assert_eq!(addr.to_string(), pubkey.to_address());
421    }
422
423    #[test]
424    fn test_locking_script_to_address_non_p2pkh() {
425        use crate::script::LockingScript;
426
427        // OP_RETURN script
428        let op_return = LockingScript::from_asm("OP_RETURN").unwrap();
429        assert!(op_return.to_address().is_none());
430
431        // P2SH script
432        let p2sh =
433            LockingScript::from_hex("a914000000000000000000000000000000000000000087").unwrap();
434        assert!(p2sh.to_address().is_none());
435
436        // Empty script
437        let empty = LockingScript::new();
438        assert!(empty.to_address().is_none());
439    }
440}