1#![forbid(unsafe_code)]
30#![cfg_attr(not(feature = "std"), no_std)]
31
32extern crate alloc;
33use alloc::string::ToString;
34use bech32::{self, Bech32m, Hrp};
35use core::fmt;
36use hex::{decode as hex_to_bytes, encode as bytes_to_hex};
37
38#[cfg(not(feature = "std"))]
40use alloc::{string::String, vec::Vec};
41
42#[cfg(feature = "std")]
43use std::{string::String, vec::Vec};
44
45#[derive(Debug, Copy, Clone, Eq, PartialEq)]
51pub enum Network {
52 Mainnet,
54 Testnet,
56 Signet,
58 Regtest,
60 Local,
62}
63
64impl Network {
65 fn hrp(self) -> &'static str {
66 match self {
67 Network::Mainnet => "sp",
68 Network::Testnet => "spt",
69 Network::Signet => "sps",
70 Network::Regtest => "sprt",
71 Network::Local => "spl",
72 }
73 }
74
75 fn from_hrp(hrp: &str) -> Option<Self> {
76 match hrp {
77 "sp" => Some(Network::Mainnet),
78 "spt" => Some(Network::Testnet),
79 "sps" => Some(Network::Signet),
80 "sprt" => Some(Network::Regtest),
81 "spl" => Some(Network::Local),
82 _ => None,
83 }
84 }
85}
86
87#[derive(Debug)]
92pub enum SparkAddressError {
93 InvalidBech32(bech32::DecodeError),
95 UnknownPrefix(String),
97 InvalidVariant,
99 MixedCase,
101 InvalidLength,
103 BadProto,
105 Hex(hex::FromHexError),
107 WrongKeyLength(usize),
109 #[cfg(feature = "validate-secp256k1")]
110 InvalidSecp256k1,
112 Bech32Encode(bech32::EncodeError),
114}
115
116impl fmt::Display for SparkAddressError {
117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118 match self {
119 SparkAddressError::InvalidBech32(e) => write!(f, "bech32 decode error: {e}"),
120 SparkAddressError::UnknownPrefix(p) => write!(f, "unknown HRP prefix: {p}"),
121 SparkAddressError::InvalidVariant => write!(f, "bech32 variant is not Bech32m"),
122 SparkAddressError::MixedCase => write!(f, "address contains mixed upper/lower case"),
123 SparkAddressError::InvalidLength => write!(f, "address exceeds maximum length (90)"),
124 SparkAddressError::BadProto => write!(f, "invalid proto payload"),
125 SparkAddressError::Hex(e) => write!(f, "hex decode error: {e}"),
126 SparkAddressError::WrongKeyLength(n) => {
127 write!(f, "wrong pubkey length: {n} (expected 33)")
128 }
129 #[cfg(feature = "validate-secp256k1")]
130 SparkAddressError::InvalidSecp256k1 => write!(f, "invalid secp256k1 pubkey"),
131 SparkAddressError::Bech32Encode(e) => write!(f, "bech32 encode error: {e}"),
132 }
133 }
134}
135
136#[cfg(feature = "std")]
137impl std::error::Error for SparkAddressError {}
138
139impl From<bech32::DecodeError> for SparkAddressError {
140 fn from(e: bech32::DecodeError) -> Self {
141 Self::InvalidBech32(e)
142 }
143}
144
145impl From<bech32::EncodeError> for SparkAddressError {
146 fn from(e: bech32::EncodeError) -> Self {
147 Self::Bech32Encode(e)
148 }
149}
150
151impl From<hex::FromHexError> for SparkAddressError {
152 fn from(e: hex::FromHexError) -> Self {
153 Self::Hex(e)
154 }
155}
156
157#[derive(Debug, Clone, Eq, PartialEq)]
163pub struct SparkAddressData {
164 pub identity_public_key: String,
166 pub network: Network,
168}
169
170const TAG: u8 = 0x0a; fn encode_proto(key: &[u8]) -> Vec<u8> {
177 let mut out = Vec::with_capacity(2 + key.len());
178 out.push(TAG);
179 let key_len: u8 = key.len().try_into().expect("key length exceeds 255 bytes");
181 out.push(key_len);
182 out.extend_from_slice(key);
183 out
184}
185
186fn decode_proto(buf: &[u8]) -> Result<&[u8], SparkAddressError> {
187 if buf.len() >= 3 && buf[0] == TAG && buf[1] as usize + 2 == buf.len() {
188 Ok(&buf[2..])
189 } else {
190 Err(SparkAddressError::BadProto)
191 }
192}
193
194pub fn encode_spark_address(data: &SparkAddressData) -> Result<String, SparkAddressError> {
213 #[cfg(feature = "validate-secp256k1")]
214 validate_pubkey(&data.identity_public_key)?;
215
216 let key_bytes = hex_to_bytes(&data.identity_public_key)?;
217 if key_bytes.len() != 33 {
218 return Err(SparkAddressError::WrongKeyLength(key_bytes.len()));
219 }
220
221 let proto = encode_proto(&key_bytes);
222
223 let hrp = Hrp::parse(data.network.hrp()).expect("static HRP is valid");
224 let addr = bech32::encode::<Bech32m>(hrp, &proto)?;
225
226 Ok(addr)
227}
228
229pub fn decode_spark_address(addr: &str) -> Result<SparkAddressData, SparkAddressError> {
240 if addr.len() > 90 {
245 return Err(SparkAddressError::InvalidLength);
246 }
247
248 let has_upper = addr.bytes().any(|b| b.is_ascii_uppercase());
249 let has_lower = addr.bytes().any(|b| b.is_ascii_lowercase());
250 if has_upper && has_lower {
251 return Err(SparkAddressError::MixedCase);
252 }
253
254 let (hrp, proto) = bech32::decode(addr)?;
255
256 let hrp_str = hrp.to_string();
260 if hrp_str.bytes().any(|b| b.is_ascii_uppercase()) {
261 return Err(SparkAddressError::MixedCase);
262 }
263
264 let reencoded = bech32::encode::<Bech32m>(hrp, &proto)?;
268 if reencoded.to_lowercase() != addr.to_lowercase() {
269 return Err(SparkAddressError::InvalidVariant);
270 }
271
272 let network = Network::from_hrp(&hrp_str)
273 .ok_or_else(|| SparkAddressError::UnknownPrefix(hrp_str.clone()))?;
274
275 let key = decode_proto(&proto)?;
276
277 if key.len() != 33 {
278 return Err(SparkAddressError::WrongKeyLength(key.len()));
279 }
280
281 let hex_key = bytes_to_hex(key);
282
283 #[cfg(feature = "validate-secp256k1")]
284 validate_pubkey(&hex_key)?;
285
286 Ok(SparkAddressData {
287 identity_public_key: hex_key,
288 network,
289 })
290}
291
292#[cfg(feature = "validate-secp256k1")]
297fn validate_pubkey(hex_str: &str) -> Result<(), SparkAddressError> {
298 use secp256k1::PublicKey;
299 let bytes = hex_to_bytes(hex_str)?;
300 PublicKey::from_slice(&bytes).map_err(|_| SparkAddressError::InvalidSecp256k1)?;
301 Ok(())
302}
303
304#[cfg(not(feature = "validate-secp256k1"))]
305fn _validate_pubkey(_: &str) {}
306
307#[cfg(test)]
312mod tests {
313 use super::*;
314
315 const PUBKEY: &str = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798";
316 const MAINNET_ADDRESS: &str =
317 "sp1pgssy7d7vel0nh9m4326qc54e6rskpczn07dktww9rv4nu5ptvt0s9ucez8h3s";
318 const REGTEST_ADDRESS: &str =
319 "sprt1pgssy7d7vel0nh9m4326qc54e6rskpczn07dktww9rv4nu5ptvt0s9ucd5rgc0";
320
321 #[test]
322 fn mainnet_round_trip() {
323 let data = SparkAddressData {
324 identity_public_key: PUBKEY.into(),
325 network: Network::Mainnet,
326 };
327 let encoded = encode_spark_address(&data).unwrap();
328 assert_eq!(encoded, MAINNET_ADDRESS);
329 let decoded = decode_spark_address(&encoded).unwrap();
330 assert_eq!(decoded, data);
331
332 let decoded = decode_spark_address(MAINNET_ADDRESS).unwrap();
333 assert_eq!(decoded.network, Network::Mainnet);
334 assert_eq!(decoded.identity_public_key, PUBKEY);
335 }
336
337 #[test]
338 fn regtest_round_trip() {
339 let data = SparkAddressData {
340 identity_public_key: PUBKEY.into(),
341 network: Network::Regtest,
342 };
343 let encoded = encode_spark_address(&data).unwrap();
344 assert_eq!(encoded, REGTEST_ADDRESS);
345 let decoded = decode_spark_address(&encoded).unwrap();
346 assert_eq!(decoded, data);
347
348 let decoded = decode_spark_address(MAINNET_ADDRESS).unwrap();
349 assert_eq!(decoded.network, Network::Mainnet);
350 assert_eq!(decoded.identity_public_key, PUBKEY);
351 }
352}