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, "{byte:08b}");
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, "{byte:08b}");
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!("{byte:08b}"));
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, Deserializer};
161 use serde::ser::Serializer;
162
163 pub fn serialize<S>(sk: &SecretKey, serializer: S) -> Result<S::Ok, S::Error>
164 where
165 S: Serializer,
166 {
167 serializer.serialize_bytes(&sk.serialize())
168 }
169
170 pub fn deserialize<'de, D>(deserializer: D) -> Result<SecretKey, D::Error>
171 where
172 D: Deserializer<'de>,
173 {
174 let key = <Vec<u8>>::deserialize(deserializer)?;
175 let sk = SecretKey::parse_slice(&key).map_err(serde::de::Error::custom)?;
176 Ok(sk)
177 }
178}
179
180#[cfg(test)]
181mod test_account_serialization {
182 use libsecp256k1::SecretKey;
183 use rand::rngs::OsRng;
184
185 use crate::model::account::Account;
186
187 #[test]
188 fn account_serialize_deserialize() {
189 let account1 = Account {
190 username: "test".to_string(),
191 api_url: "test.com".to_string(),
192 private_key: SecretKey::random(&mut OsRng),
193 };
194
195 let encoded: Vec<u8> = bincode::serialize(&account1).unwrap();
196 let account2: Account = bincode::deserialize(&encoded).unwrap();
197
198 assert_eq!(account1, account2);
199 }
200
201 #[test]
202 fn verify_account_v1() {
203 let account1 = Account {
204 username: "test1".to_string(),
205 api_url: "test1.com".to_string(),
206 private_key: SecretKey::parse_slice(&[
207 19, 34, 85, 4, 36, 83, 52, 122, 49, 107, 223, 44, 31, 16, 2, 160, 100, 103, 193, 0,
208 67, 15, 184, 133, 33, 111, 91, 143, 137, 232, 240, 42,
209 ])
210 .unwrap(),
211 };
212
213 let account2 = bincode::deserialize(
214 base64::decode("BQAAAAAAAAB0ZXN0MQkAAAAAAAAAdGVzdDEuY29tIAAAAAAAAAATIlUEJFM0ejFr3ywfEAKgZGfBAEMPuIUhb1uPiejwKg==")
215 .unwrap()
216 .as_slice()
217 ).unwrap();
218
219 assert_eq!(account1, account2);
220 }
221
222 #[test]
223 fn verify_account_v2() {
224 let account1 = Account {
225 username: "test1".to_string(),
226 api_url: "test1.com".to_string(),
227 private_key: SecretKey::parse_slice(&[
228 158, 250, 59, 72, 139, 112, 93, 137, 168, 199, 28, 230, 56, 37, 43, 52, 152, 176,
229 243, 149, 124, 11, 2, 126, 73, 118, 252, 112, 225, 207, 34, 90,
230 ])
231 .unwrap(),
232 };
233
234 let account2 = Account {
235 username: "test1".to_string(),
236 api_url: "test1.com".to_string(),
237 private_key: SecretKey::parse_slice(
238 base64::decode("nvo7SItwXYmoxxzmOCUrNJiw85V8CwJ+SXb8cOHPIlo=")
239 .unwrap()
240 .as_slice(),
241 )
242 .unwrap(),
243 };
244
245 assert_eq!(account1, account2);
246 }
247
248 #[test]
249 fn verify_account_phrase() {
250 let account1 = Account {
251 username: "test1".to_string(),
252 api_url: "test1.com".to_string(),
253 private_key: SecretKey::parse_slice(&[
254 234, 169, 139, 200, 30, 42, 176, 229, 16, 101, 229, 85, 125, 47, 182, 24, 154, 8,
255 156, 233, 24, 102, 126, 171, 86, 240, 0, 175, 6, 192, 253, 231,
256 ])
257 .unwrap(),
258 };
259
260 let account2 = Account {
261 username: "test1".to_string(),
262 api_url: "test1.com".to_string(),
263 private_key: Account::phrase_to_private_key([
264 "turkey", "era", "velvet", "detail", "prison", "income", "dose", "royal", "fever",
265 "truly", "unique", "couple", "party", "example", "piece", "art", "leaf", "follow",
266 "rose", "access", "vacant", "gather", "wasp", "audit",
267 ])
268 .unwrap(),
269 };
270
271 assert_eq!(account1, account2)
272 }
273}
274
275#[cfg(test)]
276mod test_account_key_and_phrase {
277 use libsecp256k1::SecretKey;
278 use rand::rngs::OsRng;
279
280 use crate::model::account::Account;
281
282 #[test]
283 fn account_key_and_phrase_eq() {
284 let account1 = Account {
285 username: "test".to_string(),
286 api_url: "test.com".to_string(),
287 private_key: SecretKey::random(&mut OsRng),
288 };
289
290 let phrase = account1.get_phrase().unwrap();
291
292 let reverse = Account::phrase_to_private_key(phrase).unwrap();
293
294 assert!(account1.private_key == reverse);
295 }
296}