stellar_base/crypto/
strkey.rs1use crate::error::{Error, Result};
2use byteorder::{BigEndian, ByteOrder, LittleEndian};
3use crc16::{State, XMODEM};
4
5const ACCOUNT_ID_VERSION_BYTE: u8 = 6 << 3; const MUXED_ACCOUNT_VERSION_BYTE: u8 = 12 << 3; const SECRET_SEED_VERSION_BYTE: u8 = 18 << 3; const PRE_AUTH_TX_VERSION_BYTE: u8 = 19 << 3; const SHA256_HASH_VERSION_BYTE: u8 = 23 << 3; static ALPHABET: base32::Alphabet = base32::Alphabet::Rfc4648 { padding: false };
12
13pub fn encode_account_id(data: &[u8]) -> String {
14 encode_check(ACCOUNT_ID_VERSION_BYTE, data)
15}
16
17pub fn decode_account_id(data: &str) -> Result<Vec<u8>> {
18 decode_check(ACCOUNT_ID_VERSION_BYTE, data)
19}
20
21pub fn encode_muxed_account(data: &[u8], id: u64) -> String {
22 let mut data_to_encode = Vec::new();
23 data_to_encode.resize(8 + data.len(), b'0');
24 BigEndian::write_u64(&mut data_to_encode[..8], id);
25 data_to_encode[8..].copy_from_slice(data);
26 encode_check(MUXED_ACCOUNT_VERSION_BYTE, &data_to_encode)
27}
28
29pub fn decode_muxed_account(data: &str) -> Result<(Vec<u8>, u64)> {
30 let bytes = decode_check(MUXED_ACCOUNT_VERSION_BYTE, data)?;
31 let mut decoded_data = Vec::new();
32 decoded_data.resize(bytes.len() - 8, b'0');
33 let id = BigEndian::read_u64(&bytes[..8]);
34 decoded_data.copy_from_slice(&bytes[8..]);
35 Ok((decoded_data, id))
36}
37
38pub fn encode_secret_seed(data: &[u8]) -> String {
39 encode_check(SECRET_SEED_VERSION_BYTE, data)
40}
41pub fn decode_secret_seed(data: &str) -> Result<Vec<u8>> {
42 decode_check(SECRET_SEED_VERSION_BYTE, data)
43}
44
45pub fn encode_pre_auth_tx(data: &[u8]) -> String {
46 encode_check(PRE_AUTH_TX_VERSION_BYTE, data)
47}
48pub fn decode_pre_auth_tx(data: &str) -> Result<Vec<u8>> {
49 decode_check(PRE_AUTH_TX_VERSION_BYTE, data)
50}
51
52pub fn encode_sha256_hash(data: &[u8]) -> String {
53 encode_check(SHA256_HASH_VERSION_BYTE, data)
54}
55pub fn decode_sha256_hash(data: &str) -> Result<Vec<u8>> {
56 decode_check(SHA256_HASH_VERSION_BYTE, data)
57}
58
59fn encode_check(version: u8, indata: &[u8]) -> String {
60 let mut data = Vec::with_capacity(35);
61 data.push(version);
62 data.extend_from_slice(indata);
63 let checksum = calculate_checksum(&data);
64 let data_end = data.len();
65 data.resize(data_end + 2, 0);
66 LittleEndian::write_u16(&mut data[data_end..], checksum);
67 base32::encode(ALPHABET, &data)
68}
69
70fn decode_unchecked(data: &str) -> Result<(u8, Vec<u8>)> {
71 let decoded = base32::decode(ALPHABET, data).ok_or(Error::InvalidStrKey)?;
72 let decoded_len = decoded.len();
73
74 if decoded_len == 0 {
75 return Err(Error::InvalidStrKey);
76 }
77
78 let version_byte = decoded[0];
79
80 if version_byte != MUXED_ACCOUNT_VERSION_BYTE && decoded_len != 35 {
81 return Err(Error::InvalidStrKey);
82 }
83
84 if version_byte == MUXED_ACCOUNT_VERSION_BYTE && decoded_len != 43 {
85 return Err(Error::InvalidStrKey);
86 }
87
88 let payload = &decoded[..decoded_len - 2];
89 let data = &payload[1..];
90 let checksum_bytes = &decoded[decoded_len - 2..];
91 let checksum = calculate_checksum(payload);
92
93 if !verify_checksum(checksum, checksum_bytes) {
94 return Err(Error::InvalidStrKeyChecksum);
95 }
96 let key = data.to_vec();
97 Ok((version_byte, key))
98}
99
100fn decode_check(expected_version: u8, data: &str) -> Result<Vec<u8>> {
101 let (version_byte, key) = decode_unchecked(data)?;
102 if version_byte != expected_version {
103 return Err(Error::InvalidStrKeyVersionByte);
104 }
105 Ok(key)
106}
107
108fn calculate_checksum(payload: &[u8]) -> u16 {
109 State::<XMODEM>::calculate(payload)
110}
111
112fn verify_checksum(checksum: u16, bytes: &[u8]) -> bool {
113 let expected = LittleEndian::read_u16(bytes);
114 expected == checksum
115}
116
117#[cfg(test)]
118mod tests {
119 use super::{decode_account_id, encode_account_id};
120 use super::{decode_muxed_account, encode_muxed_account};
121 use super::{decode_pre_auth_tx, encode_pre_auth_tx};
122 use super::{decode_secret_seed, encode_secret_seed};
123 use super::{decode_sha256_hash, encode_sha256_hash};
124 use crate::crypto::DalekKeyPair;
125 use crate::network::Network;
126
127 #[test]
128 fn test_encode_decode_secret_seed() {
129 let seed = "SDJHRQF4GCMIIKAAAQ6IHY42X73FQFLHUULAPSKKD4DFDM7UXWWCRHBE";
130 let secret = decode_secret_seed(seed).unwrap();
131 let encoded = encode_secret_seed(&secret);
132 assert_eq!(seed, &encoded);
133 }
134
135 #[test]
136 fn test_encode_decode_account_id() {
137 let addr = "GCZHXL5HXQX5ABDM26LHYRCQZ5OJFHLOPLZX47WEBP3V2PF5AVFK2A5D";
138 let accountid = decode_account_id(addr).unwrap();
139 let encoded = encode_account_id(&accountid);
140 assert_eq!(addr, &encoded);
141 }
142
143 #[test]
144 fn test_invalid_version() {
145 let addr = "GCZHXL5HXQX5ABDM26LHYRCQZ5OJFHLOPLZX47WEBP3V2PF5AVFK2A5D";
146 let result = decode_secret_seed(addr);
147 assert!(result.is_err());
148 }
149
150 #[test]
151 fn test_encode_decode_muxed_account() {
152 let addr = "MAAAAAAAAAAAAAB7BQ2L7E5NBWMXDUCMZSIPOBKRDSBYVLMXGSSKF6YNPIB7Y77ITLVL6";
153 let (key, id) = decode_muxed_account(addr).unwrap();
154 assert_eq!(0, id);
155 let public_addr = encode_account_id(&key);
156 assert_eq!(
157 "GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ",
158 public_addr
159 );
160 let back = encode_muxed_account(&key, 0);
161 assert_eq!(addr, back);
162 }
163
164 #[test]
165 fn test_encode_decode_muxed_account_with_large_id() {
166 let addr = "MCAAAAAAAAAAAAB7BQ2L7E5NBWMXDUCMZSIPOBKRDSBYVLMXGSSKF6YNPIB7Y77ITKNOG";
167 let (key, id) = decode_muxed_account(addr).unwrap();
168 assert_eq!(9223372036854775808, id);
169 let public_addr = encode_account_id(&key);
170 assert_eq!(
171 "GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ",
172 public_addr
173 );
174 let back = encode_muxed_account(&key, id);
175 assert_eq!(addr, back);
176 }
177
178 #[test]
179 fn test_invalid_account_id() {
180 let addresses = vec![
181 "SAA6NXOBOXP3RXGAXBW6PGFI5BPK4ODVAWITS4VDOMN5C2M4B66ZML",
182 "MAAAAAAAAAAAAAB7BQ2L7E5NBWMXDUCMZSIPOBKRDSBYVLMXGSSKF6YNPIB7Y77ITLVL6",
183 "GAAAAAAAACGC6",
184 "GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUACUSI",
185 "G47QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVP2I",
186 "",
187 ];
188 for addr in addresses {
189 let result = decode_account_id(addr);
190 assert!(result.is_err());
191 }
192 }
193
194 #[test]
195 fn test_invalid_muxed_account() {
196 let addresses = vec![
197 "SAA6NXOBOXP3RXGAXBW6PGFI5BPK4ODVAWITS4VDOMN5C2M4B66ZML",
198 "GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ",
199 "MAAAAAAAAAAAAAB7BQ2L7E5NBWMXDUCMZSIPOBKRDSBYVLMXGSSKF6YNPIB7Y77ITIADJPA",
200 "M4AAAAAAAAAAAAB7BQ2L7E5NBWMXDUCMZSIPOBKRDSBYVLMXGSSKF6YNPIB7Y77ITIU2K",
201 "MAAAAAAAAAAAAAB7BQ2L7E5NBWMXDUCMZSIPOBKRDSBYVLMXGSSKF6YNPIB7Y77ITLVL4",
202 "",
203 ];
204
205 for addr in addresses {
206 let result = decode_muxed_account(addr);
207 assert!(result.is_err());
208 }
209 }
210
211 #[test]
212 fn test_pre_auth_tx() {
213 let keypair = DalekKeyPair::from_network(&Network::new_test()).unwrap();
214 let pk = keypair.public_key();
215 let encoded = encode_pre_auth_tx(pk.as_bytes());
216 assert_eq!('T', encoded.chars().next().unwrap());
217 let decoded = decode_pre_auth_tx(&encoded).unwrap();
218 assert_eq!(pk.as_bytes(), &decoded[..]);
219 }
220
221 #[test]
222 fn test_sha256_hash() {
223 let keypair = DalekKeyPair::from_network(&Network::new_test()).unwrap();
224 let pk = keypair.public_key();
225 let encoded = encode_sha256_hash(pk.as_bytes());
226 assert_eq!('X', encoded.chars().next().unwrap());
227 let decoded = decode_sha256_hash(&encoded).unwrap();
228 assert_eq!(pk.as_bytes(), &decoded[..]);
229 }
230}