terra_rust_wallet/
lib.rs

1// `error_chain!` can recurse deeply
2// #![recursion_limit = "1024"]
3#![allow(missing_docs)]
4/*!
5* This crate provides an interface into the Terra wallet service.
6* # PFC
7*
8* This work is sponsored by the PFC (Productive Framework Code) Validator,
9* feel free to delegate to the [PFC](https://station.terra.money/validator/terravaloper12g4nkvsjjnl0t7fvq3hdcw7y8dc9fq69nyeu9q) validator.
10*
11* It will help defray the costs.
12*
13* # Warning
14* This uses cryptographic routines that have not gone through any security audit.
15*
16* The manner which it stores private keys may be unsecure/subject to hacks, and if you use it, may put the assets behind those keys at risk.
17*
18* This is ALPHA software.
19*
20* # Usage
21* TBD
22*/
23
24/// Error Messages
25pub mod errors;
26
27use crate::errors::KeyringErrorAdapter;
28//#[macro_use]
29//extern crate error_chain;
30use crate::errors::TerraRustWalletError;
31use secp256k1::Secp256k1;
32use serde::{Deserialize, Serialize};
33use terra_rust_api::{PrivateKey, PublicKey};
34
35#[derive(Deserialize, Serialize, Debug)]
36/// Internal structure used to hold list of keys in keyring
37pub struct WalletInternal {
38    pub keys: Vec<String>,
39}
40#[derive(Deserialize, Serialize, Debug)]
41/// Internal structure used to hold list of keys in keyring
42pub struct WalletListInternal {
43    pub wallets: Vec<String>,
44}
45
46///
47/// Wallet operations based on Keyring API
48///
49/// stores key names in another 'username/password' to facilitate listing keys, and deletion of ALL keys in a wallet
50#[derive(Clone)]
51pub struct Wallet<'a> {
52    pub name: &'a str,
53}
54impl<'a> Wallet<'a> {
55    /// create a new wallet to store keys into. This just creates the structure
56    /// use #new to create a new wallet
57    pub fn new(wallet_name: &'a str) -> Result<Wallet<'a>, TerraRustWalletError> {
58        log::debug!("Creating new wallet {}", wallet_name);
59        let wallet = Wallet::create(wallet_name);
60        let wallet_list_name = &wallet.full_list_name();
61        let keyring = keyring::Entry::new(wallet_name, wallet_list_name);
62        let wallet_internal = WalletInternal { keys: vec![] };
63        keyring
64            .set_password(&serde_json::to_string(&wallet_internal)?)
65            .map_err(KeyringErrorAdapter::from)?;
66        let string_key_name: String = String::from(wallet_name);
67
68        match Wallet::get_wallets() {
69            Ok(old_list) => {
70                let mut new_list: Vec<String> = vec![];
71                for s in old_list {
72                    if s.ne(wallet_name) {
73                        new_list.push(s);
74                    }
75                }
76                new_list.push(string_key_name);
77                let wallet_list = WalletListInternal { wallets: new_list };
78                Wallet::set_wallets(&wallet_list)?;
79            }
80            Err(_) => {
81                // Keyring just returns a 'generic' error, probably need to dig in and check if it is 'NOTFOUND' vs other
82                let wallet_list = WalletListInternal {
83                    wallets: vec![string_key_name],
84                };
85                Wallet::set_wallets(&wallet_list)?;
86            }
87        }
88
89        Ok(wallet)
90    }
91    /// setups the wallet structure
92    pub fn create(wallet: &'a str) -> Wallet<'a> {
93        Wallet { name: wallet }
94    }
95    /// retrieves the private key from the keyring
96    pub fn get_private_key<C: secp256k1::Signing + secp256k1::Context>(
97        &self,
98        secp: &'a Secp256k1<C>,
99        key_name: &'a str,
100        seed: Option<&'a str>,
101    ) -> Result<PrivateKey, TerraRustWalletError> {
102        let full_key_name = self.full_key_name(key_name);
103        let keyring = keyring::Entry::new(self.name, &full_key_name);
104        let phrase = &keyring.get_password()?; //.map_err(KeyringErrorAdapter::from)?;
105                                               // log::info!("{}", phrase);
106        match seed {
107            None => Ok(PrivateKey::from_words(secp, phrase, 0, 0)?),
108            Some(seed_str) => Ok(PrivateKey::from_words_seed(secp, phrase, seed_str)?),
109        }
110    }
111    /// retrieves the public key associated with the stored private key
112    pub fn get_public_key<C: secp256k1::Signing + secp256k1::Context>(
113        &self,
114        secp: &Secp256k1<C>,
115        key_name: &str,
116        seed: Option<&str>,
117    ) -> Result<PublicKey, TerraRustWalletError> {
118        let private_key: PrivateKey = self.get_private_key(secp, key_name, seed)?;
119
120        let pub_key = private_key.public_key(secp);
121        Ok(pub_key)
122    }
123
124    /// get account from key name
125    pub fn get_account<C: secp256k1::Signing + secp256k1::Context>(
126        &self,
127        secp: &Secp256k1<C>,
128        key_name: &str,
129        seed: Option<&str>,
130    ) -> Result<String, TerraRustWalletError> {
131        let pub_key = self.get_public_key(secp, key_name, seed)?;
132        let account = pub_key.account()?;
133        Ok(account)
134    }
135    /// stores the private key into the keyring
136    pub fn store_key(&self, key_name: &str, pk: &PrivateKey) -> Result<bool, TerraRustWalletError> {
137        let full_key_name = self.full_key_name(key_name);
138
139        let keyring = keyring::Entry::new(self.name, &full_key_name);
140        keyring.set_password(pk.words().unwrap())?; // .map_err(KeyringErrorAdapter::from)?;
141        let old_list = self.get_keys()?;
142        let string_key_name: String = String::from(key_name);
143        let mut new_list: Vec<String> = vec![];
144        for s in old_list {
145            if s.ne(key_name) {
146                new_list.push(s);
147            }
148        }
149
150        new_list.push(string_key_name);
151        let wallet_internal = WalletInternal { keys: new_list };
152        self.set_keys(&wallet_internal)?;
153
154        Ok(true)
155    }
156    /// deletes the private key from the keyring
157    pub fn delete_key(&self, key_name: &str) -> Result<bool, TerraRustWalletError> {
158        let full_key_name = self.full_key_name(key_name);
159        let keyring = keyring::Entry::new(self.name, &full_key_name);
160        keyring.delete_password()?; //  .map_err(KeyringErrorAdapter::from)?;
161        let old_list = self.get_keys()?;
162        let mut new_list = vec![];
163        for s in old_list {
164            if s.ne(key_name) {
165                new_list.push(s);
166            }
167        }
168        let wallet_internal = WalletInternal { keys: new_list };
169        self.set_keys(&wallet_internal)?;
170        Ok(true)
171    }
172    /// lists the keys in the wallet
173    pub fn list(&self) -> Result<Vec<String>, TerraRustWalletError> {
174        self.get_keys()
175    }
176
177    /// deletes the wallet and ALL the keys in the wallet
178    pub fn delete(&self) -> Result<(), TerraRustWalletError> {
179        let keys = self.get_keys()?;
180        for key in keys {
181            log::debug!("Deleting Key {} in wallet {}", key, &self.name);
182            self.delete_key(&key)?;
183        }
184        let wallet_list_name = self.full_list_name();
185        let keyring = keyring::Entry::new(self.name, &wallet_list_name);
186        keyring.delete_password()?; //   .map_err(KeyringErrorAdapter::from)?;
187        let old_list = Wallet::get_wallets()?;
188        // let string_key_name: String = String::from(self.name);
189        let mut new_list: Vec<String> = vec![];
190        for s in old_list {
191            if s.ne(self.name) {
192                new_list.push(s);
193            }
194        }
195        let wallet_list = WalletListInternal { wallets: new_list };
196        Wallet::set_wallets(&wallet_list)?;
197        Ok(())
198    }
199    /// key name format
200    fn full_key_name(&self, key_name: &'a str) -> String {
201        format!("TERRA-RUST-{}-{}", self.name, key_name)
202    }
203    /// used to store list of keys for a wallet
204    fn full_list_name(&self) -> String {
205        format!("TERRA-RUST-{}_KEYS", self.name)
206    }
207    /// used to store list of wallets
208    fn wallet_list_name() -> String {
209        "TERRA-RUST_WALLETS".to_string()
210    }
211
212    /// get list of keys in a wallet
213    fn get_keys(&self) -> Result<Vec<String>, TerraRustWalletError> {
214        let wallet_list_name = self.full_list_name();
215        let keyring = keyring::Entry::new(self.name, &wallet_list_name);
216        let pass = keyring.get_password()?;
217        /*    .map_err(|source| TerraRustWalletError::KeyNotFound {
218            key: wallet_list_name,
219            source: KeyringErrorAdapter::from(source),
220        })?;*/
221
222        let wallet_internal: WalletInternal = serde_json::from_str(&pass)?;
223        Ok(wallet_internal.keys)
224    }
225
226    /// get list of wallets
227    pub fn get_wallets() -> Result<Vec<String>, TerraRustWalletError> {
228        let wallet_list_name = Wallet::wallet_list_name();
229        let keyring = keyring::Entry::new(&wallet_list_name, "wallets");
230
231        let wallet_internal: WalletListInternal = serde_json::from_str(&keyring.get_password()?)?; //.map_err(KeyringErrorAdapter::from)?)?;
232        Ok(wallet_internal.wallets)
233    }
234
235    /// update keys in a wallet
236    fn set_keys(&self, int: &WalletInternal) -> Result<(), TerraRustWalletError> {
237        let wallet_list_name = self.full_list_name();
238        let keyring = keyring::Entry::new(self.name, &wallet_list_name);
239
240        keyring.set_password(&serde_json::to_string(int)?)?; //   .map_err(KeyringErrorAdapter::from)?;
241        Ok(())
242    }
243    /// update list of wallets
244    fn set_wallets(int: &WalletListInternal) -> Result<(), TerraRustWalletError> {
245        let wallet_list_name = Wallet::wallet_list_name();
246        let keyring = keyring::Entry::new(&wallet_list_name, "wallets");
247
248        keyring.set_password(&serde_json::to_string(int)?)?; //  .map_err(KeyringErrorAdapter::from)?;
249        Ok(())
250    }
251}
252
253#[cfg(test)]
254mod tst {
255    use super::*;
256
257    #[test]
258    pub fn test_wallet_create_delete() -> anyhow::Result<()> {
259        let wallet = Wallet::new("PFC-Test Wallet")?;
260        let key_list = wallet.get_keys()?;
261        assert!(key_list.is_empty());
262        wallet.delete()?;
263        Ok(())
264    }
265    #[test]
266    pub fn test_wallet_add_del() -> anyhow::Result<()> {
267        let str_1 = "notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius";
268        let str_2 = "wonder caution square unveil april art add hover spend smile proud admit modify old copper throw crew happy nature luggage reopen exhibit ordinary napkin";
269
270        let s = Secp256k1::new();
271        let pk = PrivateKey::from_words(&s, str_1, 0, 0)?;
272        let pk2 = PrivateKey::from_words(&s, str_2, 0, 0)?;
273
274        let wallet = Wallet::new("PFC-Test Wallet")?;
275        wallet.store_key("PFC-Test-Key", &pk)?;
276        let key_list = wallet.get_keys()?;
277        assert_eq!(key_list.len(), 1);
278
279        wallet.store_key("PFC-Test-Key-2", &pk2)?;
280        let mut key_list = wallet.get_keys()?;
281        assert_eq!(key_list.len(), 2);
282
283        key_list.sort();
284        assert_eq!(key_list.join(","), "PFC-Test-Key,PFC-Test-Key-2");
285
286        let pk_get = wallet.get_private_key(&s, "PFC-Test-Key", None)?;
287        assert_eq!(pk_get.words().unwrap(), str_1);
288
289        wallet.delete_key("PFC-Test-Key")?;
290        let key_list = wallet.get_keys()?;
291        assert_eq!(key_list.len(), 1);
292        assert_eq!(key_list.join(","), "PFC-Test-Key-2");
293
294        wallet.delete()?;
295        Ok(())
296    }
297}