Skip to main content

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