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, Eq, Hash)]
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 pub fn server(&self) -> XOnlyPublicKey {
29 self.server
30 }
31}
32
33impl ArkAddress {
34 pub fn new(network: Network, server: XOnlyPublicKey, vtxo_tap_key: TweakedPublicKey) -> Self {
35 let hrp = match network {
36 Network::Bitcoin => "ark",
37 Network::Testnet | Network::Testnet4 | Network::Signet | Network::Regtest => "tark",
38 };
39
40 let hrp = Hrp::parse_unchecked(hrp);
41
42 Self {
43 version: 0,
44 hrp,
45 server,
46 vtxo_tap_key,
47 }
48 }
49
50 pub fn encode(&self) -> String {
51 let mut bytes = [0u8; 65];
52
53 bytes[0] = self.version;
54
55 bytes[1..33].copy_from_slice(&self.server.serialize());
56 bytes[33..].copy_from_slice(&self.vtxo_tap_key.serialize());
57
58 bech32::encode::<Bech32m>(self.hrp, bytes.as_slice()).expect("data can be encoded")
59 }
60
61 pub fn decode(value: &str) -> Result<Self, Error> {
62 let (hrp, bytes) = bech32::decode(value).map_err(Error::address_format)?;
63
64 if bytes.len() != 65 {
66 return Err(Error::address_format(format!(
67 "invalid ark address length: expected 65 bytes, got {}",
68 bytes.len()
69 )));
70 }
71
72 let version = bytes[0];
73
74 let server = XOnlyPublicKey::from_slice(&bytes[1..33]).map_err(Error::address_format)?;
75 let vtxo_tap_key =
76 XOnlyPublicKey::from_slice(&bytes[33..]).map_err(Error::address_format)?;
77
78 let vtxo_tap_key = TweakedPublicKey::dangerous_assume_tweaked(vtxo_tap_key);
81
82 Ok(Self {
83 version,
84 hrp,
85 server,
86 vtxo_tap_key,
87 })
88 }
89}
90
91impl std::fmt::Display for ArkAddress {
92 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93 f.write_str(&self.encode())
94 }
95}
96
97impl FromStr for ArkAddress {
98 type Err = Error;
99
100 fn from_str(s: &str) -> Result<Self, Self::Err> {
101 Self::decode(s)
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use bitcoin::hex::DisplayHex;
109
110 #[test]
112 fn roundtrip() {
113 let address = "tark1qqellv77udfmr20tun8dvju5vgudpf9vxe8jwhthrkn26fz96pawqfdy8nk05rsmrf8h94j26905e7n6sng8y059z8ykn2j5xcuw4xt846qj6x";
114
115 let decoded = ArkAddress::decode(address).unwrap();
116
117 let hrp = decoded.hrp.to_string();
118 assert_eq!(hrp, "tark");
119
120 let version = decoded.version;
121 assert_eq!(version, 0);
122
123 let server = decoded.server.serialize().as_hex().to_string();
124 assert_eq!(
125 server,
126 "33ffb3dee353b1a9ebe4ced64b946238d0a4ac364f275d771da6ad2445d07ae0"
127 );
128
129 let vtxo_tap_key = decoded.vtxo_tap_key.serialize().as_hex().to_string();
130 assert_eq!(
131 vtxo_tap_key,
132 "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967"
133 );
134
135 let encoded = decoded.encode();
136
137 assert_eq!(encoded, address);
138 }
139
140 #[test]
141 fn hash_and_eq() {
142 use std::collections::HashSet;
143
144 let addr_str = "tark1qqellv77udfmr20tun8dvju5vgudpf9vxe8jwhthrkn26fz96pawqfdy8nk05rsmrf8h94j26905e7n6sng8y059z8ykn2j5xcuw4xt846qj6x";
145 let a = ArkAddress::decode(addr_str).unwrap();
146 let b = ArkAddress::decode(addr_str).unwrap();
147
148 assert_eq!(a, b);
150
151 let mut set = HashSet::new();
153 set.insert(a);
154 assert!(!set.insert(b), "duplicate address should not be inserted");
155 assert_eq!(set.len(), 1);
156
157 let other = ArkAddress::new(
159 Network::Regtest,
160 a.server,
161 TweakedPublicKey::dangerous_assume_tweaked(a.server),
162 );
163 assert_ne!(a, other);
164 assert!(set.insert(other));
165 assert_eq!(set.len(), 2);
166 }
167}