iota_sdk/client/secret/
mod.rs

1// Copyright 2021 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4//! Secret manager module enabling address generation and transaction essence signing.
5
6/// Module for ledger nano based secret management.
7#[cfg(feature = "ledger_nano")]
8#[cfg_attr(docsrs, doc(cfg(feature = "ledger_nano")))]
9pub mod ledger_nano;
10/// Module for mnemonic based secret management.
11pub mod mnemonic;
12/// Module for single private key based secret management.
13#[cfg(feature = "private_key_secret_manager")]
14#[cfg_attr(docsrs, doc(cfg(feature = "private_key_secret_manager")))]
15pub mod private_key;
16/// Module for stronghold based secret management.
17#[cfg(feature = "stronghold")]
18#[cfg_attr(docsrs, doc(cfg(feature = "stronghold")))]
19pub mod stronghold;
20/// Signing related types
21pub mod types;
22
23#[cfg(feature = "stronghold")]
24use std::time::Duration;
25use std::{collections::HashMap, fmt, ops::Range, str::FromStr};
26
27use async_trait::async_trait;
28use crypto::{
29    keys::{bip39::Mnemonic, bip44::Bip44},
30    signatures::secp256k1_ecdsa::{self, EvmAddress},
31};
32use serde::{de::DeserializeOwned, Deserialize, Serialize};
33use zeroize::Zeroizing;
34
35#[cfg(feature = "ledger_nano")]
36use self::ledger_nano::LedgerSecretManager;
37use self::mnemonic::MnemonicSecretManager;
38#[cfg(feature = "private_key_secret_manager")]
39use self::private_key::PrivateKeySecretManager;
40#[cfg(feature = "stronghold")]
41use self::stronghold::StrongholdSecretManager;
42pub use self::types::{GenerateAddressOptions, LedgerNanoStatus};
43#[cfg(feature = "stronghold")]
44use crate::client::secret::types::StrongholdDto;
45use crate::{
46    client::{
47        api::{
48            input_selection::{is_alias_transition, Error as InputSelectionError},
49            transaction::validate_transaction_payload_length,
50            verify_semantic, PreparedTransactionData,
51        },
52        Error,
53    },
54    types::block::{
55        address::{Address, Ed25519Address},
56        output::Output,
57        payload::{transaction::TransactionEssence, TransactionPayload},
58        semantic::ConflictReason,
59        signature::{Ed25519Signature, Signature},
60        unlock::{AliasUnlock, NftUnlock, ReferenceUnlock, SignatureUnlock, Unlock, Unlocks},
61    },
62    utils::unix_timestamp_now,
63};
64
65/// The secret manager interface.
66#[async_trait]
67pub trait SecretManage: Send + Sync {
68    type Error: std::error::Error + Send + Sync;
69
70    /// Generates addresses.
71    ///
72    /// For `coin_type`, see also <https://github.com/satoshilabs/slips/blob/master/slip-0044.md>.
73    async fn generate_ed25519_addresses(
74        &self,
75        coin_type: u32,
76        account_index: u32,
77        address_indexes: Range<u32>,
78        options: impl Into<Option<GenerateAddressOptions>> + Send,
79    ) -> Result<Vec<Ed25519Address>, Self::Error>;
80
81    async fn generate_evm_addresses(
82        &self,
83        coin_type: u32,
84        account_index: u32,
85        address_indexes: Range<u32>,
86        options: impl Into<Option<GenerateAddressOptions>> + Send,
87    ) -> Result<Vec<EvmAddress>, Self::Error>;
88
89    /// Signs msg using the given [`Bip44`] using Ed25519.
90    async fn sign_ed25519(&self, msg: &[u8], chain: Bip44) -> Result<Ed25519Signature, Self::Error>;
91
92    /// Signs msg using the given [`Bip44`] using Secp256k1.
93    async fn sign_secp256k1_ecdsa(
94        &self,
95        msg: &[u8],
96        chain: Bip44,
97    ) -> Result<(secp256k1_ecdsa::PublicKey, secp256k1_ecdsa::RecoverableSignature), Self::Error>;
98
99    /// Signs `essence_hash` using the given `chain`, returning an [`Unlock`].
100    async fn signature_unlock(&self, essence_hash: &[u8; 32], chain: Bip44) -> Result<Unlock, Self::Error> {
101        Ok(Unlock::Signature(SignatureUnlock::new(Signature::from(
102            self.sign_ed25519(essence_hash, chain).await?,
103        ))))
104    }
105
106    /// Signs a transaction essence.
107    async fn sign_transaction_essence(
108        &self,
109        prepared_transaction_data: &PreparedTransactionData,
110        time: Option<u32>,
111    ) -> Result<Unlocks, Self::Error>;
112
113    async fn sign_transaction(
114        &self,
115        prepared_transaction_data: PreparedTransactionData,
116    ) -> Result<TransactionPayload, Self::Error>;
117}
118
119pub trait SecretManagerConfig: SecretManage {
120    type Config: Serialize + DeserializeOwned + fmt::Debug + Send + Sync;
121
122    fn to_config(&self) -> Option<Self::Config>;
123
124    fn from_config(config: &Self::Config) -> Result<Self, Self::Error>
125    where
126        Self: Sized;
127}
128
129/// Supported secret managers
130#[non_exhaustive]
131pub enum SecretManager {
132    /// Secret manager that uses [`iota_stronghold`] as the backing storage.
133    #[cfg(feature = "stronghold")]
134    #[cfg_attr(docsrs, doc(cfg(feature = "stronghold")))]
135    Stronghold(StrongholdSecretManager),
136
137    /// Secret manager that uses a Ledger Nano hardware wallet or Speculos simulator.
138    #[cfg(feature = "ledger_nano")]
139    #[cfg_attr(docsrs, doc(cfg(feature = "ledger_nano")))]
140    LedgerNano(LedgerSecretManager),
141
142    /// Secret manager that uses a mnemonic in plain memory. It's not recommended for production use. Use
143    /// LedgerNano or Stronghold instead.
144    Mnemonic(MnemonicSecretManager),
145
146    /// Secret manager that uses a single private key.
147    #[cfg(feature = "private_key_secret_manager")]
148    #[cfg_attr(docsrs, doc(cfg(feature = "private_key_secret_manager")))]
149    PrivateKey(Box<PrivateKeySecretManager>),
150
151    /// Secret manager that's just a placeholder, so it can be provided to an online wallet, but can't be used for
152    /// signing.
153    Placeholder,
154}
155
156#[cfg(feature = "stronghold")]
157impl From<StrongholdSecretManager> for SecretManager {
158    fn from(secret_manager: StrongholdSecretManager) -> Self {
159        Self::Stronghold(secret_manager)
160    }
161}
162
163#[cfg(feature = "ledger_nano")]
164impl From<LedgerSecretManager> for SecretManager {
165    fn from(secret_manager: LedgerSecretManager) -> Self {
166        Self::LedgerNano(secret_manager)
167    }
168}
169
170impl From<MnemonicSecretManager> for SecretManager {
171    fn from(secret_manager: MnemonicSecretManager) -> Self {
172        Self::Mnemonic(secret_manager)
173    }
174}
175
176#[cfg(feature = "private_key_secret_manager")]
177impl From<PrivateKeySecretManager> for SecretManager {
178    fn from(secret_manager: PrivateKeySecretManager) -> Self {
179        Self::PrivateKey(Box::new(secret_manager))
180    }
181}
182
183impl fmt::Debug for SecretManager {
184    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185        match self {
186            #[cfg(feature = "stronghold")]
187            Self::Stronghold(_) => f.debug_tuple("Stronghold").field(&"...").finish(),
188            #[cfg(feature = "ledger_nano")]
189            Self::LedgerNano(_) => f.debug_tuple("LedgerNano").field(&"...").finish(),
190            Self::Mnemonic(_) => f.debug_tuple("Mnemonic").field(&"...").finish(),
191            #[cfg(feature = "private_key_secret_manager")]
192            Self::PrivateKey(_) => f.debug_tuple("PrivateKey").field(&"...").finish(),
193            Self::Placeholder => f.debug_struct("Placeholder").finish(),
194        }
195    }
196}
197
198impl fmt::Display for SecretManager {
199    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200        match self {
201            #[cfg(feature = "stronghold")]
202            Self::Stronghold(_) => write!(f, "Stronghold"),
203            #[cfg(feature = "ledger_nano")]
204            Self::LedgerNano(l) => {
205                if l.is_simulator {
206                    write!(f, "LedgerNano Simulator")
207                } else {
208                    write!(f, "LedgerNano")
209                }
210            }
211            Self::Mnemonic(_) => write!(f, "Mnemonic"),
212            #[cfg(feature = "private_key_secret_manager")]
213            Self::PrivateKey(_) => write!(f, "PrivateKey"),
214            Self::Placeholder => write!(f, "Placeholder"),
215        }
216    }
217}
218
219impl FromStr for SecretManager {
220    type Err = Error;
221
222    fn from_str(s: &str) -> crate::client::Result<Self> {
223        Self::try_from(serde_json::from_str::<SecretManagerDto>(s)?)
224    }
225}
226
227/// DTO for secret manager types with required data.
228#[derive(Clone, Debug, Serialize, Deserialize)]
229#[non_exhaustive]
230pub enum SecretManagerDto {
231    /// Stronghold
232    #[cfg(feature = "stronghold")]
233    #[cfg_attr(docsrs, doc(cfg(feature = "stronghold")))]
234    #[serde(alias = "stronghold")]
235    Stronghold(StrongholdDto),
236    /// Ledger Device, bool specifies if it's a simulator or not
237    #[cfg(feature = "ledger_nano")]
238    #[cfg_attr(docsrs, doc(cfg(feature = "ledger_nano")))]
239    #[serde(alias = "ledgerNano")]
240    LedgerNano(bool),
241    /// Mnemonic
242    #[serde(alias = "mnemonic")]
243    Mnemonic(Zeroizing<String>),
244    /// Private Key
245    #[cfg(feature = "private_key_secret_manager")]
246    #[cfg_attr(docsrs, doc(cfg(feature = "private_key_secret_manager")))]
247    #[serde(alias = "privateKey")]
248    PrivateKey(Zeroizing<String>),
249    /// Hex seed
250    #[serde(alias = "hexSeed")]
251    HexSeed(Zeroizing<String>),
252    /// Placeholder
253    #[serde(alias = "placeholder")]
254    Placeholder,
255}
256
257impl TryFrom<SecretManagerDto> for SecretManager {
258    type Error = Error;
259
260    fn try_from(value: SecretManagerDto) -> crate::client::Result<Self> {
261        Ok(match value {
262            #[cfg(feature = "stronghold")]
263            SecretManagerDto::Stronghold(stronghold_dto) => {
264                let mut builder = StrongholdSecretManager::builder();
265
266                if let Some(password) = stronghold_dto.password {
267                    builder = builder.password(password);
268                }
269
270                if let Some(timeout) = stronghold_dto.timeout {
271                    builder = builder.timeout(Duration::from_secs(timeout));
272                }
273
274                Self::Stronghold(builder.build(&stronghold_dto.snapshot_path)?)
275            }
276
277            #[cfg(feature = "ledger_nano")]
278            SecretManagerDto::LedgerNano(is_simulator) => Self::LedgerNano(LedgerSecretManager::new(is_simulator)),
279
280            SecretManagerDto::Mnemonic(mnemonic) => {
281                Self::Mnemonic(MnemonicSecretManager::try_from_mnemonic(mnemonic.as_str().to_owned())?)
282            }
283
284            #[cfg(feature = "private_key_secret_manager")]
285            SecretManagerDto::PrivateKey(private_key) => {
286                Self::PrivateKey(Box::new(PrivateKeySecretManager::try_from_hex(private_key)?))
287            }
288
289            SecretManagerDto::HexSeed(hex_seed) => {
290                // `SecretManagerDto` is `ZeroizeOnDrop` so it will take care of zeroizing the original.
291                Self::Mnemonic(MnemonicSecretManager::try_from_hex_seed(hex_seed)?)
292            }
293
294            SecretManagerDto::Placeholder => Self::Placeholder,
295        })
296    }
297}
298
299impl From<&SecretManager> for SecretManagerDto {
300    fn from(value: &SecretManager) -> Self {
301        match value {
302            #[cfg(feature = "stronghold")]
303            SecretManager::Stronghold(stronghold_adapter) => Self::Stronghold(StrongholdDto {
304                password: None,
305                timeout: stronghold_adapter.get_timeout().map(|duration| duration.as_secs()),
306                snapshot_path: stronghold_adapter
307                    .snapshot_path
308                    .clone()
309                    .into_os_string()
310                    .to_string_lossy()
311                    .into(),
312            }),
313
314            #[cfg(feature = "ledger_nano")]
315            SecretManager::LedgerNano(ledger_nano) => Self::LedgerNano(ledger_nano.is_simulator),
316
317            // `MnemonicSecretManager(Seed)` doesn't have Debug or Display implemented and in the current use cases of
318            // the client/wallet we also don't need to convert it in this direction with the mnemonic/seed, we only need
319            // to know the type
320            SecretManager::Mnemonic(_mnemonic) => Self::Mnemonic("...".to_string().into()),
321
322            #[cfg(feature = "private_key_secret_manager")]
323            SecretManager::PrivateKey(_private_key) => Self::PrivateKey("...".to_string().into()),
324
325            SecretManager::Placeholder => Self::Placeholder,
326        }
327    }
328}
329
330#[async_trait]
331impl SecretManage for SecretManager {
332    type Error = Error;
333
334    async fn generate_ed25519_addresses(
335        &self,
336        coin_type: u32,
337        account_index: u32,
338        address_indexes: Range<u32>,
339        options: impl Into<Option<GenerateAddressOptions>> + Send,
340    ) -> crate::client::Result<Vec<Ed25519Address>> {
341        match self {
342            #[cfg(feature = "stronghold")]
343            Self::Stronghold(secret_manager) => Ok(secret_manager
344                .generate_ed25519_addresses(coin_type, account_index, address_indexes, options)
345                .await?),
346            #[cfg(feature = "ledger_nano")]
347            Self::LedgerNano(secret_manager) => Ok(secret_manager
348                .generate_ed25519_addresses(coin_type, account_index, address_indexes, options)
349                .await?),
350            Self::Mnemonic(secret_manager) => {
351                secret_manager
352                    .generate_ed25519_addresses(coin_type, account_index, address_indexes, options)
353                    .await
354            }
355            #[cfg(feature = "private_key_secret_manager")]
356            Self::PrivateKey(secret_manager) => {
357                secret_manager
358                    .generate_ed25519_addresses(coin_type, account_index, address_indexes, options)
359                    .await
360            }
361            Self::Placeholder => Err(Error::PlaceholderSecretManager),
362        }
363    }
364
365    async fn generate_evm_addresses(
366        &self,
367        coin_type: u32,
368        account_index: u32,
369        address_indexes: Range<u32>,
370        options: impl Into<Option<GenerateAddressOptions>> + Send,
371    ) -> Result<Vec<EvmAddress>, Self::Error> {
372        match self {
373            #[cfg(feature = "stronghold")]
374            Self::Stronghold(secret_manager) => Ok(secret_manager
375                .generate_evm_addresses(coin_type, account_index, address_indexes, options)
376                .await?),
377            #[cfg(feature = "ledger_nano")]
378            Self::LedgerNano(secret_manager) => Ok(secret_manager
379                .generate_evm_addresses(coin_type, account_index, address_indexes, options)
380                .await?),
381            Self::Mnemonic(secret_manager) => {
382                secret_manager
383                    .generate_evm_addresses(coin_type, account_index, address_indexes, options)
384                    .await
385            }
386            #[cfg(feature = "private_key_secret_manager")]
387            Self::PrivateKey(secret_manager) => {
388                secret_manager
389                    .generate_evm_addresses(coin_type, account_index, address_indexes, options)
390                    .await
391            }
392            Self::Placeholder => Err(Error::PlaceholderSecretManager),
393        }
394    }
395
396    async fn sign_ed25519(&self, msg: &[u8], chain: Bip44) -> crate::client::Result<Ed25519Signature> {
397        match self {
398            #[cfg(feature = "stronghold")]
399            Self::Stronghold(secret_manager) => Ok(secret_manager.sign_ed25519(msg, chain).await?),
400            #[cfg(feature = "ledger_nano")]
401            Self::LedgerNano(secret_manager) => Ok(secret_manager.sign_ed25519(msg, chain).await?),
402            Self::Mnemonic(secret_manager) => secret_manager.sign_ed25519(msg, chain).await,
403            #[cfg(feature = "private_key_secret_manager")]
404            Self::PrivateKey(secret_manager) => secret_manager.sign_ed25519(msg, chain).await,
405            Self::Placeholder => Err(Error::PlaceholderSecretManager),
406        }
407    }
408
409    async fn sign_secp256k1_ecdsa(
410        &self,
411        msg: &[u8],
412        chain: Bip44,
413    ) -> Result<(secp256k1_ecdsa::PublicKey, secp256k1_ecdsa::RecoverableSignature), Self::Error> {
414        match self {
415            #[cfg(feature = "stronghold")]
416            Self::Stronghold(secret_manager) => Ok(secret_manager.sign_secp256k1_ecdsa(msg, chain).await?),
417            #[cfg(feature = "ledger_nano")]
418            Self::LedgerNano(secret_manager) => Ok(secret_manager.sign_secp256k1_ecdsa(msg, chain).await?),
419            Self::Mnemonic(secret_manager) => secret_manager.sign_secp256k1_ecdsa(msg, chain).await,
420            #[cfg(feature = "private_key_secret_manager")]
421            Self::PrivateKey(secret_manager) => secret_manager.sign_secp256k1_ecdsa(msg, chain).await,
422            Self::Placeholder => Err(Error::PlaceholderSecretManager),
423        }
424    }
425
426    async fn sign_transaction_essence(
427        &self,
428        prepared_transaction_data: &PreparedTransactionData,
429        time: Option<u32>,
430    ) -> Result<Unlocks, Self::Error> {
431        match self {
432            #[cfg(feature = "stronghold")]
433            Self::Stronghold(secret_manager) => Ok(secret_manager
434                .sign_transaction_essence(prepared_transaction_data, time)
435                .await?),
436            #[cfg(feature = "ledger_nano")]
437            Self::LedgerNano(secret_manager) => Ok(secret_manager
438                .sign_transaction_essence(prepared_transaction_data, time)
439                .await?),
440            Self::Mnemonic(secret_manager) => {
441                secret_manager
442                    .sign_transaction_essence(prepared_transaction_data, time)
443                    .await
444            }
445            #[cfg(feature = "private_key_secret_manager")]
446            Self::PrivateKey(secret_manager) => {
447                secret_manager
448                    .sign_transaction_essence(prepared_transaction_data, time)
449                    .await
450            }
451            Self::Placeholder => Err(Error::PlaceholderSecretManager),
452        }
453    }
454
455    async fn sign_transaction(
456        &self,
457        prepared_transaction_data: PreparedTransactionData,
458    ) -> Result<TransactionPayload, Self::Error> {
459        match self {
460            #[cfg(feature = "stronghold")]
461            Self::Stronghold(secret_manager) => Ok(secret_manager.sign_transaction(prepared_transaction_data).await?),
462            #[cfg(feature = "ledger_nano")]
463            Self::LedgerNano(secret_manager) => Ok(secret_manager.sign_transaction(prepared_transaction_data).await?),
464            Self::Mnemonic(secret_manager) => secret_manager.sign_transaction(prepared_transaction_data).await,
465            #[cfg(feature = "private_key_secret_manager")]
466            Self::PrivateKey(secret_manager) => secret_manager.sign_transaction(prepared_transaction_data).await,
467            Self::Placeholder => Err(Error::PlaceholderSecretManager),
468        }
469    }
470}
471
472pub trait DowncastSecretManager: SecretManage {
473    fn downcast<T: 'static + SecretManage>(&self) -> Option<&T>;
474}
475
476impl<S: 'static + SecretManage + Send + Sync> DowncastSecretManager for S {
477    fn downcast<T: 'static + SecretManage>(&self) -> Option<&T> {
478        (self as &(dyn std::any::Any + Send + Sync)).downcast_ref::<T>()
479    }
480}
481
482impl SecretManagerConfig for SecretManager {
483    type Config = SecretManagerDto;
484
485    fn to_config(&self) -> Option<Self::Config> {
486        match self {
487            #[cfg(feature = "stronghold")]
488            Self::Stronghold(s) => s.to_config().map(Self::Config::Stronghold),
489            #[cfg(feature = "ledger_nano")]
490            Self::LedgerNano(s) => s.to_config().map(Self::Config::LedgerNano),
491            Self::Mnemonic(_) => None,
492            #[cfg(feature = "private_key_secret_manager")]
493            Self::PrivateKey(_) => None,
494            Self::Placeholder => None,
495        }
496    }
497
498    fn from_config(config: &Self::Config) -> Result<Self, Self::Error> {
499        Ok(match config {
500            #[cfg(feature = "stronghold")]
501            SecretManagerDto::Stronghold(config) => Self::Stronghold(StrongholdSecretManager::from_config(config)?),
502            #[cfg(feature = "ledger_nano")]
503            SecretManagerDto::LedgerNano(config) => Self::LedgerNano(LedgerSecretManager::from_config(config)?),
504            SecretManagerDto::HexSeed(hex_seed) => {
505                Self::Mnemonic(MnemonicSecretManager::try_from_hex_seed(hex_seed.clone())?)
506            }
507            SecretManagerDto::Mnemonic(mnemonic) => {
508                Self::Mnemonic(MnemonicSecretManager::try_from_mnemonic(mnemonic.as_str().to_owned())?)
509            }
510            #[cfg(feature = "private_key_secret_manager")]
511            SecretManagerDto::PrivateKey(private_key) => {
512                Self::PrivateKey(Box::new(PrivateKeySecretManager::try_from_hex(private_key.to_owned())?))
513            }
514            SecretManagerDto::Placeholder => Self::Placeholder,
515        })
516    }
517}
518
519impl SecretManager {
520    /// Tries to create a [`SecretManager`] from a mnemonic string.
521    pub fn try_from_mnemonic(mnemonic: impl Into<Mnemonic>) -> crate::client::Result<Self> {
522        Ok(Self::Mnemonic(MnemonicSecretManager::try_from_mnemonic(mnemonic)?))
523    }
524
525    /// Tries to create a [`SecretManager`] from a seed hex string.
526    pub fn try_from_hex_seed(seed: impl Into<Zeroizing<String>>) -> crate::client::Result<Self> {
527        Ok(Self::Mnemonic(MnemonicSecretManager::try_from_hex_seed(seed)?))
528    }
529}
530
531pub(crate) async fn default_sign_transaction_essence<M: SecretManage>(
532    secret_manager: &M,
533    prepared_transaction_data: &PreparedTransactionData,
534    time: Option<u32>,
535) -> crate::client::Result<Unlocks>
536where
537    crate::client::Error: From<M::Error>,
538{
539    // The hashed_essence gets signed
540    let hashed_essence = prepared_transaction_data.essence.hash();
541    let mut blocks = Vec::new();
542    let mut block_indexes = HashMap::<Address, usize>::new();
543
544    // Assuming inputs_data is ordered by address type
545    for (current_block_index, input) in prepared_transaction_data.inputs_data.iter().enumerate() {
546        // Get the address that is required to unlock the input
547        let TransactionEssence::Regular(regular) = &prepared_transaction_data.essence;
548        let alias_transition = is_alias_transition(&input.output, *input.output_id(), regular.outputs(), None);
549        let (input_address, _) = input.output.required_and_unlocked_address(
550            time.unwrap_or_else(|| unix_timestamp_now().as_secs() as u32),
551            input.output_metadata.output_id(),
552            alias_transition,
553        )?;
554
555        // Check if we already added an [Unlock] for this address
556        match block_indexes.get(&input_address) {
557            // If we already have an [Unlock] for this address, add a [Unlock] based on the address type
558            Some(block_index) => match input_address {
559                Address::Alias(_alias) => blocks.push(Unlock::Alias(AliasUnlock::new(*block_index as u16)?)),
560                Address::Ed25519(_ed25519) => {
561                    blocks.push(Unlock::Reference(ReferenceUnlock::new(*block_index as u16)?));
562                }
563                Address::Nft(_nft) => blocks.push(Unlock::Nft(NftUnlock::new(*block_index as u16)?)),
564            },
565            None => {
566                // We can only sign ed25519 addresses and block_indexes needs to contain the alias or nft
567                // address already at this point, because the reference index needs to be lower
568                // than the current block index
569                if !input_address.is_ed25519() {
570                    Err(InputSelectionError::MissingInputWithEd25519Address)?;
571                }
572
573                let chain = input.chain.ok_or(Error::MissingBip32Chain)?;
574
575                let block = secret_manager.signature_unlock(&hashed_essence, chain).await?;
576                blocks.push(block);
577
578                // Add the ed25519 address to the block_indexes, so it gets referenced if further inputs have
579                // the same address in their unlock condition
580                block_indexes.insert(input_address, current_block_index);
581            }
582        }
583
584        // When we have an alias or Nft output, we will add their alias or nft address to block_indexes,
585        // because they can be used to unlock outputs via [Unlock::Alias] or [Unlock::Nft],
586        // that have the corresponding alias or nft address in their unlock condition
587        match &input.output {
588            Output::Alias(alias_output) => block_indexes.insert(
589                Address::Alias(alias_output.alias_address(input.output_id())),
590                current_block_index,
591            ),
592            Output::Nft(nft_output) => block_indexes.insert(
593                Address::Nft(nft_output.nft_address(input.output_id())),
594                current_block_index,
595            ),
596            _ => None,
597        };
598    }
599
600    Ok(Unlocks::new(blocks)?)
601}
602
603pub(crate) async fn default_sign_transaction<M: SecretManage>(
604    secret_manager: &M,
605    prepared_transaction_data: PreparedTransactionData,
606) -> crate::client::Result<TransactionPayload>
607where
608    crate::client::Error: From<M::Error>,
609{
610    log::debug!("[sign_transaction] {:?}", prepared_transaction_data);
611    let current_time = unix_timestamp_now().as_secs() as u32;
612
613    let unlocks = secret_manager
614        .sign_transaction_essence(&prepared_transaction_data, Some(current_time))
615        .await?;
616
617    let PreparedTransactionData {
618        essence, inputs_data, ..
619    } = prepared_transaction_data;
620    let tx_payload = TransactionPayload::new(essence, unlocks)?;
621
622    validate_transaction_payload_length(&tx_payload)?;
623
624    let conflict = verify_semantic(&inputs_data, &tx_payload, current_time)?;
625
626    if conflict != ConflictReason::None {
627        log::debug!("[sign_transaction] conflict: {conflict:?} for {:#?}", tx_payload);
628        return Err(Error::TransactionSemantic(conflict));
629    }
630
631    Ok(tx_payload)
632}