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