#[cfg(feature = "ledger_nano")]
#[cfg_attr(docsrs, doc(cfg(feature = "ledger_nano")))]
pub mod ledger_nano;
pub mod mnemonic;
pub mod placeholder;
#[cfg(feature = "stronghold")]
#[cfg_attr(docsrs, doc(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 crypto::keys::slip10::Chain;
use zeroize::ZeroizeOnDrop;
#[cfg(feature = "ledger_nano")]
use self::ledger_nano::LedgerSecretManager;
#[cfg(feature = "stronghold")]
use self::stronghold::StrongholdSecretManager;
pub use self::types::{GenerateAddressOptions, LedgerNanoStatus};
use self::{mnemonic::MnemonicSecretManager, placeholder::PlaceholderSecretManager};
#[cfg(feature = "stronghold")]
use crate::client::secret::types::StrongholdDto;
use crate::{
client::{
api::{
input_selection::{is_alias_transition, Error as InputSelectionError},
transaction::validate_transaction_payload_length,
verify_semantic, PreparedTransactionData,
},
Error,
},
types::block::{
address::Address,
output::Output,
payload::{transaction::TransactionEssence, Payload, TransactionPayload},
semantic::ConflictReason,
signature::{Ed25519Signature, Signature},
unlock::{AliasUnlock, NftUnlock, ReferenceUnlock, SignatureUnlock, Unlock, Unlocks},
},
utils::unix_timestamp_now,
};
#[async_trait]
pub trait SecretManage: Send + Sync {
type Error;
async fn generate_addresses(
&self,
coin_type: u32,
account_index: u32,
address_indexes: Range<u32>,
options: Option<GenerateAddressOptions>,
) -> Result<Vec<Address>, Self::Error>;
async fn sign_ed25519(&self, msg: &[u8], chain: &Chain) -> Result<Ed25519Signature, Self::Error>;
async fn signature_unlock(&self, essence_hash: &[u8; 32], chain: &Chain) -> Result<Unlock, Self::Error> {
Ok(Unlock::Signature(SignatureUnlock::new(Signature::Ed25519(
self.sign_ed25519(essence_hash, chain).await?,
))))
}
}
#[async_trait]
pub trait SecretManageExt: SecretManage {
async fn sign_transaction_essence(
&self,
prepared_transaction_data: &PreparedTransactionData,
time: Option<u32>,
) -> Result<Unlocks, <Self as SecretManage>::Error>;
}
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 = Error;
fn from_str(s: &str) -> crate::client::Result<Self> {
Self::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")]
#[cfg_attr(docsrs, doc(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 = Error;
fn try_from(value: &SecretManagerDto) -> crate::client::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 {
type Error = Error;
async fn generate_addresses(
&self,
coin_type: u32,
account_index: u32,
address_indexes: Range<u32>,
options: Option<GenerateAddressOptions>,
) -> crate::client::Result<Vec<Address>> {
match self {
#[cfg(feature = "stronghold")]
Self::Stronghold(secret_manager) => Ok(secret_manager
.generate_addresses(coin_type, account_index, address_indexes, options)
.await?),
#[cfg(feature = "ledger_nano")]
Self::LedgerNano(secret_manager) => Ok(secret_manager
.generate_addresses(coin_type, account_index, address_indexes, options)
.await?),
Self::Mnemonic(secret_manager) => {
secret_manager
.generate_addresses(coin_type, account_index, address_indexes, options)
.await
}
Self::Placeholder(secret_manager) => {
secret_manager
.generate_addresses(coin_type, account_index, address_indexes, options)
.await
}
}
}
async fn sign_ed25519(&self, msg: &[u8], chain: &Chain) -> crate::client::Result<Ed25519Signature> {
match self {
#[cfg(feature = "stronghold")]
Self::Stronghold(secret_manager) => Ok(secret_manager.sign_ed25519(msg, chain).await?),
#[cfg(feature = "ledger_nano")]
Self::LedgerNano(secret_manager) => Ok(secret_manager.sign_ed25519(msg, chain).await?),
Self::Mnemonic(secret_manager) => secret_manager.sign_ed25519(msg, chain).await,
Self::Placeholder(secret_manager) => secret_manager.sign_ed25519(msg, chain).await,
}
}
}
#[async_trait]
impl SecretManageExt for SecretManager {
async fn sign_transaction_essence(
&self,
prepared_transaction_data: &PreparedTransactionData,
time: Option<u32>,
) -> crate::client::Result<Unlocks> {
match self {
#[cfg(feature = "stronghold")]
Self::Stronghold(_) => {
self.default_sign_transaction_essence(prepared_transaction_data, time)
.await
}
#[cfg(feature = "ledger_nano")]
Self::LedgerNano(secret_manager) => Ok(secret_manager
.sign_transaction_essence(prepared_transaction_data, time)
.await?),
Self::Mnemonic(_) => {
self.default_sign_transaction_essence(prepared_transaction_data, time)
.await
}
Self::Placeholder(_) => self.sign_transaction_essence(prepared_transaction_data, time).await,
}
}
}
impl SecretManager {
pub fn try_from_mnemonic(mnemonic: &str) -> crate::client::Result<Self> {
Ok(Self::Mnemonic(MnemonicSecretManager::try_from_mnemonic(mnemonic)?))
}
pub fn try_from_hex_seed(seed: &str) -> crate::client::Result<Self> {
Ok(Self::Mnemonic(MnemonicSecretManager::try_from_hex_seed(seed)?))
}
async fn default_sign_transaction_essence<'a>(
&self,
prepared_transaction_data: &PreparedTransactionData,
time: Option<u32>,
) -> crate::client::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 TransactionEssence::Regular(regular) = &prepared_transaction_data.essence;
let alias_transition = is_alias_transition(input, regular.outputs()).map(|t| t.0);
let (input_address, _) = input.output.required_and_unlocked_address(
time.unwrap_or_else(|| unix_timestamp_now().as_secs() as u32),
input.output_metadata.output_id(),
alias_transition,
)?;
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(InputSelectionError::MissingInputWithEd25519Address)?;
}
let chain = input.chain.as_ref().ok_or(Error::MissingBip32Chain)?;
let block = self.signature_unlock(&hashed_essence, chain).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)?)
}
pub async fn sign_transaction(
&self,
prepared_transaction_data: PreparedTransactionData,
) -> crate::client::Result<Payload> {
log::debug!("[sign_transaction] {:?}", prepared_transaction_data);
let current_time = unix_timestamp_now().as_secs() as u32;
let unlocks = self
.sign_transaction_essence(&prepared_transaction_data, Some(current_time))
.await?;
let tx_payload = TransactionPayload::new(prepared_transaction_data.essence.clone(), unlocks)?;
validate_transaction_payload_length(&tx_payload)?;
let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload, current_time)?;
if conflict != ConflictReason::None {
log::debug!("[sign_transaction] conflict: {conflict:?} for {:#?}", tx_payload);
return Err(Error::TransactionSemantic(conflict));
}
Ok(Payload::from(tx_payload))
}
}