iota_sdk/client/secret/
mnemonic.rs1use std::ops::Range;
7
8use async_trait::async_trait;
9use crypto::{
10 hashes::{blake2b::Blake2b256, Digest},
11 keys::{bip39::Mnemonic, bip44::Bip44, slip10::Seed},
12 signatures::{
13 ed25519,
14 secp256k1_ecdsa::{self, EvmAddress},
15 },
16};
17use zeroize::Zeroizing;
18
19use super::{GenerateAddressOptions, SecretManage};
20use crate::{
21 client::{api::PreparedTransactionData, Client, Error},
22 types::block::{
23 address::Ed25519Address, payload::transaction::TransactionPayload, signature::Ed25519Signature, unlock::Unlocks,
24 },
25};
26
27pub struct MnemonicSecretManager(Seed);
31
32impl std::fmt::Debug for MnemonicSecretManager {
33 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
34 f.debug_tuple("MnemonicSecretManager").finish()
35 }
36}
37
38#[async_trait]
39impl SecretManage for MnemonicSecretManager {
40 type Error = Error;
41
42 async fn generate_ed25519_addresses(
43 &self,
44 coin_type: u32,
45 account_index: u32,
46 address_indexes: Range<u32>,
47 options: impl Into<Option<GenerateAddressOptions>> + Send,
48 ) -> Result<Vec<Ed25519Address>, Self::Error> {
49 let internal = options.into().map(|o| o.internal).unwrap_or_default();
50
51 Ok(address_indexes
52 .map(|address_index| {
53 let chain = Bip44::new(coin_type)
54 .with_account(account_index)
55 .with_change(internal as _)
56 .with_address_index(address_index);
57
58 let public_key = chain
59 .derive(&self.0.to_master_key::<ed25519::SecretKey>())
60 .secret_key()
61 .public_key()
62 .to_bytes();
63
64 let result = Blake2b256::digest(public_key).into();
66
67 crate::client::Result::Ok(Ed25519Address::new(result))
68 })
69 .collect::<Result<_, _>>()?)
70 }
71
72 async fn generate_evm_addresses(
73 &self,
74 coin_type: u32,
75 account_index: u32,
76 address_indexes: Range<u32>,
77 options: impl Into<Option<GenerateAddressOptions>> + Send,
78 ) -> Result<Vec<EvmAddress>, Self::Error> {
79 let internal = options.into().map(|o| o.internal).unwrap_or_default();
80
81 Ok(address_indexes
82 .map(|address_index| {
83 let chain = Bip44::new(coin_type)
84 .with_account(account_index)
85 .with_change(internal as _)
86 .with_address_index(address_index);
87
88 let public_key = chain
89 .derive(&self.0.to_master_key::<secp256k1_ecdsa::SecretKey>())
90 .secret_key()
91 .public_key();
92
93 crate::client::Result::Ok(public_key.evm_address())
94 })
95 .collect::<Result<_, _>>()?)
96 }
97
98 async fn sign_ed25519(&self, msg: &[u8], chain: Bip44) -> Result<Ed25519Signature, Self::Error> {
99 let private_key = chain.derive(&self.0.to_master_key::<ed25519::SecretKey>()).secret_key();
101 let public_key = private_key.public_key();
102 let signature = private_key.sign(msg);
103
104 Ok(Ed25519Signature::new(public_key, signature))
105 }
106
107 async fn sign_secp256k1_ecdsa(
108 &self,
109 msg: &[u8],
110 chain: Bip44,
111 ) -> Result<(secp256k1_ecdsa::PublicKey, secp256k1_ecdsa::RecoverableSignature), Self::Error> {
112 let private_key = chain
114 .derive(&self.0.to_master_key::<secp256k1_ecdsa::SecretKey>())
115 .secret_key();
116 let public_key = private_key.public_key();
117 let signature = private_key.try_sign_keccak256(msg)?;
118
119 Ok((public_key, signature))
120 }
121
122 async fn sign_transaction_essence(
123 &self,
124 prepared_transaction_data: &PreparedTransactionData,
125 time: Option<u32>,
126 ) -> Result<Unlocks, Self::Error> {
127 super::default_sign_transaction_essence(self, prepared_transaction_data, time).await
128 }
129
130 async fn sign_transaction(
131 &self,
132 prepared_transaction_data: PreparedTransactionData,
133 ) -> Result<TransactionPayload, Self::Error> {
134 super::default_sign_transaction(self, prepared_transaction_data).await
135 }
136}
137
138impl MnemonicSecretManager {
139 pub fn try_from_mnemonic(mnemonic: impl Into<Mnemonic>) -> Result<Self, Error> {
143 Ok(Self(Client::mnemonic_to_seed(mnemonic.into())?.into()))
144 }
145
146 pub fn try_from_hex_seed(hex: impl Into<Zeroizing<String>>) -> Result<Self, Error> {
148 let hex = hex.into();
149 let bytes = Zeroizing::new(prefix_hex::decode::<Vec<u8>>(hex.as_str())?);
150 let seed = Seed::from_bytes(bytes.as_ref());
151 Ok(Self(seed))
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use pretty_assertions::assert_eq;
158
159 use super::*;
160 use crate::types::block::address::ToBech32Ext;
161
162 #[tokio::test]
163 async fn address() {
164 use crate::client::constants::IOTA_COIN_TYPE;
165
166 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";
167 let secret_manager = MnemonicSecretManager::try_from_mnemonic(mnemonic.to_owned()).unwrap();
168
169 let addresses = secret_manager
170 .generate_ed25519_addresses(IOTA_COIN_TYPE, 0, 0..1, None)
171 .await
172 .unwrap();
173
174 assert_eq!(
175 addresses[0].to_bech32_unchecked("atoi"),
176 "atoi1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluehe53e"
177 );
178 }
179
180 #[tokio::test]
181 async fn seed_address() {
182 use crate::client::constants::IOTA_COIN_TYPE;
183
184 let seed = "0x256a818b2aac458941f7274985a410e57fb750f3a3a67969ece5bd9ae7eef5b2".to_owned();
185 let secret_manager = MnemonicSecretManager::try_from_hex_seed(seed).unwrap();
186
187 let addresses = secret_manager
188 .generate_ed25519_addresses(IOTA_COIN_TYPE, 0, 0..1, None)
189 .await
190 .unwrap();
191
192 assert_eq!(
193 addresses[0].to_bech32_unchecked("atoi"),
194 "atoi1qzt0nhsf38nh6rs4p6zs5knqp6psgha9wsv74uajqgjmwc75ugupx3y7x0r"
195 );
196 }
197}