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