layer_climb_signer/
key.rs

1use super::signer::TxSigner;
2use anyhow::{anyhow, Context, Result};
3use async_trait::async_trait;
4use bip32::DerivationPath;
5use bip39::Mnemonic;
6use signature::Signer;
7use std::{str::FromStr, sync::LazyLock};
8
9pub type PublicKey = tendermint::PublicKey;
10
11// https://github.com/confio/cosmos-hd-key-derivation-spec?tab=readme-ov-file#the-cosmos-hub-path
12pub static COSMOS_HUB_PATH: LazyLock<DerivationPath> =
13    LazyLock::new(|| DerivationPath::from_str("m/44'/118'/0'/0/0").unwrap());
14
15pub fn cosmos_hub_derivation(index: u32) -> Result<DerivationPath> {
16    DerivationPath::from_str(&format!("m/44'/118'/0'/0/{index}")).map_err(|err| anyhow!("{}", err))
17}
18
19pub struct KeySigner {
20    pub key: bip32::XPrv,
21}
22
23impl KeySigner {
24    pub fn new_mnemonic_iter<I, S>(mnemonic: I, derivation: Option<&DerivationPath>) -> Result<Self>
25    where
26        I: IntoIterator<Item = S>,
27        S: AsRef<str>,
28    {
29        let mut joined_str = String::new();
30        for word in mnemonic {
31            joined_str.push_str(word.as_ref());
32            joined_str.push(' ');
33        }
34
35        Self::new_mnemonic_str(&joined_str, derivation)
36    }
37
38    pub fn new_mnemonic_str(mnemonic: &str, derivation: Option<&DerivationPath>) -> Result<Self> {
39        let derivation = derivation.unwrap_or(&COSMOS_HUB_PATH);
40        let mnemonic: Mnemonic = mnemonic.parse()?;
41        let seed = mnemonic.to_seed("");
42        let key =
43            bip32::XPrv::derive_from_path(seed, derivation).map_err(|err| anyhow!("{}", err))?;
44
45        Ok(Self { key })
46    }
47}
48
49cfg_if::cfg_if! {
50    if #[cfg(target_arch = "wasm32")] {
51        #[async_trait(?Send)]
52        impl TxSigner for KeySigner {
53            async fn sign(&self, msg: &layer_climb_proto::tx::SignDoc) -> Result<Vec<u8>> {
54                sign(self, msg).await
55            }
56
57            async fn public_key(&self) -> Result<PublicKey> {
58                public_key(self).await
59            }
60        }
61
62    } else {
63        #[async_trait]
64        impl TxSigner for KeySigner {
65            async fn sign(&self, msg: &layer_climb_proto::tx::SignDoc) -> Result<Vec<u8>> {
66                sign(self, msg).await
67            }
68
69            async fn public_key(&self) -> Result<PublicKey> {
70                public_key(self).await
71            }
72        }
73    }
74}
75
76async fn sign(signer: &KeySigner, msg: &layer_climb_proto::tx::SignDoc) -> Result<Vec<u8>> {
77    let signed: k256::ecdsa::Signature = signer
78        .key
79        .private_key()
80        .try_sign(&layer_climb_proto::proto_into_bytes(msg)?)
81        .map_err(|err| anyhow!("{}", err))?;
82    Ok(signed.to_vec())
83}
84
85async fn public_key(signer: &KeySigner) -> Result<PublicKey> {
86    let public_key = signer.key.public_key();
87    let public_key_bytes = public_key.to_bytes();
88    PublicKey::from_raw_secp256k1(&public_key_bytes).context("Invalid secp256k1 public key")
89}