1use blake2::{Blake2b, Digest};
9use serde::{Deserialize, Serialize};
10use std::convert::{TryFrom, TryInto};
11use std::fmt::{Display, Error, Formatter};
12use std::str::FromStr;
13use thiserror::Error;
14
15#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize)]
16pub struct Address(String);
17
18impl Display for Address {
19 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
20 Display::fmt(&self.0, f)
21 }
22}
23
24impl TryFrom<String> for Address {
25 type Error = AddressError;
26
27 fn try_from(value: String) -> Result<Self, Self::Error> {
28 let bytes = bs58::decode(&value).into_vec()?;
29 if bytes.len() != 35 {
30 return Err(AddressError::InvalidSs58Length { len: bytes.len() });
31 }
32 let hash = ss58hash(&bytes[..33]);
33 if bytes[33..] != hash.as_ref()[0..2] {
34 return Err(AddressError::InvalidSs58Checksum);
35 }
36 Ok(Address(value))
37 }
38}
39
40impl FromStr for Address {
41 type Err = AddressError;
42
43 fn from_str(s: &str) -> Result<Self, Self::Err> {
44 s.to_owned().try_into()
45 }
46}
47
48impl<'de> Deserialize<'de> for Address {
49 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
50 where
51 D: serde::Deserializer<'de>,
52 {
53 Address::try_from(String::deserialize(deserializer)?).map_err(serde::de::Error::custom)
54 }
55}
56
57const PREFIX: &[u8] = b"SS58PRE";
58fn ss58hash(data: &[u8]) -> impl AsRef<[u8]> {
63 let mut context = Blake2b::new();
64 context.input(PREFIX);
65 context.input(data);
66 context.result()
67}
68
69#[derive(Clone, Debug, Eq, Error, PartialEq, Serialize)]
70pub enum AddressError {
71 #[error("Address did not have a valid checksum")]
72 InvalidSs58Checksum,
73 #[error("Expected 35 bytes in the address, but found {len}")]
74 InvalidSs58Length { len: usize },
75 #[error("Address wasn't proper b58: {source:?}")]
76 NotBase58 {
77 #[serde(serialize_with = "xand_utils::snafu_extensions::debug_serialize")]
78 #[from]
79 source: bs58::decode::Error,
80 },
81}
82
83#[cfg(test)]
84mod test {
85 use super::*;
86 use proptest::proptest;
87 use sp_core::{crypto::Ss58Codec, ed25519, sr25519, Pair};
88
89 proptest! {
90 #[test]
91 fn test_address_validation(pubkey_bytes: [u8; 32]) {
92 let sr_key = sr25519::Public::from_raw(pubkey_bytes);
93 let ed_key = ed25519::Public::from_raw(pubkey_bytes);
94 let sr_str = sr_key.to_ss58check();
95 let ed_str = ed_key.to_ss58check();
96 Address::try_from(sr_str).unwrap();
98 Address::try_from(ed_str).unwrap();
99 }
100 }
101
102 #[test]
103 fn test_bad_address_checksum_fails_validation() {
104 let sr_key = sr25519::Pair::from_seed(&[42; 32]);
105 let mut as_ss58 = sr_key.public().to_ss58check();
106 as_ss58.pop();
108 as_ss58.push('s');
109 assert_eq!(
110 as_ss58.parse::<Address>().unwrap_err(),
111 AddressError::InvalidSs58Checksum
112 );
113 }
114
115 #[test]
116 fn test_bad_address_length_fails_validation() {
117 let sr_key = sr25519::Pair::from_seed(&[42; 32]);
118 let mut as_ss58 = sr_key.public().to_ss58check();
119 as_ss58.pop();
121 assert_eq!(
122 Address::try_from(as_ss58.clone()).unwrap_err(),
123 AddressError::InvalidSs58Length { len: 34 }
124 );
125 as_ss58.push('s');
127 as_ss58.push('s');
128 assert_eq!(
129 Address::try_from(as_ss58).unwrap_err(),
130 AddressError::InvalidSs58Length { len: 36 }
131 );
132 }
133
134 #[test]
135 fn deserializing_invalid_address_fails() {
136 let address = serde_json::from_str::<Address>(r#""""#);
137 assert!(address.is_err());
138 }
139}