layer_climb_address/
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))
17        .map_err(|err| anyhow!("{}", err))
18}
19
20pub struct KeySigner {
21    pub key: bip32::XPrv,
22}
23
24impl KeySigner {
25    pub fn new_mnemonic_iter<I, S>(mnemonic: I, derivation: Option<&DerivationPath>) -> Result<Self>
26    where
27        I: IntoIterator<Item = S>,
28        S: AsRef<str>,
29    {
30        let mut joined_str = String::new();
31        for word in mnemonic {
32            joined_str.push_str(word.as_ref());
33            joined_str.push(' ');
34        }
35
36        Self::new_mnemonic_str(&joined_str, derivation)
37    }
38
39    pub fn new_mnemonic_str(mnemonic: &str, derivation: Option<&DerivationPath>) -> Result<Self> {
40        let derivation = derivation.unwrap_or(&COSMOS_HUB_PATH);
41        let mnemonic: Mnemonic = mnemonic.parse()?;
42        let seed = mnemonic.to_seed("");
43        let key =
44            bip32::XPrv::derive_from_path(seed, derivation).map_err(|err| anyhow!("{}", err))?;
45
46        Ok(Self { key })
47    }
48}
49
50cfg_if::cfg_if! {
51    if #[cfg(target_arch = "wasm32")] {
52        #[async_trait(?Send)]
53        impl TxSigner for KeySigner {
54            async fn sign(&self, msg: &layer_climb_proto::tx::SignDoc) -> Result<Vec<u8>> {
55                sign(self, msg).await
56            }
57
58            async fn public_key(&self) -> Result<PublicKey> {
59                public_key(self).await
60            }
61        }
62
63    } else {
64        #[async_trait]
65        impl TxSigner for KeySigner {
66            async fn sign(&self, msg: &layer_climb_proto::tx::SignDoc) -> Result<Vec<u8>> {
67                sign(self, msg).await
68            }
69
70            async fn public_key(&self) -> Result<PublicKey> {
71                public_key(self).await
72            }
73        }
74    }
75}
76
77async fn sign(signer: &KeySigner, msg: &layer_climb_proto::tx::SignDoc) -> Result<Vec<u8>> {
78    let signed: k256::ecdsa::Signature = signer
79        .key
80        .private_key()
81        .try_sign(&layer_climb_proto::proto_into_bytes(msg)?)
82        .map_err(|err| anyhow!("{}", err))?;
83    Ok(signed.to_vec())
84}
85
86async fn public_key(signer: &KeySigner) -> Result<PublicKey> {
87    let public_key = signer.key.public_key();
88    let public_key_bytes = public_key.to_bytes();
89    PublicKey::from_raw_secp256k1(&public_key_bytes).context("Invalid secp256k1 public key")
90}