1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
use byteorder::{ByteOrder, LittleEndian};
use base32;
use crc16::{State, XMODEM};
use error::{Result, Error};
pub type Key = Vec<u8>;
const ACCOUNT_ID_VERSION_BYTE: u8 = 6 << 3;
const 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 };
#[derive(Debug, Clone)]
pub enum StrKey {
AccountId(Key),
Seed(Key),
PreAuthTx(Key),
Sha256Hash(Key),
}
impl StrKey {
pub fn from_account_id(account_id: &str) -> Result<StrKey> {
Self::decode(ACCOUNT_ID_VERSION_BYTE, account_id).map(StrKey::AccountId)
}
pub fn from_seed(seed: &str) -> Result<StrKey> {
Self::decode(SEED_VERSION_BYTE, seed).map(StrKey::Seed)
}
pub fn from_pre_auth_tx(pre_auth_tx: &str) -> Result<StrKey> {
Self::decode(PRE_AUTH_TX_VERSION_BYTE, pre_auth_tx).map(StrKey::PreAuthTx)
}
pub fn from_sha256_hash(hash: &str) -> Result<StrKey> {
Self::decode(SHA256_HASH_VERSION_BYTE, hash).map(StrKey::Sha256Hash)
}
pub fn encode(&self) -> Result<String> {
let version_byte = self.version_byte();
let mut data = Vec::with_capacity(35);
data.push(version_byte);
match *self {
StrKey::AccountId(ref key) => data.extend_from_slice(&key),
StrKey::Seed(ref key) => data.extend_from_slice(&key),
StrKey::PreAuthTx(ref key) => data.extend_from_slice(&key),
StrKey::Sha256Hash(ref key) => data.extend_from_slice(&key),
}
let checksum = Self::calculate_checksum(&data);
let data_end = data.len();
data.resize(data_end + 2, 0);
LittleEndian::write_u16(&mut data[data_end..], checksum);
Ok(base32::encode(ALPHABET, &data))
}
fn decode(expected_version_byte: u8, encoded: &str) -> Result<Key> {
let decoded = base32::decode(ALPHABET, &encoded).ok_or(Error::InvalidStrKey)?;
let decoded_len = decoded.len();
let version_byte = decoded[0];
if version_byte != expected_version_byte {
return Err(Error::InvalidStrKeyVersionByte);
}
let payload = &decoded[..decoded_len - 2];
let data = &payload[1..];
let checksum_bytes = &decoded[decoded_len - 2..];
let checksum = Self::calculate_checksum(payload);
if Self::verify_checksum(checksum, checksum_bytes) {
let key = data.to_vec();
Ok(key)
} else {
Err(Error::InvalidStrKeyChecksum)
}
}
fn calculate_checksum(payload: &[u8]) -> u16 {
State::<XMODEM>::calculate(payload)
}
fn verify_checksum(checksum: u16, bytes: &[u8]) -> bool {
let expected = LittleEndian::read_u16(bytes);
expected == checksum
}
fn version_byte(&self) -> u8 {
match *self {
StrKey::AccountId(_) => ACCOUNT_ID_VERSION_BYTE,
StrKey::Seed(_) => SEED_VERSION_BYTE,
StrKey::PreAuthTx(_) => PRE_AUTH_TX_VERSION_BYTE,
StrKey::Sha256Hash(_) => SHA256_HASH_VERSION_BYTE,
}
}
}
#[cfg(test)]
mod tests {
use StrKey;
#[test]
fn test_from_account_id() {
let account_id = "GCLDNMHZTEY6PUYQBYOVERBBZ2W3RLMYOSZWHAMY5R4YW2N6MM4LFA72";
let strkey = StrKey::from_account_id(&account_id).unwrap();
let new_account_id = strkey.encode().unwrap();
assert_eq!(account_id, new_account_id);
}
#[test]
fn test_from_seed() {
let seed = "SDAKFNYEIAORZKKCYRILFQKLLOCNPL5SWJ3YY5NM3ZH6GJSZGXHZEPQS";
let strkey = StrKey::from_seed(&seed).unwrap();
let new_seed = strkey.encode().unwrap();
assert_eq!(seed, new_seed);
}
#[test]
fn test_from_seed_returns_error() {
let seed = "GDAKFNYEIAORZKKCYRILFQKLLOCNPL5SWJ3YY5NM3ZH6GJSZGXHZEPQS";
let strkey = StrKey::from_seed(&seed);
assert!(strkey.is_err());
}
}