eosio_client_api/
wallet_types.rs

1/**
2* This contains the interfaces used in chatting to KleosD.
3* This is temporary used to sign things (transactions, digests) until eos-keys gains the ability to
4* perform cannonical signatures
5*/
6//use chrono::{DateTime, Utc, Duration};
7use serde::{Deserialize, Serialize};
8//use crate::api_types::eosio_datetime_format;
9use crate::errors::{ErrorKind, Result};
10use crate::json_rpc::EOSRPC;
11use eosio_client_keys::{EOSPrivateKey, EOSPublicKey};
12use serde_json::Value;
13use libabieos_sys::vec_u8_to_hex;
14use crate::api_types::{ TransactionIn, TransactionInSigned};
15
16const WALLET_UNLOCKED_EXCEPTION: usize = 3_120_007;
17#[allow(dead_code)]
18pub const EOSIO_CHAIN_ID: &str = "cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f";
19
20#[derive(Debug, Serialize, Deserialize)]
21pub struct WalletList {
22    name: String,
23}
24
25pub struct Wallet {
26    keos: EOSRPC,
27    chain_id: Option<String>,
28}
29
30impl Wallet {
31    pub fn create(keos: EOSRPC) -> Wallet {
32        Wallet {
33            keos,
34            chain_id: None,
35        }
36    }
37    pub fn create_with_chain_id(keos: EOSRPC, chain_id: &str) -> Wallet {
38        Wallet {
39            keos,
40            chain_id: Some(String::from(chain_id)),
41        }
42    }
43    pub async fn list(&self) -> Result<Vec<String>> {
44        let value = serde_json::json!({});
45        let res = self
46            .keos
47            .non_blocking_req("/v1/wallet/list_wallets", value)
48            .await?;
49        let list: Vec<String> = serde_json::from_str(&res).unwrap();
50
51        Ok(list)
52    }
53    pub async fn keys(&self) -> Result<Vec<EOSPublicKey>> {
54        let value = serde_json::json!({});
55        let res = self
56            .keos
57            .non_blocking_req("/v1/wallet/get_public_keys", value)
58            .await?;
59        let list: Vec<String> = serde_json::from_str(&res).unwrap();
60        let keys = EOSPublicKey::from_eos_strings(&list)?;
61
62        Ok(keys)
63    }
64    pub async fn private_keys(
65        &self,
66        wallet: &str,
67        pass: &str,
68    ) -> Result<Vec<(EOSPublicKey, EOSPrivateKey)>> {
69        let value = serde_json::json!([wallet, pass]);
70        let res = self
71            .keos
72            .non_blocking_req("/v1/wallet/list_keys", value)
73            .await?;
74        let list: Vec<(String, String)> = serde_json::from_str(&res).unwrap();
75        let mut r: Vec<(EOSPublicKey, EOSPrivateKey)> = vec![];
76        for pair in list {
77            let public: EOSPublicKey = EOSPublicKey::from_eos_string(&pair.0)?;
78            let private: EOSPrivateKey = EOSPrivateKey::from_string(&pair.1)?;
79            r.push((public, private));
80        }
81
82        Ok(r)
83    }
84
85    pub async fn unlock(&self, wallet: &str, pass: &str) -> Result<bool> {
86        let value = serde_json::json!([wallet, pass]);
87        match self.keos.non_blocking_req("/v1/wallet/unlock", value).await {
88            Ok(res) => {
89                let resp: Value = serde_json::from_str(&res).unwrap();
90                if resp.is_object() {
91                    Ok(true)
92                } else {
93                    Err("Fail-Wallet Unlock unknown response".into())
94                }
95            }
96            Err(e) => match e.0 {
97                ErrorKind::InvalidResponseStatus(k) => {
98                    if k.code == WALLET_UNLOCKED_EXCEPTION {
99                        Ok(true)
100                    } else {
101                        eprintln!("{:#?}", k);
102                        Err("Fail-Wallet Unlock".into())
103                    }
104                },
105                ErrorKind::InvalidResponseErr(k) => {
106                    eprintln!("{:#?}", k);
107                    panic!("Wallet unlock Fail-Err");
108                }
109                _ => {
110                    eprintln!("{:#?}", e);
111                    panic!("Wallet unlock Fail");
112                }
113            },
114        }
115    }
116    pub async fn sign_transaction(
117        &self,
118        transaction: TransactionIn,
119        pubkey: Vec<EOSPublicKey>,
120    ) -> Result<TransactionInSigned> {
121        let mut pubkey_str: Vec<String> = vec![];
122        for k in pubkey {
123            pubkey_str.push(k.to_eos_string()?)
124        }
125        if self.chain_id.is_none() {
126            Err(ErrorKind::WalletMissingChainID.into())
127        } else {
128            let value =
129                serde_json::json![[transaction, pubkey_str, self.chain_id.as_ref().unwrap()]];
130            let res = self
131                .keos
132                .non_blocking_req("/v1/wallet/sign_transaction", value)
133                .await?;
134            let t: TransactionInSigned = serde_json::from_str(&res).unwrap();
135            Ok(t)
136        }
137    }
138
139    pub async fn sign_digest(&self, digest: &[u8], pubkey: &EOSPublicKey) -> Result<String> {
140        let digest_b = vec_u8_to_hex(digest)?;
141        let value = serde_json::json![[digest_b, pubkey.to_eos_string()?]];
142        let res = self
143            .keos
144            .non_blocking_req("/v1/wallet/sign_digest", value)
145            .await?;
146        let sig: String = serde_json::from_str(&res).unwrap();
147        Ok(sig)
148    }
149}
150#[allow(dead_code)]
151pub fn get_wallet_pass() -> Result<String> {
152    use std::fs;
153    let pass = String::from(fs::read_to_string(".env")?.trim());
154    Ok(pass)
155}
156
157#[cfg(test)]
158mod test {
159    use super::*;
160    use eosio_client_keys::hash::hash_sha256;
161    use eosio_client_keys::EOSSignature;
162
163    const KEOSD_HOST: &str = "http://127.0.0.1:3888";
164
165    #[tokio::test]
166    async fn wallet_list_test() -> Result<()> {
167        let keos = EOSRPC::non_blocking(String::from(KEOSD_HOST)).await?;
168        let _wallets = Wallet::create(keos).list().await?;
169        Ok(())
170    }
171
172    #[tokio::test]
173    async fn wallet_list_unlock() -> Result<()> {
174        let keos = EOSRPC::non_blocking(String::from(KEOSD_HOST)).await?;
175        let pass = get_wallet_pass()?;
176        let _wallets = Wallet::create(keos).unlock("default", &pass).await?;
177        Ok(())
178    }
179
180    #[tokio::test]
181    async fn wallet_list_keys() -> Result<()> {
182        let keos = EOSRPC::non_blocking(String::from(KEOSD_HOST)).await?;
183        let pass = get_wallet_pass()?;
184        let wallet = Wallet::create(keos);
185        let _wallets = wallet.unlock("default", &pass).await?;
186        let _keys = wallet.keys().await?;
187
188        Ok(())
189    }
190
191    #[tokio::test]
192    async fn wallet_list_private_keys() -> Result<()> {
193        let keos = EOSRPC::non_blocking(String::from(KEOSD_HOST)).await?;
194        let pass = get_wallet_pass()?;
195        let wallet = Wallet::create(keos);
196        let _res = wallet.unlock("default", &pass).await?;
197        let keys = wallet.private_keys("default", &pass).await?;
198        for k in keys {
199            assert_eq!(k.0.to_eos_string()?, k.1.to_public().to_eos_string()?);
200        }
201        Ok(())
202    }
203
204    #[tokio::test]
205    async fn wallet_sign_txn() -> Result<()> {
206        let keos = EOSRPC::non_blocking(String::from(KEOSD_HOST)).await?;
207        let pass = get_wallet_pass()?;
208        let wallet = Wallet::create_with_chain_id(keos, EOSIO_CHAIN_ID);
209        let _res = wallet.unlock("default", &pass).await?;
210        let t = TransactionIn::dummy();
211        let pubkey =
212            EOSPublicKey::from_eos_string("EOS7ctUUZhtCGHnxUnh4Rg5eethj3qNS5S9fijyLMKgRsBLh8eMBB")?;
213        let ti: TransactionInSigned = wallet.sign_transaction(t, vec![pubkey]).await?;
214        let sigs = ti.signatures;
215        assert_eq!(sigs.len(), 1);
216        for sig in sigs {
217            let eos_sig: EOSSignature = EOSSignature::from_string(&sig)?;
218            if !eos_sig.is_canonical() {
219                eprintln!("{:#?}", eos_sig.to_eos_string());
220            }
221            assert!(eos_sig.is_canonical());
222        }
223        Ok(())
224    }
225
226    #[tokio::test]
227    async fn wallet_sign_digest() -> Result<()> {
228        let keos = EOSRPC::non_blocking(String::from(KEOSD_HOST)).await?;
229        let pass = get_wallet_pass()?;
230        let wallet = Wallet::create(keos);
231        let _res = wallet.unlock("default", &pass).await?;
232        let pubkey =
233            EOSPublicKey::from_eos_string("EOS7ctUUZhtCGHnxUnh4Rg5eethj3qNS5S9fijyLMKgRsBLh8eMBB")?;
234        let phrase: Vec<u8> = "Greg! The Stop sign".as_bytes().to_vec();
235        let hash = hash_sha256(&phrase);
236        let sig = wallet.sign_digest(&hash, &pubkey).await?;
237        let eos_sig: EOSSignature = EOSSignature::from_string(&sig)?;
238        eos_sig.verify_hash(&hash, &pubkey)?;
239        assert!(eos_sig.is_canonical());
240
241        Ok(())
242    }
243}