1use crate::model::pubkey;
2use bip39_dict::Language;
3use libsecp256k1::{PublicKey, SecretKey};
4use serde::{Deserialize, Serialize};
5use sha2::{Digest, Sha256};
6use std::fmt::Write;
7
8use super::errors::{LbErrKind, LbResult};
9
10pub const MAX_USERNAME_LENGTH: usize = 32;
11
12pub type Username = String;
13pub type ApiUrl = String;
14
15#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
16pub struct Account {
17 pub username: Username,
18 pub api_url: ApiUrl,
19 #[serde(with = "secret_key_serializer")]
20 pub private_key: SecretKey,
21}
22
23impl Account {
24 pub fn new(username: String, api_url: String) -> Self {
25 let private_key = pubkey::generate_key();
26 Self { username, api_url, private_key }
27 }
28
29 pub fn public_key(&self) -> PublicKey {
30 PublicKey::from_secret_key(&self.private_key)
31 }
32
33 pub fn get_phrase(&self) -> LbResult<[&'static str; 24]> {
34 let key = self.private_key.serialize();
35 let key_bits = key.iter().fold(String::new(), |mut out, byte| {
36 let _ = write!(out, "{:08b}", byte);
37 out
38 });
39
40 let checksum: String =
41 sha2::Sha256::digest(&key)
42 .into_iter()
43 .fold(String::new(), |mut out, byte| {
44 let _ = write!(out, "{:08b}", byte);
45 out
46 });
47
48 let checksum_last_4_bits = &checksum[..4];
49 let combined_bits = format!("{}{}", key_bits, checksum_last_4_bits);
50
51 let mut phrase: [&str; 24] = Default::default();
52
53 for (i, chunk) in combined_bits
54 .chars()
55 .collect::<Vec<_>>()
56 .chunks(11)
57 .enumerate()
58 {
59 let index =
60 u16::from_str_radix(&chunk.iter().collect::<String>(), 2).map_err(|_| {
61 LbErrKind::Unexpected(
62 "could not parse appropriate private key bits into u16".to_string(),
63 )
64 })?;
65 let word = bip39_dict::ENGLISH.lookup_word(bip39_dict::MnemonicIndex(index));
66
67 phrase[i] = word;
68 }
69
70 Ok(phrase)
71 }
72
73 pub fn phrase_to_private_key(phrases: [&str; 24]) -> LbResult<SecretKey> {
74 let mut combined_bits = phrases
75 .iter()
76 .map(|word| {
77 bip39_dict::ENGLISH
78 .lookup_mnemonic(word)
79 .map(|index| format!("{:011b}", index.0))
80 })
81 .collect::<Result<String, _>>()
82 .map_err(|_| LbErrKind::KeyPhraseInvalid)?;
83
84 if combined_bits.len() != 264 {
85 return Err(LbErrKind::Unexpected("the number of bits after translating the phrase does not equal the expected amount (264)".to_string()).into());
86 }
87
88 for _ in 0..4 {
89 combined_bits.remove(253);
90 }
91
92 let key_bits = &combined_bits[..256];
93 let checksum_last_4_bits = &combined_bits[256..260];
94
95 let mut key: Vec<u8> = Vec::new();
96 for chunk in key_bits.chars().collect::<Vec<_>>().chunks(8) {
97 let comp = u8::from_str_radix(&chunk.iter().collect::<String>(), 2).map_err(|_| {
98 LbErrKind::Unexpected(
99 "could not parse appropriate phrases bits into u8".to_string(),
100 )
101 })?;
102
103 key.push(comp);
104 }
105
106 let gen_checksum: String =
107 sha2::Sha256::digest(&key)
108 .iter()
109 .fold(String::new(), |mut acc, byte| {
110 acc.push_str(&format!("{:08b}", byte));
111 acc
112 });
113
114 let gen_checksum_last_4 = &gen_checksum[..4];
115
116 if gen_checksum_last_4 != checksum_last_4_bits {
117 return Err(LbErrKind::KeyPhraseInvalid)?;
118 }
119
120 Ok(SecretKey::parse_slice(&key).map_err(|e| {
121 error!("unexpected secretkey parse error: {e:?}");
122 LbErrKind::KeyPhraseInvalid
123 })?)
124 }
125
126 pub fn color(&self) -> (u8, u8, u8) {
140 let mut hasher = Sha256::new();
141 hasher.update(&self.username);
142 let result = hasher.finalize();
143
144 (result[0], result[1], result[2])
145 }
146
147 pub fn is_beta(&self) -> bool {
154 matches!(self.username.as_str(), "parth" | "travis" | "smail" | "adam" | "krish" | "aravd")
155 }
156}
157
158pub mod secret_key_serializer {
159 use libsecp256k1::SecretKey;
160 use serde::de::Deserialize;
161 use serde::de::Deserializer;
162 use serde::ser::Serializer;
163
164 pub fn serialize<S>(sk: &SecretKey, serializer: S) -> Result<S::Ok, S::Error>
165 where
166 S: Serializer,
167 {
168 serializer.serialize_bytes(&sk.serialize())
169 }
170
171 pub fn deserialize<'de, D>(deserializer: D) -> Result<SecretKey, D::Error>
172 where
173 D: Deserializer<'de>,
174 {
175 let key = <Vec<u8>>::deserialize(deserializer)?;
176 let sk = SecretKey::parse_slice(&key).map_err(serde::de::Error::custom)?;
177 Ok(sk)
178 }
179}
180
181#[cfg(test)]
182mod test_account_serialization {
183 use libsecp256k1::SecretKey;
184 use rand::rngs::OsRng;
185
186 use crate::model::account::Account;
187
188 #[test]
189 fn account_serialize_deserialize() {
190 let account1 = Account {
191 username: "test".to_string(),
192 api_url: "test.com".to_string(),
193 private_key: SecretKey::random(&mut OsRng),
194 };
195
196 let encoded: Vec<u8> = bincode::serialize(&account1).unwrap();
197 let account2: Account = bincode::deserialize(&encoded).unwrap();
198
199 assert_eq!(account1, account2);
200 }
201
202 #[test]
203 fn verify_account_v1() {
204 let account1 = Account {
205 username: "test1".to_string(),
206 api_url: "test1.com".to_string(),
207 private_key: SecretKey::parse_slice(&[
208 19, 34, 85, 4, 36, 83, 52, 122, 49, 107, 223, 44, 31, 16, 2, 160, 100, 103, 193, 0,
209 67, 15, 184, 133, 33, 111, 91, 143, 137, 232, 240, 42,
210 ])
211 .unwrap(),
212 };
213
214 let account2 = bincode::deserialize(
215 base64::decode("BQAAAAAAAAB0ZXN0MQkAAAAAAAAAdGVzdDEuY29tIAAAAAAAAAATIlUEJFM0ejFr3ywfEAKgZGfBAEMPuIUhb1uPiejwKg==")
216 .unwrap()
217 .as_slice()
218 ).unwrap();
219
220 assert_eq!(account1, account2);
221 }
222
223 #[test]
224 fn verify_account_v2() {
225 let account1 = Account {
226 username: "test1".to_string(),
227 api_url: "test1.com".to_string(),
228 private_key: SecretKey::parse_slice(&[
229 158, 250, 59, 72, 139, 112, 93, 137, 168, 199, 28, 230, 56, 37, 43, 52, 152, 176,
230 243, 149, 124, 11, 2, 126, 73, 118, 252, 112, 225, 207, 34, 90,
231 ])
232 .unwrap(),
233 };
234
235 let account2 = Account {
236 username: "test1".to_string(),
237 api_url: "test1.com".to_string(),
238 private_key: SecretKey::parse_slice(
239 base64::decode("nvo7SItwXYmoxxzmOCUrNJiw85V8CwJ+SXb8cOHPIlo=")
240 .unwrap()
241 .as_slice(),
242 )
243 .unwrap(),
244 };
245
246 assert_eq!(account1, account2);
247 }
248
249 #[test]
250 fn verify_account_phrase() {
251 let account1 = Account {
252 username: "test1".to_string(),
253 api_url: "test1.com".to_string(),
254 private_key: SecretKey::parse_slice(&[
255 234, 169, 139, 200, 30, 42, 176, 229, 16, 101, 229, 85, 125, 47, 182, 24, 154, 8,
256 156, 233, 24, 102, 126, 171, 86, 240, 0, 175, 6, 192, 253, 231,
257 ])
258 .unwrap(),
259 };
260
261 let account2 = Account {
262 username: "test1".to_string(),
263 api_url: "test1.com".to_string(),
264 private_key: Account::phrase_to_private_key([
265 "turkey", "era", "velvet", "detail", "prison", "income", "dose", "royal", "fever",
266 "truly", "unique", "couple", "party", "example", "piece", "art", "leaf", "follow",
267 "rose", "access", "vacant", "gather", "wasp", "audit",
268 ])
269 .unwrap(),
270 };
271
272 assert_eq!(account1, account2)
273 }
274}
275
276#[cfg(test)]
277mod test_account_key_and_phrase {
278 use libsecp256k1::SecretKey;
279 use rand::rngs::OsRng;
280
281 use crate::model::account::Account;
282
283 #[test]
284 fn account_key_and_phrase_eq() {
285 let account1 = Account {
286 username: "test".to_string(),
287 api_url: "test.com".to_string(),
288 private_key: SecretKey::random(&mut OsRng),
289 };
290
291 let phrase = account1.get_phrase().unwrap();
292
293 let reverse = Account::phrase_to_private_key(phrase).unwrap();
294
295 assert!(account1.private_key == reverse);
296 }
297}