use std::ops::Range;
use async_trait::async_trait;
use crypto::{
hashes::{blake2b::Blake2b256, Digest},
keys::slip10::{Chain, Curve, Seed},
};
use super::{GenerateAddressOptions, SecretManage};
use crate::{
client::{constants::HD_WALLET_TYPE, Client, Error},
types::block::{
address::{Address, Ed25519Address},
signature::Ed25519Signature,
},
};
pub struct MnemonicSecretManager(Seed);
#[async_trait]
impl SecretManage for MnemonicSecretManager {
type Error = Error;
async fn generate_addresses(
&self,
coin_type: u32,
account_index: u32,
address_indexes: Range<u32>,
options: Option<GenerateAddressOptions>,
) -> Result<Vec<Address>, Self::Error> {
let internal = options.map(|o| o.internal).unwrap_or_default();
let mut addresses = Vec::new();
for address_index in address_indexes {
let chain = Chain::from_u32_hardened(vec![
HD_WALLET_TYPE,
coin_type,
account_index,
internal as u32,
address_index,
]);
let public_key = self
.0
.derive(Curve::Ed25519, &chain)?
.secret_key()
.public_key()
.to_bytes();
let result = Blake2b256::digest(public_key).try_into().map_err(|_e| {
crate::client::Error::Blake2b256("hashing the public key while generating the address failed.")
});
addresses.push(Address::Ed25519(Ed25519Address::new(result?)));
}
Ok(addresses)
}
async fn sign_ed25519(&self, msg: &[u8], chain: &Chain) -> Result<Ed25519Signature, Self::Error> {
let private_key = self.0.derive(Curve::Ed25519, chain)?.secret_key();
let public_key = private_key.public_key().to_bytes();
let signature = private_key.sign(msg).to_bytes();
Ok(Ed25519Signature::new(public_key, signature))
}
}
impl MnemonicSecretManager {
pub fn try_from_mnemonic(mnemonic: &str) -> Result<Self, Error> {
Ok(Self(Client::mnemonic_to_seed(mnemonic)?))
}
pub fn try_from_hex_seed(hex: &str) -> Result<Self, Error> {
let bytes: Vec<u8> = prefix_hex::decode(hex)?;
Ok(Self(Seed::from_bytes(&bytes)))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn address() {
use crate::client::constants::IOTA_COIN_TYPE;
let mnemonic = "giant dynamic museum toddler six deny defense ostrich bomb access mercy blood explain muscle shoot shallow glad autumn author calm heavy hawk abuse rally";
let secret_manager = MnemonicSecretManager::try_from_mnemonic(mnemonic).unwrap();
let addresses = secret_manager
.generate_addresses(IOTA_COIN_TYPE, 0, 0..1, None)
.await
.unwrap();
assert_eq!(
addresses[0].to_bech32("atoi"),
"atoi1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluehe53e".to_string()
);
}
#[tokio::test]
async fn seed_address() {
use crate::client::constants::IOTA_COIN_TYPE;
let seed = "0x256a818b2aac458941f7274985a410e57fb750f3a3a67969ece5bd9ae7eef5b2";
let secret_manager = MnemonicSecretManager::try_from_hex_seed(seed).unwrap();
let addresses = secret_manager
.generate_addresses(IOTA_COIN_TYPE, 0, 0..1, None)
.await
.unwrap();
assert_eq!(
addresses[0].to_bech32("atoi"),
"atoi1qzt0nhsf38nh6rs4p6zs5knqp6psgha9wsv74uajqgjmwc75ugupx3y7x0r".to_string()
);
}
}