Skip to main content

lb_rs/model/
account.rs

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
12/// A flag for which users have volunteers as beta testers.
13///
14/// Beta users are users to which riskier code is enabled first for testing.
15/// Beta users are also users who have opted into telemetry by way of approving a PR that adds
16/// their name to this list. Certainly telemetry in lockbook will always be opt in but the
17/// mechanism of consent may evolve over time.
18pub 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}