1#[cfg(feature = "ledger_nano")]
8#[cfg_attr(docsrs, doc(cfg(feature = "ledger_nano")))]
9pub mod ledger_nano;
10pub mod mnemonic;
12#[cfg(feature = "private_key_secret_manager")]
14#[cfg_attr(docsrs, doc(cfg(feature = "private_key_secret_manager")))]
15pub mod private_key;
16#[cfg(feature = "stronghold")]
18#[cfg_attr(docsrs, doc(cfg(feature = "stronghold")))]
19pub mod stronghold;
20pub 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#[async_trait]
67pub trait SecretManage: Send + Sync {
68 type Error: std::error::Error + Send + Sync;
69
70 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 async fn sign_ed25519(&self, msg: &[u8], chain: Bip44) -> Result<Ed25519Signature, Self::Error>;
91
92 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 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 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#[non_exhaustive]
131pub enum SecretManager {
132 #[cfg(feature = "stronghold")]
134 #[cfg_attr(docsrs, doc(cfg(feature = "stronghold")))]
135 Stronghold(StrongholdSecretManager),
136
137 #[cfg(feature = "ledger_nano")]
139 #[cfg_attr(docsrs, doc(cfg(feature = "ledger_nano")))]
140 LedgerNano(LedgerSecretManager),
141
142 Mnemonic(MnemonicSecretManager),
145
146 #[cfg(feature = "private_key_secret_manager")]
148 #[cfg_attr(docsrs, doc(cfg(feature = "private_key_secret_manager")))]
149 PrivateKey(Box<PrivateKeySecretManager>),
150
151 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#[derive(Clone, Debug, Serialize, Deserialize)]
229#[non_exhaustive]
230pub enum SecretManagerDto {
231 #[cfg(feature = "stronghold")]
233 #[cfg_attr(docsrs, doc(cfg(feature = "stronghold")))]
234 #[serde(alias = "stronghold")]
235 Stronghold(StrongholdDto),
236 #[cfg(feature = "ledger_nano")]
238 #[cfg_attr(docsrs, doc(cfg(feature = "ledger_nano")))]
239 #[serde(alias = "ledgerNano")]
240 LedgerNano(bool),
241 #[serde(alias = "mnemonic")]
243 Mnemonic(Zeroizing<String>),
244 #[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 #[serde(alias = "hexSeed")]
251 HexSeed(Zeroizing<String>),
252 #[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 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 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 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 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 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 for (current_block_index, input) in prepared_transaction_data.inputs_data.iter().enumerate() {
546 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 match block_indexes.get(&input_address) {
557 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 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 block_indexes.insert(input_address, current_block_index);
581 }
582 }
583
584 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}