stellar_base/crypto/
strkey.rs

1use crate::error::{Error, Result};
2use byteorder::{BigEndian, ByteOrder, LittleEndian};
3use crc16::{State, XMODEM};
4
5const ACCOUNT_ID_VERSION_BYTE: u8 = 6 << 3; // G
6const MUXED_ACCOUNT_VERSION_BYTE: u8 = 12 << 3; // M
7const SECRET_SEED_VERSION_BYTE: u8 = 18 << 3; // S
8const PRE_AUTH_TX_VERSION_BYTE: u8 = 19 << 3; // T
9const SHA256_HASH_VERSION_BYTE: u8 = 23 << 3; // X
10
11static 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}