cosm_utils/signing_key/
key.rs

1use cosmrs::bip32;
2use cosmrs::bip32::secp256k1::elliptic_curve::rand_core::OsRng;
3use cosmrs::crypto::{secp256k1, PublicKey};
4use cosmrs::tendermint::block::Height;
5use cosmrs::tx::{Body, SignDoc, SignerInfo};
6
7#[cfg(feature = "keyring")]
8use keyring::Entry;
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11
12use crate::chain::error::ChainError;
13use crate::chain::fee::Fee;
14use crate::chain::msg::Msg;
15use crate::chain::tx::RawTx;
16use crate::modules::auth::model::{Account, Address};
17
18#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
19pub struct SigningKey {
20    /// human readable key name
21    pub name: String,
22    /// private key associated with `name`
23    pub key: Key,
24}
25
26impl SigningKey {
27    pub async fn public_key(&self, derivation_path: &str) -> Result<PublicKey, ChainError> {
28        match &self.key {
29            Key::Raw(bytes) => {
30                let key = raw_bytes_to_signing_key(bytes)?;
31                Ok(key.public_key())
32            }
33
34            Key::Mnemonic(phrase) => {
35                let key = mnemonic_to_signing_key(phrase, derivation_path)?;
36                Ok(key.public_key())
37            }
38
39            #[cfg(feature = "keyring")]
40            Key::Keyring(params) => {
41                let entry = Entry::new(&params.service, &params.key_name)?;
42                let key = mnemonic_to_signing_key(&entry.get_password()?, derivation_path)?;
43                Ok(key.public_key())
44            }
45        }
46    }
47
48    #[allow(clippy::too_many_arguments)]
49    pub async fn sign(
50        &self,
51        msgs: Vec<impl Msg + Serialize>,
52        timeout_height: u64,
53        memo: &str,
54        account: Account,
55        fee: Fee,
56        chain_id: &str,
57        derivation_path: &str,
58    ) -> Result<RawTx, ChainError> {
59        let public_key = if account.pubkey.is_none() {
60            Some(self.public_key(derivation_path).await?)
61        } else {
62            account.pubkey
63        };
64
65        match &self.key {
66            Key::Raw(bytes) => {
67                let sign_doc = build_sign_doc(
68                    msgs,
69                    timeout_height,
70                    memo,
71                    &account,
72                    fee,
73                    public_key,
74                    chain_id,
75                )?;
76
77                let key = raw_bytes_to_signing_key(bytes)?;
78
79                let raw = sign_doc.sign(&key).map_err(ChainError::crypto)?;
80                Ok(raw.into())
81            }
82
83            Key::Mnemonic(phrase) => {
84                let sign_doc = build_sign_doc(
85                    msgs,
86                    timeout_height,
87                    memo,
88                    &account,
89                    fee,
90                    public_key,
91                    chain_id,
92                )?;
93
94                let key = mnemonic_to_signing_key(phrase, derivation_path)?;
95
96                let raw = sign_doc.sign(&key).map_err(ChainError::crypto)?;
97                Ok(raw.into())
98            }
99
100            #[cfg(feature = "keyring")]
101            Key::Keyring(params) => {
102                let sign_doc = build_sign_doc(
103                    msgs,
104                    timeout_height,
105                    memo,
106                    &account,
107                    fee,
108                    public_key,
109                    chain_id,
110                )?;
111
112                let entry = Entry::new(&params.service, &params.key_name)?;
113                let key = mnemonic_to_signing_key(&entry.get_password()?, derivation_path)?;
114
115                let raw = sign_doc.sign(&key).map_err(ChainError::crypto)?;
116                Ok(raw.into())
117            }
118        }
119    }
120
121    pub async fn to_addr(
122        &self,
123        prefix: &str,
124        derivation_path: &str,
125    ) -> Result<Address, ChainError> {
126        let account = self
127            .public_key(derivation_path)
128            .await?
129            .account_id(prefix)
130            .map_err(ChainError::crypto)?;
131        Ok(account.into())
132    }
133
134    pub fn random_mnemonic(key_name: String) -> SigningKey {
135        let mnemonic = bip32::Mnemonic::random(OsRng, Default::default());
136
137        SigningKey {
138            name: key_name,
139            key: Key::Mnemonic(mnemonic.phrase().to_string()),
140        }
141    }
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
145#[non_exhaustive]
146pub enum Key {
147    /// Create a key from a set of bytes.
148    Raw(Vec<u8>),
149
150    /// Mnemonic allows you to pass the private key mnemonic words
151    /// to Cosm-orc for configuring a transaction signing key.
152    /// DO NOT USE FOR MAINNET
153    Mnemonic(String),
154
155    // TODO: Add Keyring password CRUD operations
156    /// Use OS Keyring to access private key.
157    /// Safe for testnet / mainnet.
158    #[cfg(feature = "keyring")]
159    Keyring(KeyringParams),
160    // TODO: Add ledger support(under a new ledger feature flag / Key variant)
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
164pub struct KeyringParams {
165    pub service: String,
166    pub key_name: String,
167}
168
169fn mnemonic_to_signing_key(
170    mnemonic: &str,
171    derivation_path: &str,
172) -> Result<secp256k1::SigningKey, ChainError> {
173    let seed = bip32::Mnemonic::new(mnemonic, bip32::Language::English)
174        .map_err(|_| ChainError::Mnemonic)?
175        .to_seed("");
176
177    secp256k1::SigningKey::derive_from_path(
178        seed,
179        &derivation_path
180            .parse()
181            .map_err(|_| ChainError::DerviationPath)?,
182    )
183    .map_err(|_| ChainError::DerviationPath)
184}
185
186fn raw_bytes_to_signing_key(bytes: &[u8]) -> Result<secp256k1::SigningKey, ChainError> {
187    secp256k1::SigningKey::from_slice(bytes).map_err(ChainError::crypto)
188}
189
190fn build_sign_doc(
191    msgs: Vec<impl Msg>,
192    timeout_height: u64,
193    memo: &str,
194    account: &Account,
195    fee: Fee,
196    public_key: Option<PublicKey>,
197    chain_id: &str,
198) -> Result<SignDoc, ChainError> {
199    let timeout: Height = timeout_height.try_into()?;
200
201    let tx = Body::new(
202        msgs.into_iter()
203            .map(|m| m.into_any())
204            .collect::<Result<Vec<_>, _>>()
205            .map_err(|e| ChainError::ProtoEncoding {
206                message: e.to_string(),
207            })?,
208        memo,
209        timeout,
210    );
211
212    // NOTE: if we are making requests in parallel with the same key, we need to serialize `account.sequence` to avoid errors
213    let auth_info =
214        SignerInfo::single_direct(public_key, account.sequence).auth_info(fee.try_into()?);
215
216    SignDoc::new(
217        &tx,
218        &auth_info,
219        &chain_id.parse().map_err(|_| ChainError::ChainId {
220            chain_id: chain_id.to_string(),
221        })?,
222        account.account_number,
223    )
224    .map_err(ChainError::proto_encoding)
225}