#[cfg(feature = "ledger_nano")]
pub mod ledger_nano;
pub mod mnemonic;
pub mod placeholder;
#[cfg(feature = "stronghold")]
pub mod stronghold;
pub mod types;
#[cfg(feature = "stronghold")]
use std::time::Duration;
use std::{collections::HashMap, ops::Range, str::FromStr};
use async_trait::async_trait;
use iota_types::block::{
address::Address,
output::Output,
unlock::{AliasUnlock, NftUnlock, ReferenceUnlock, Unlock, Unlocks},
};
pub use types::{GenerateAddressOptions, LedgerNanoStatus};
use zeroize::ZeroizeOnDrop;
#[cfg(feature = "ledger_nano")]
use self::ledger_nano::LedgerSecretManager;
#[cfg(feature = "stronghold")]
use self::stronghold::StrongholdSecretManager;
use self::{mnemonic::MnemonicSecretManager, placeholder::PlaceholderSecretManager};
#[cfg(feature = "stronghold")]
use crate::secret::types::StrongholdDto;
use crate::{
api::{PreparedTransactionData, RemainderData},
secret::types::InputSigningData,
};
#[async_trait]
pub trait SecretManage: Send + Sync {
async fn generate_addresses(
&self,
coin_type: u32,
account_index: u32,
address_indexes: Range<u32>,
internal: bool,
options: Option<GenerateAddressOptions>,
) -> crate::Result<Vec<Address>>;
async fn signature_unlock(
&self,
input: &InputSigningData,
essence_hash: &[u8; 32],
remainder: &Option<RemainderData>,
) -> crate::Result<Unlock>;
}
#[async_trait]
pub trait SecretManageExt {
async fn sign_transaction_essence(
&self,
prepared_transaction_data: &PreparedTransactionData,
) -> crate::Result<Unlocks>;
}
#[allow(clippy::large_enum_variant)]
pub enum SecretManager {
#[cfg(feature = "stronghold")]
#[cfg_attr(docsrs, doc(cfg(feature = "stronghold")))]
Stronghold(StrongholdSecretManager),
#[cfg(feature = "ledger_nano")]
#[cfg_attr(docsrs, doc(cfg(feature = "ledger_nano")))]
LedgerNano(LedgerSecretManager),
Mnemonic(MnemonicSecretManager),
Placeholder(PlaceholderSecretManager),
}
impl std::fmt::Debug for SecretManager {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
#[cfg(feature = "stronghold")]
Self::Stronghold(_) => f.debug_tuple("Stronghold").field(&"...").finish(),
#[cfg(feature = "ledger_nano")]
Self::LedgerNano(_) => f.debug_tuple("LedgerNano").field(&"...").finish(),
Self::Mnemonic(_) => f.debug_tuple("Mnemonic").field(&"...").finish(),
Self::Placeholder(_) => f.debug_struct("Placeholder").finish(),
}
}
}
impl FromStr for SecretManager {
type Err = crate::Error;
fn from_str(s: &str) -> crate::Result<Self> {
SecretManager::try_from(&serde_json::from_str::<SecretManagerDto>(s)?)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, ZeroizeOnDrop)]
pub enum SecretManagerDto {
#[cfg(feature = "stronghold")]
#[cfg_attr(docsrs, doc(cfg(feature = "stronghold")))]
#[serde(alias = "stronghold")]
Stronghold(StrongholdDto),
#[cfg(feature = "ledger_nano")]
#[serde(alias = "ledgerNano")]
LedgerNano(bool),
#[serde(alias = "mnemonic")]
Mnemonic(String),
#[serde(alias = "hexSeed")]
HexSeed(String),
#[serde(alias = "placeholder")]
Placeholder,
}
impl TryFrom<&SecretManagerDto> for SecretManager {
type Error = crate::Error;
fn try_from(value: &SecretManagerDto) -> crate::Result<Self> {
Ok(match value {
#[cfg(feature = "stronghold")]
SecretManagerDto::Stronghold(stronghold_dto) => {
let mut builder = StrongholdSecretManager::builder();
if let Some(password) = &stronghold_dto.password {
builder = builder.password(password);
}
if let Some(timeout) = &stronghold_dto.timeout {
builder = builder.timeout(Duration::from_secs(*timeout));
}
Self::Stronghold(builder.build(&stronghold_dto.snapshot_path)?)
}
#[cfg(feature = "ledger_nano")]
SecretManagerDto::LedgerNano(is_simulator) => Self::LedgerNano(LedgerSecretManager::new(*is_simulator)),
SecretManagerDto::Mnemonic(mnemonic) => Self::Mnemonic(MnemonicSecretManager::try_from_mnemonic(mnemonic)?),
SecretManagerDto::HexSeed(hex_seed) => Self::Mnemonic(MnemonicSecretManager::try_from_hex_seed(hex_seed)?),
SecretManagerDto::Placeholder => Self::Placeholder(PlaceholderSecretManager),
})
}
}
impl From<&SecretManager> for SecretManagerDto {
fn from(value: &SecretManager) -> Self {
match value {
#[cfg(feature = "stronghold")]
SecretManager::Stronghold(stronghold_adapter) => Self::Stronghold(StrongholdDto {
password: None,
timeout: stronghold_adapter.get_timeout().map(|duration| duration.as_secs()),
snapshot_path: stronghold_adapter
.snapshot_path
.clone()
.into_os_string()
.to_string_lossy()
.into(),
}),
#[cfg(feature = "ledger_nano")]
SecretManager::LedgerNano(ledger_nano) => Self::LedgerNano(ledger_nano.is_simulator),
SecretManager::Mnemonic(_mnemonic) => Self::Mnemonic("...".to_string()),
SecretManager::Placeholder(_) => Self::Placeholder,
}
}
}
#[async_trait]
impl SecretManage for SecretManager {
async fn generate_addresses(
&self,
coin_type: u32,
account_index: u32,
address_indexes: Range<u32>,
internal: bool,
options: Option<GenerateAddressOptions>,
) -> crate::Result<Vec<Address>> {
match self {
#[cfg(feature = "stronghold")]
SecretManager::Stronghold(secret_manager) => {
secret_manager
.generate_addresses(coin_type, account_index, address_indexes, internal, options)
.await
}
#[cfg(feature = "ledger_nano")]
SecretManager::LedgerNano(secret_manager) => {
secret_manager
.generate_addresses(coin_type, account_index, address_indexes, internal, options)
.await
}
SecretManager::Mnemonic(secret_manager) => {
secret_manager
.generate_addresses(coin_type, account_index, address_indexes, internal, options)
.await
}
SecretManager::Placeholder(secret_manager) => {
secret_manager
.generate_addresses(coin_type, account_index, address_indexes, internal, options)
.await
}
}
}
async fn signature_unlock(
&self,
input: &InputSigningData,
essence_hash: &[u8; 32],
metadata: &Option<RemainderData>,
) -> crate::Result<Unlock> {
match self {
#[cfg(feature = "stronghold")]
SecretManager::Stronghold(secret_manager) => {
secret_manager.signature_unlock(input, essence_hash, metadata).await
}
#[cfg(feature = "ledger_nano")]
SecretManager::LedgerNano(secret_manager) => {
secret_manager.signature_unlock(input, essence_hash, metadata).await
}
SecretManager::Mnemonic(secret_manager) => {
secret_manager.signature_unlock(input, essence_hash, metadata).await
}
SecretManager::Placeholder(secret_manager) => {
secret_manager.signature_unlock(input, essence_hash, metadata).await
}
}
}
}
#[async_trait]
impl SecretManageExt for SecretManager {
async fn sign_transaction_essence(
&self,
prepared_transaction_data: &PreparedTransactionData,
) -> crate::Result<Unlocks> {
match self {
#[cfg(feature = "stronghold")]
SecretManager::Stronghold(_) => self.default_sign_transaction_essence(prepared_transaction_data).await,
#[cfg(feature = "ledger_nano")]
SecretManager::LedgerNano(secret_manager) => {
secret_manager.sign_transaction_essence(prepared_transaction_data).await
}
SecretManager::Mnemonic(_) => self.default_sign_transaction_essence(prepared_transaction_data).await,
SecretManager::Placeholder(_) => self.sign_transaction_essence(prepared_transaction_data).await,
}
}
}
impl SecretManager {
async fn default_sign_transaction_essence<'a>(
&self,
prepared_transaction_data: &PreparedTransactionData,
) -> crate::Result<Unlocks> {
let hashed_essence = prepared_transaction_data.essence.hash();
let mut blocks = Vec::new();
let mut block_indexes = HashMap::<Address, usize>::new();
for (current_block_index, input) in prepared_transaction_data.inputs_data.iter().enumerate() {
let (_, input_address) = Address::try_from_bech32(&input.bech32_address)?;
match block_indexes.get(&input_address) {
Some(block_index) => match input_address {
Address::Alias(_alias) => blocks.push(Unlock::Alias(AliasUnlock::new(*block_index as u16)?)),
Address::Ed25519(_ed25519) => {
blocks.push(Unlock::Reference(ReferenceUnlock::new(*block_index as u16)?));
}
Address::Nft(_nft) => blocks.push(Unlock::Nft(NftUnlock::new(*block_index as u16)?)),
},
None => {
if !input_address.is_ed25519() {
return Err(crate::Error::MissingInputWithEd25519Address);
}
let block = self
.signature_unlock(input, &hashed_essence, &prepared_transaction_data.remainder)
.await?;
blocks.push(block);
block_indexes.insert(input_address, current_block_index);
}
}
match &input.output {
Output::Alias(alias_output) => block_indexes.insert(
Address::Alias(alias_output.alias_address(input.output_id())),
current_block_index,
),
Output::Nft(nft_output) => block_indexes.insert(
Address::Nft(nft_output.nft_address(input.output_id())),
current_block_index,
),
_ => None,
};
}
Ok(Unlocks::new(blocks)?)
}
}