use bip32::XPrv;
use crate::{Error, SigningKey};
pub const COSMOS_PATH: &str = "m/44'/118'/0'/0/0";
pub fn seed(mnemonic: &str, passphrase: &str) -> Result<[u8; 64], Error> {
let mn = bip39::Mnemonic::parse(mnemonic).map_err(|e| Error::Mnemonic(e.to_string()))?;
Ok(mn.to_seed(passphrase))
}
pub fn signing_key(seed: &[u8; 64], path: &str) -> Result<SigningKey, Error> {
let path = path.parse().map_err(|e: bip32::Error| Error::Derive(e.to_string()))?;
let xprv = XPrv::derive_from_path(seed, &path).map_err(|e| Error::Derive(e.to_string()))?;
Ok(xprv.into())
}
pub fn cosmos_key(mnemonic: &str, passphrase: &str) -> Result<SigningKey, Error> {
let seed = seed(mnemonic, passphrase)?;
signing_key(&seed, COSMOS_PATH)
}
pub fn generate_mnemonic() -> Result<String, Error> {
bip39::Mnemonic::generate(24)
.map(|m| m.to_string())
.map_err(|e| Error::Mnemonic(e.to_string()))
}
#[cfg(test)]
mod tests {
use super::*;
const MNEMONIC: &str =
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
#[test]
fn derivation_is_deterministic() {
let a = cosmos_key(MNEMONIC, "").unwrap();
let b = cosmos_key(MNEMONIC, "").unwrap();
assert_eq!(a.to_bytes(), b.to_bytes(), "same mnemonic โ same key");
}
#[test]
fn passphrase_changes_the_key() {
let a = cosmos_key(MNEMONIC, "").unwrap();
let b = cosmos_key(MNEMONIC, "trezor").unwrap();
assert_ne!(a.to_bytes(), b.to_bytes(), "the 25th word forks the wallet");
}
#[test]
fn bad_mnemonic_is_rejected() {
assert!(cosmos_key("not a real mnemonic phrase at all", "").is_err());
}
}