use bech32;
use bip39::{Language, Mnemonic, Seed};
use hdwallet::secp256k1::{PublicKey, Secp256k1, SecretKey};
use hdwallet::{DefaultKeyChain, ExtendedPrivKey, KeyChain};
use ripemd::Ripemd160;
use sha2::{Digest, Sha256};
const PUBLIC_KEY_LENGTH: usize = 33;
pub struct PrivateKeyWallet(SecretKey);
impl PrivateKeyWallet {
pub fn to_secret_key(&self) -> &SecretKey {
&self.0
}
pub fn from_key(sk: SecretKey) -> PrivateKeyWallet {
PrivateKeyWallet(sk)
}
pub fn from_seed(phrase: String, path: Option<String>) -> PrivateKeyWallet {
let mnemonic = Mnemonic::from_phrase(phrase.as_str(), Language::English)
.expect("Failed to create mnemonic phrase");
let seed = Seed::new(&mnemonic, "");
let seed_bytes = seed.as_bytes();
let master_key = ExtendedPrivKey::with_seed(seed_bytes).unwrap();
let key_chain = DefaultKeyChain::new(master_key);
let chain_path = match path {
Some(s) => s,
None => "m/44'/118'/0'/0/0".to_string(),
};
let child_key = key_chain
.derive_private_key(chain_path.as_str().into())
.unwrap();
let private_key = child_key.0.private_key;
PrivateKeyWallet(private_key)
}
}
pub struct PublicKeyWallet([u8; PUBLIC_KEY_LENGTH]);
impl PublicKeyWallet {
pub fn to_bytes(&self) -> &[u8; PUBLIC_KEY_LENGTH] {
&self.0
}
pub fn to_string(&self) -> String {
let public_key = PublicKey::from_slice(&self.0).expect("Invalid bytes in the public key");
public_key.to_string()
}
pub fn from_key(pk: [u8; PUBLIC_KEY_LENGTH]) -> PublicKeyWallet {
PublicKeyWallet(pk)
}
pub fn from_private_key(key: &SecretKey) -> PublicKeyWallet {
let secp = Secp256k1::new();
let public_key = PublicKey::from_secret_key(&secp, key);
PublicKeyWallet(public_key.serialize())
}
pub fn to_address(&self, prefix: &str) -> String {
let mut sha256 = Sha256::new();
sha256.update(self.0);
let s = sha256.finalize();
let mut ripemd = Ripemd160::new();
ripemd.update(s);
let r = ripemd.finalize();
let five_bit_r =
bech32::convert_bits(&r, 8, 8, true).expect("Unsuccessful bech32::convert_bits call");
subtle_encoding::bech32::encode(prefix, five_bit_r)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn from_seed(phrase: String) -> PublicKeyWallet {
let sk = PrivateKeyWallet::from_seed(phrase, None);
let pk = PublicKeyWallet::from_private_key(sk.to_secret_key());
pk
}
#[test]
fn test_private_from_seed() {
assert_eq!(
from_seed(String::from(
"soap weird dutch gap region blossom antique economy legend loan ugly boring"
))
.to_address("cyber"),
String::from("cyber1gw824ephm676c93ur3zgefctj3frvupc4tmn3v")
);
assert_eq!(
from_seed(String::from(
"tomorrow few flag walnut dwarf kiwi close stick sniff satoshi chest vacuum"
))
.to_address("cyber"),
String::from("cyber1q652n3ylk27rxkkxhj8d0ty3txcm2pjnn4q46r")
);
assert_eq!(
from_seed(String::from(
"soap weird dutch gap region blossom antique economy legend loan ugly boring"
))
.to_address("bostrom"),
String::from("bostrom1gw824ephm676c93ur3zgefctj3frvupc3nggx3")
);
assert_eq!(
from_seed(String::from(
"tomorrow few flag walnut dwarf kiwi close stick sniff satoshi chest vacuum"
))
.to_address("bostrom"),
String::from("bostrom1q652n3ylk27rxkkxhj8d0ty3txcm2pjnhdnwd7")
);
}
}