ark_core/
ark_address.rs

1use crate::Error;
2use bech32::Bech32m;
3use bech32::Hrp;
4use bitcoin::key::TweakedPublicKey;
5use bitcoin::Network;
6use bitcoin::ScriptBuf;
7use bitcoin::XOnlyPublicKey;
8
9#[derive(Debug, Clone, Copy)]
10pub struct ArkAddress {
11    hrp: Hrp,
12    server: XOnlyPublicKey,
13    vtxo_tap_key: TweakedPublicKey,
14}
15
16impl ArkAddress {
17    pub fn to_p2tr_script_pubkey(&self) -> ScriptBuf {
18        ScriptBuf::new_p2tr_tweaked(self.vtxo_tap_key)
19    }
20}
21
22impl ArkAddress {
23    pub fn new(network: Network, server: XOnlyPublicKey, vtxo_tap_key: TweakedPublicKey) -> Self {
24        let hrp = match network {
25            Network::Bitcoin => "ark",
26            _ => "tark",
27        };
28
29        let hrp = Hrp::parse_unchecked(hrp);
30
31        Self {
32            hrp,
33            server,
34            vtxo_tap_key,
35        }
36    }
37
38    pub fn encode(&self) -> String {
39        let mut bytes = [0u8; 64];
40
41        bytes[..32].copy_from_slice(&self.server.serialize());
42        bytes[32..].copy_from_slice(&self.vtxo_tap_key.serialize());
43
44        bech32::encode::<Bech32m>(self.hrp, bytes.as_slice()).expect("data can be encoded")
45    }
46
47    pub fn decode(value: &str) -> Result<Self, Error> {
48        let (hrp, bytes) = bech32::decode(value).map_err(Error::address_format)?;
49
50        let server = XOnlyPublicKey::from_slice(&bytes[..32]).map_err(Error::address_format)?;
51        let vtxo_tap_key =
52            XOnlyPublicKey::from_slice(&bytes[32..]).map_err(Error::address_format)?;
53
54        // It is safe to call `dangerous_assume_tweaked` because we are treating the VTXO tap key as
55        // finished product i.e. we are only going to use it as an address to send coins to.
56        let vtxo_tap_key = TweakedPublicKey::dangerous_assume_tweaked(vtxo_tap_key);
57
58        Ok(Self {
59            hrp,
60            server,
61            vtxo_tap_key,
62        })
63    }
64}
65
66impl std::fmt::Display for ArkAddress {
67    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68        f.write_str(&self.encode())
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75    use bitcoin::hex::DisplayHex;
76
77    // Taken from https://github.com/ark-network/ark/blob/b536a9e65252573aaa48110ef5d0c90894eb550c/common/fixtures/encoding.json.
78    #[test]
79    fn roundtrip() {
80        let address = "tark1x0lm8hhr2wc6n6lyemtyh9rz8rg2ftpkfun46aca56kjg3ws0tsztfpuanaquxc6faedvjk3tax0575y6perapg3e95654pk8r4fjecs5fyd2";
81
82        let decoded = ArkAddress::decode(address).unwrap();
83
84        let hrp = decoded.hrp.to_string();
85        assert_eq!(hrp, "tark");
86
87        let server = decoded.server.serialize().as_hex().to_string();
88        assert_eq!(
89            server,
90            "33ffb3dee353b1a9ebe4ced64b946238d0a4ac364f275d771da6ad2445d07ae0"
91        );
92
93        let vtxo_tap_key = decoded.vtxo_tap_key.serialize().as_hex().to_string();
94        assert_eq!(
95            vtxo_tap_key,
96            "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967"
97        );
98
99        let encoded = decoded.encode();
100
101        assert_eq!(encoded, address);
102    }
103}