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