iota_sdk/client/secret/
mnemonic.rs

1// Copyright 2021 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4//! Implementation of [`MnemonicSecretManager`].
5
6use 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
27/// Secret manager that uses only a mnemonic.
28///
29/// Computation are done in-memory. A mnemonic needs to be supplied upon the creation of [`MnemonicSecretManager`].
30pub 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                // Hash the public key to get the address
65                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        // Get the private and public key for this Ed25519 address
100        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        // Get the private and public key for this secp256k1_ecdsa key
113        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    /// Create a new [`MnemonicSecretManager`] from a BIP-39 mnemonic in English.
140    ///
141    /// For more information, see <https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki>.
142    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    /// Create a new [`MnemonicSecretManager`] from a hex-encoded raw seed string.
147    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}