1use crate::Error;
2use bech32::Bech32m;
3use bech32::Hrp;
4use bitcoin::key::TweakedPublicKey;
5use bitcoin::Network;
6use bitcoin::ScriptBuf;
7use bitcoin::XOnlyPublicKey;
8use std::str::FromStr;
9
10#[derive(Debug, Clone, Copy, PartialEq)]
11pub struct ArkAddress {
12 version: u8,
13 hrp: Hrp,
14 server: XOnlyPublicKey,
15 vtxo_tap_key: TweakedPublicKey,
16}
17
18impl ArkAddress {
19 pub fn to_p2tr_script_pubkey(&self) -> ScriptBuf {
20 ScriptBuf::new_p2tr_tweaked(self.vtxo_tap_key)
21 }
22
23 pub fn to_sub_dust_script_pubkey(&self) -> ScriptBuf {
24 ScriptBuf::new_op_return(self.vtxo_tap_key.serialize())
25 }
26}
27
28impl ArkAddress {
29 pub fn new(network: Network, server: XOnlyPublicKey, vtxo_tap_key: TweakedPublicKey) -> Self {
30 let hrp = match network {
31 Network::Bitcoin => "ark",
32 Network::Testnet | Network::Testnet4 | Network::Signet | Network::Regtest => "tark",
33 };
34
35 let hrp = Hrp::parse_unchecked(hrp);
36
37 Self {
38 version: 0,
39 hrp,
40 server,
41 vtxo_tap_key,
42 }
43 }
44
45 pub fn encode(&self) -> String {
46 let mut bytes = [0u8; 65];
47
48 bytes[0] = self.version;
49
50 bytes[1..33].copy_from_slice(&self.server.serialize());
51 bytes[33..].copy_from_slice(&self.vtxo_tap_key.serialize());
52
53 bech32::encode::<Bech32m>(self.hrp, bytes.as_slice()).expect("data can be encoded")
54 }
55
56 pub fn decode(value: &str) -> Result<Self, Error> {
57 let (hrp, bytes) = bech32::decode(value).map_err(Error::address_format)?;
58
59 if bytes.len() != 65 {
61 return Err(Error::address_format(format!(
62 "invalid ark address length: expected 65 bytes, got {}",
63 bytes.len()
64 )));
65 }
66
67 let version = bytes[0];
68
69 let server = XOnlyPublicKey::from_slice(&bytes[1..33]).map_err(Error::address_format)?;
70 let vtxo_tap_key =
71 XOnlyPublicKey::from_slice(&bytes[33..]).map_err(Error::address_format)?;
72
73 let vtxo_tap_key = TweakedPublicKey::dangerous_assume_tweaked(vtxo_tap_key);
76
77 Ok(Self {
78 version,
79 hrp,
80 server,
81 vtxo_tap_key,
82 })
83 }
84}
85
86impl std::fmt::Display for ArkAddress {
87 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88 f.write_str(&self.encode())
89 }
90}
91
92impl FromStr for ArkAddress {
93 type Err = Error;
94
95 fn from_str(s: &str) -> Result<Self, Self::Err> {
96 Self::decode(s)
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103 use bitcoin::hex::DisplayHex;
104
105 #[test]
107 fn roundtrip() {
108 let address = "tark1qqellv77udfmr20tun8dvju5vgudpf9vxe8jwhthrkn26fz96pawqfdy8nk05rsmrf8h94j26905e7n6sng8y059z8ykn2j5xcuw4xt846qj6x";
109
110 let decoded = ArkAddress::decode(address).unwrap();
111
112 let hrp = decoded.hrp.to_string();
113 assert_eq!(hrp, "tark");
114
115 let version = decoded.version;
116 assert_eq!(version, 0);
117
118 let server = decoded.server.serialize().as_hex().to_string();
119 assert_eq!(
120 server,
121 "33ffb3dee353b1a9ebe4ced64b946238d0a4ac364f275d771da6ad2445d07ae0"
122 );
123
124 let vtxo_tap_key = decoded.vtxo_tap_key.serialize().as_hex().to_string();
125 assert_eq!(
126 vtxo_tap_key,
127 "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967"
128 );
129
130 let encoded = decoded.encode();
131
132 assert_eq!(encoded, address);
133 }
134}