1use crate::chain::Ed25519KeyHash;
8use crate::crypto::key::*;
9use std::collections::BTreeMap;
10use thiserror::*;
11
12use bip39_dict::{Entropy, Mnemonics, ENGLISH};
13
14pub struct StandardWallet {
17 account_key: HDPublicKey,
18 spending: PublicKeys,
19 changing: PublicKeys,
20 staking: PublicKeys,
21}
22
23pub struct StandardWalletKeys {
24 top_key: HDKey,
25 root_key: HDKey,
26 account_key: HDKey,
27 spending: SecretKeys,
28 changing: SecretKeys,
29 staking: SecretKeys,
30}
31
32pub struct PublicKeys {
33 #[allow(unused)]
34 hdkey: HDPublicKey,
35 pub latest: u32,
36 #[allow(unused)]
37 generated: Vec<(u32, Ed25519PublicKey, Ed25519KeyHash)>,
38 hash_to_idx: BTreeMap<Ed25519KeyHash, u32>,
39}
40
41pub struct SecretKeys {
42 hdkey: HDKey,
43 generated: Vec<(u32, Ed25519ExtendedKey)>,
44}
45
46#[derive(Error, Debug)]
47pub enum WalletError {
48 #[error("mnemonics error {0}")]
49 MnemonicsError(#[from] bip39_dict::MnemonicError),
50 #[error("entropy error {0}")]
51 EntropyError(#[from] bip39_dict::EntropyError),
52 #[error("unknown words count {0}")]
53 UnknownWordCount(usize),
54}
55
56const INDEX_SPENDING: Bip32Index = Bip32Index::soft(0);
57const INDEX_CHANGING: Bip32Index = Bip32Index::soft(1);
58const INDEX_STAKING: Bip32Index = Bip32Index::soft(2);
59
60fn words_to_top_key(words: &str, password: &[u8]) -> Result<HDKey, WalletError> {
62 let nb_words = words.chars().filter(|c| *c == ' ').count() + 1;
63 match nb_words {
64 24 => {
65 let mnemonics = Mnemonics::<24>::from_string(&ENGLISH, words).unwrap();
66 let entropy = Entropy::<32>::from_mnemonics::<24, 8>(&mnemonics).unwrap();
67 Ok(HDKey::from_bip39_entropy(&entropy.as_ref(), password))
68 }
69 21 => {
70 let mnemonics = Mnemonics::<21>::from_string(&ENGLISH, words).unwrap();
71 let entropy = Entropy::<28>::from_mnemonics::<21, 7>(&mnemonics).unwrap();
72 Ok(HDKey::from_bip39_entropy(&entropy.as_ref(), password))
73 }
74 18 => {
75 let mnemonics = Mnemonics::<18>::from_string(&ENGLISH, words).unwrap();
76 let entropy = Entropy::<24>::from_mnemonics::<18, 6>(&mnemonics).unwrap();
77 Ok(HDKey::from_bip39_entropy(&entropy.as_ref(), password))
78 }
79 15 => {
80 let mnemonics = Mnemonics::<15>::from_string(&ENGLISH, words).unwrap();
81 let entropy = Entropy::<20>::from_mnemonics::<15, 5>(&mnemonics).unwrap();
82 Ok(HDKey::from_bip39_entropy(&entropy.as_ref(), password))
83 }
84 12 => {
85 let mnemonics = Mnemonics::<12>::from_string(&ENGLISH, words).unwrap();
86 let entropy = Entropy::<16>::from_mnemonics::<12, 4>(&mnemonics).unwrap();
87 Ok(HDKey::from_bip39_entropy(&entropy.as_ref(), password))
88 }
89 _ => Err(WalletError::UnknownWordCount(nb_words)),
90 }
91}
92
93#[allow(dead_code)]
96pub(crate) fn words_to_root_key(words: &str, password: &[u8]) -> Result<HDKey, WalletError> {
97 let top_key = words_to_top_key(words, password)?;
98 let root_key = top_key
99 .bip32_derive(Bip32Index::hard(1852))
100 .bip32_derive(Bip32Index::hard(1815));
101 Ok(root_key)
102}
103
104impl SecretKeys {
105 pub fn new(hdkey: HDKey, lookahead: u32) -> Self {
106 let generated = (0..lookahead)
107 .map(|i| (i, hdkey.bip32_derive(Bip32Index::soft(i)).strip_hd()))
108 .collect();
109
110 Self { hdkey, generated }
111 }
112
113 pub fn to_public(&self) -> PublicKeys {
114 let hdkey = self.hdkey.to_public();
115 let generated = self
116 .generated
117 .iter()
118 .map(|(i, k)| {
119 let k = k.to_public();
120 let h = k.hash();
121 (*i, k, h)
122 })
123 .collect::<Vec<_>>();
124
125 let hash_to_idx = generated.iter().map(|(i, _, h)| (h.clone(), *i)).collect();
126 let latest = generated.iter().map(|(i, _, _)| *i).max().unwrap();
127
128 PublicKeys {
129 hdkey,
130 latest,
131 generated,
132 hash_to_idx,
133 }
134 }
135}
136
137impl PublicKeys {
138 pub fn new(hdkey: HDPublicKey, lookahead: u32) -> Self {
139 let generated = (0..lookahead)
140 .map(|i| {
141 let key = hdkey.bip32_derive(Bip32Index::soft(i)).strip_hd();
142 let key_hash = key.hash();
143 (i, key, key_hash)
144 })
145 .collect::<Vec<_>>();
146 let hash_to_idx = generated.iter().map(|(i, _, h)| (h.clone(), *i)).collect();
147 let latest = generated.iter().map(|(i, _, _)| *i).max().unwrap();
148
149 Self {
150 hdkey,
151 latest,
152 generated,
153 hash_to_idx,
154 }
155 }
156}
157
158impl StandardWalletKeys {
159 pub fn from_words(words: &str, password: &[u8]) -> Result<Self, WalletError> {
162 let top_key = words_to_top_key(words, password)?;
163 let root_key = top_key
164 .bip32_derive(Bip32Index::hard(1852))
165 .bip32_derive(Bip32Index::hard(1815));
166 let account_key = root_key.bip32_derive(Bip32Index::hard(0));
167
168 let spending_gen_key = account_key.bip32_derive(INDEX_SPENDING);
169 let changing_gen_key = account_key.bip32_derive(INDEX_CHANGING);
170 let staking_gen_key = account_key.bip32_derive(INDEX_STAKING);
171
172 Ok(Self {
173 top_key,
174 root_key,
175 account_key,
176 spending: SecretKeys::new(spending_gen_key, 10),
177 changing: SecretKeys::new(changing_gen_key, 10),
178 staking: SecretKeys::new(staking_gen_key, 10),
179 })
180 }
181
182 pub fn to_public(&self) -> StandardWallet {
183 StandardWallet {
184 account_key: self.account_key.to_public(),
185 spending: self.spending.to_public(),
186 changing: self.changing.to_public(),
187 staking: self.staking.to_public(),
188 }
189 }
190
191 pub fn top_key(&self) -> &HDKey {
192 &self.top_key
193 }
194
195 pub fn root_key(&self) -> &HDKey {
196 &self.root_key
197 }
198
199 pub fn account_key(&self) -> &HDKey {
200 &self.account_key
201 }
202}
203
204impl StandardWallet {
205 pub fn from_public_account_key(
206 account_key: HDPublicKey,
207 index_spend: u32,
208 index_change: u32,
209 index_staking: u32,
210 ) -> Self {
211 let spending_gen_key = account_key.bip32_derive(INDEX_SPENDING);
212 let changing_gen_key = account_key.bip32_derive(INDEX_CHANGING);
213 let staking_gen_key = account_key.bip32_derive(INDEX_STAKING);
214
215 Self {
216 account_key,
217 spending: PublicKeys::new(spending_gen_key, index_spend),
218 changing: PublicKeys::new(changing_gen_key, index_change),
219 staking: PublicKeys::new(staking_gen_key, index_staking),
220 }
221 }
222
223 pub fn account_key(&self) -> &HDPublicKey {
224 &self.account_key
225 }
226
227 pub fn indexes(&self) -> (u32, u32, u32) {
228 (
229 self.spending.latest,
230 self.changing.latest,
231 self.staking.latest,
232 )
233 }
234
235 pub fn match_credential(&self, h: &Ed25519KeyHash) -> Option<(u32, bool)> {
236 match self.spending.hash_to_idx.get(&h) {
237 None => match self.changing.hash_to_idx.get(&h) {
238 None => None,
239 Some(idx) => Some((*idx, true)),
240 },
241 Some(idx) => Some((*idx, false)),
242 }
243 }
244
245 }
304
305#[cfg(test)]
306mod tests {
307 use super::super::address::*;
308 use crate::chaininfo::ChainInfo;
309 use crate::crypto::key::*;
310
311 fn root_key_15() -> HDKey {
312 let entropy = [
314 0x0c, 0xcb, 0x74, 0xf3, 0x6b, 0x7d, 0xa1, 0x64, 0x9a, 0x81, 0x44, 0x67, 0x55, 0x22,
315 0xd4, 0xd8, 0x09, 0x7c, 0x64, 0x12,
316 ];
317 HDKey::from_bip39_entropy(&entropy, &[])
318 }
319
320 #[test]
321 fn bip32_15_base() {
322 let top = root_key_15()
325 .bip32_derive(Bip32Index::hard(1852))
326 .bip32_derive(Bip32Index::hard(1815));
327
328 let spend_key = top
329 .bip32_derive(Bip32Index::hard(0))
330 .bip32_derive(Bip32Index::soft(0))
331 .bip32_derive(Bip32Index::soft(0));
332 let stake_key = top
333 .bip32_derive(Bip32Index::hard(0))
334 .bip32_derive(Bip32Index::soft(2))
335 .bip32_derive(Bip32Index::soft(0));
336
337 let spend_pk = spend_key.to_public();
338 let stake_pk = stake_key.to_public();
339
340 let spend_cred = Credential::Key(spend_pk.hash());
341 let stake_cred = Credential::Key(stake_pk.hash());
342
343 let addr_net_0 = Address::Base(Base {
344 network: ChainInfo::TESTNET.network_id,
345 payment: spend_cred.clone(),
346 stake: stake_cred.clone(),
347 });
348 assert_eq!(addr_net_0.serialize().to_bech32(ChainInfo::TESTNET.bech32_hrp_address), "addr_test1qpu5vlrf4xkxv2qpwngf6cjhtw542ayty80v8dyr49rf5ewvxwdrt70qlcpeeagscasafhffqsxy36t90ldv06wqrk2qum8x5w");
349
350 let addr_net_3 = Address::Base(Base {
351 network: ChainInfo::MAINNET.network_id,
352 payment: spend_cred,
353 stake: stake_cred,
354 });
355 assert_eq!(addr_net_3.serialize().to_bech32(ChainInfo::MAINNET.bech32_hrp_address), "addr1q9u5vlrf4xkxv2qpwngf6cjhtw542ayty80v8dyr49rf5ewvxwdrt70qlcpeeagscasafhffqsxy36t90ldv06wqrk2qld6xc3");
356 }
357}