1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
use bitcoin::key::Secp256k1;
use bitcoin::{Address, Network, PublicKey};
use ex3_node_error::OtherError;
use ex3_node_types::address_types::BtcAddressType;
use ex3_node_types::chain::Chain;
use num_traits::ToPrimitive;

const BTC_MAIN_NET: u8 = 1u8;
const BTC_TEST_NET: u8 = 2u8;
const BTC_REG_TEST: u8 = 3u8;

pub struct BtcAddressParser;

impl BtcAddressParser {
    pub fn to_address<T: AsRef<[u8]>>(
        pub_key: &T,
        chain: &Chain,
        address_type: &BtcAddressType,
    ) -> super::Result<String> {
        let network = match chain.network.clone().to_u8().unwrap() {
            BTC_MAIN_NET => Network::Bitcoin,
            BTC_TEST_NET => Network::Testnet,
            BTC_REG_TEST => Network::Regtest,
            _ => panic!("Invalid network"),
        };

        let ecdsa_pub_key = PublicKey::from_slice(pub_key.as_ref())
            .map_err(|_| OtherError::new("Invalid address type"))?;

        match address_type {
            BtcAddressType::P2pkh => Ok(Address::p2pkh(&ecdsa_pub_key, network).to_string()),
            BtcAddressType::P2wpkh => Ok(Address::p2wpkh(&ecdsa_pub_key, network)
                .map_err(|e| OtherError::new(format!("Invalid public key: {}", e)))?
                .to_string()),
            BtcAddressType::P2shP2wpkh => Ok(Address::p2shwpkh(&ecdsa_pub_key, network)
                .map_err(|e| OtherError::new(format!("Invalid public key: {}", e)))?
                .to_string()),
            BtcAddressType::P2tr => {
                let secp = Secp256k1::verification_only();
                Ok(Address::p2tr(&secp, ecdsa_pub_key.into(), None, network).to_string())
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use ex3_node_types::chain::{Chain, ChainType};

    #[test]
    fn test_to_address() {
        let pub_key =
            hex::decode("03e3bf8c4b1c2e8cf487257aebb77f5fe8e5175c5eaf5d8e743fb3037ae01780f0")
                .unwrap();

        let btc_test_chain = Chain {
            network: BTC_TEST_NET.into(),
            r#type: ChainType::Bitcoin,
        };

        // Test P2PKH
        let address =
            BtcAddressParser::to_address(&pub_key, &btc_test_chain, &BtcAddressType::P2pkh)
                .unwrap();
        assert_eq!(address.to_lowercase(), "mjejgn6ppovfersvzhdxmum1obqagkclxp");

        // Test P2WPKH
        let address =
            BtcAddressParser::to_address(&pub_key, &btc_test_chain, &BtcAddressType::P2wpkh)
                .unwrap();

        assert_eq!(
            address.to_lowercase(),
            "tb1q94zxlextqvnt2gw0us8hzwu8skntawungngulr"
        );

        // Test P2SH-P2WPKH
        let address =
            BtcAddressParser::to_address(&pub_key, &btc_test_chain, &BtcAddressType::P2shP2wpkh)
                .unwrap();

        assert_eq!(
            address.to_lowercase(),
            "2mw8nsbar2n87etfb3ybmxkysf29tygrryr"
        );

        // Test P2TR
        let address =
            BtcAddressParser::to_address(&pub_key, &btc_test_chain, &BtcAddressType::P2tr).unwrap();

        assert_eq!(
            address.to_lowercase(),
            "tb1pmyu9ecss64r0lhq2fkvytewq4luft6mzd7ewww6lsadtygssswpqdppecv"
        );
    }
}