Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix stronghold backup/restore #2233

Merged
merged 3 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bindings/nodejs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed

- Mana allotment when burning something/setting a TransactionCapabilityFlag;
- Stronghold backup/restore;

## 2.0.0-alpha.6 - 2024-04-17

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@ impl<S: 'static + SecretManagerConfig> Wallet<S> {
}

// Store the wallet address
stronghold
.set(WALLET_ADDRESS_KEY, self.address().await.as_ref())
.await?;
stronghold.set(WALLET_ADDRESS_KEY, &self.address().await).await?;

// Store the wallet bip path
stronghold.set(WALLET_BIP_PATH_KEY, &self.bip_path().await).await?;

// Store the wallet alias
stronghold.set(WALLET_ALIAS_KEY, &self.alias().await).await?;
if let Some(alias) = self.alias().await {
stronghold.set(WALLET_ALIAS_KEY, &alias).await?;
}

let serialized_wallet_ledger = serde_json::to_value(&WalletLedgerDto::from(&*self.ledger.read().await))?;
stronghold.set(WALLET_LEDGER_KEY, &serialized_wallet_ledger).await?;
Expand All @@ -71,6 +71,7 @@ pub(crate) async fn read_fields_from_stronghold_snapshot<S: 'static + SecretMana
),
WalletError,
> {
log::debug!("[read_fields_from_stronghold_snapshot]");
migrate(stronghold).await?;

// Get client_options
Expand Down
255 changes: 130 additions & 125 deletions sdk/tests/wallet/backup_restore.rs
Original file line number Diff line number Diff line change
@@ -1,130 +1,135 @@
// Copyright 2022 IOTA Stiftung
// Copyright 2022-2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

// use std::path::PathBuf;

// use crypto::keys::bip39::Mnemonic;
// use iota_sdk::{
// client::{
// api::GetAddressesOptions,
// constants::{IOTA_COIN_TYPE, SHIMMER_COIN_TYPE},
// node_manager::node::{Node, NodeDto},
// secret::{mnemonic::MnemonicSecretManager, stronghold::StrongholdSecretManager, SecretManager},
// },
// crypto::keys::bip44::Bip44,
// wallet::{ClientOptions, Result, Wallet},
// };
// use pretty_assertions::assert_eq;
// use url::Url;

// use crate::wallet::common::{setup, tear_down, NODE_LOCAL, NODE_OTHER};

// // Backup and restore with Stronghold
// #[tokio::test]
// async fn backup_and_restore() -> Result<(), WalletError> {
// iota_stronghold::engine::snapshot::try_set_encrypt_work_factor(0).unwrap();

// let storage_path = "test-storage/backup_and_restore";
// setup(storage_path)?;

// let client_options = ClientOptions::new().with_node(NODE_LOCAL)?;

// let stronghold_password = "some_hopefully_secure_password".to_owned();

// // Create directory if not existing, because stronghold panics otherwise
// std::fs::create_dir_all(storage_path).ok();
// let stronghold = StrongholdSecretManager::builder()
// .password(stronghold_password.clone())
// .build("test-storage/backup_and_restore/1.stronghold")?;

// stronghold.store_mnemonic(Mnemonic::from("inhale gorilla deny three celery song category owner lottery rent author wealth penalty crawl hobby obtain glad warm early rain clutch slab august bleak".to_string())).await.unwrap();

// let wallet = Wallet::builder()
// .with_secret_manager(SecretManager::Stronghold(stronghold))
// .with_client_options(client_options.clone())
// .with_bip_path(Bip44::new(SHIMMER_COIN_TYPE))
// .with_storage_path("test-storage/backup_and_restore/1")
// .finish()
// .await?;

// wallet
// .backup(
// PathBuf::from("test-storage/backup_and_restore/backup.stronghold"),
// stronghold_password.clone(),
// )
// .await?;

// // restore from backup

// let stronghold = StrongholdSecretManager::builder().build("test-storage/backup_and_restore/2.stronghold")?;

// let restored_wallet = Wallet::builder()
// .with_storage_path("test-storage/backup_and_restore/2")
// .with_secret_manager(SecretManager::Stronghold(stronghold))
// .with_client_options(ClientOptions::new().with_node(NODE_OTHER)?)
// // Build with a different coin type, to check if it gets replaced by the one from the backup
// .with_bip_path(Bip44::new(IOTA_COIN_TYPE))
// .finish()
// .await?;

// // Wrong password fails
// restored_wallet
// .restore_backup(
// PathBuf::from("test-storage/backup_and_restore/backup.stronghold"),
// "wrong password".to_owned(),
// None,
// None,
// )
// .await
// .unwrap_err();

// // Correct password works, even after trying with a wrong one before
// restored_wallet
// .restore_backup(
// PathBuf::from("test-storage/backup_and_restore/backup.stronghold"),
// stronghold_password,
// None,
// None,
// )
// .await?;

// // Validate restored data

// // Restored coin type is used
// assert_eq!(restored_wallet.bip_path().await.unwrap().coin_type, SHIMMER_COIN_TYPE);

// // compare restored client options
// let client_options = restored_wallet.client_options().await;
// let node_dto = NodeDto::Node(Node::from(Url::parse(NODE_LOCAL).unwrap()));
// assert!(client_options.node_manager_builder.nodes.contains(&node_dto));

// assert_eq!(wallet.address().clone(), restored_wallet.address().clone());

// // secret manager is the same
// assert_eq!(
// wallet
// .get_secret_manager()
// .read()
// .await
// .generate_ed25519_addresses(GetAddressesOptions {
// coin_type: SHIMMER_COIN_TYPE,
// range: 0..1,
// ..Default::default()
// })
// .await?,
// restored_wallet
// .get_secret_manager()
// .read()
// .await
// .generate_ed25519_addresses(GetAddressesOptions {
// coin_type: SHIMMER_COIN_TYPE,
// range: 0..1,
// ..Default::default()
// })
// .await?,
// );
// tear_down(storage_path)
// }
use std::path::PathBuf;

use crypto::keys::bip39::Mnemonic;
use iota_sdk::{
client::{
api::GetAddressesOptions,
constants::{IOTA_COIN_TYPE, SHIMMER_COIN_TYPE},
node_manager::node::{Node, NodeDto},
secret::{stronghold::StrongholdSecretManager, SecretManager},
},
crypto::keys::bip44::Bip44,
wallet::{ClientOptions, Wallet},
};
use pretty_assertions::assert_eq;
use url::Url;

use crate::wallet::common::{setup, tear_down, NODE_LOCAL};

// Backup and restore with Stronghold
#[ignore]
#[tokio::test]
async fn backup_and_restore() -> Result<(), Box<dyn std::error::Error>> {
iota_stronghold::engine::snapshot::try_set_encrypt_work_factor(0).unwrap();

let storage_path = "test-storage/backup_and_restore";
setup(storage_path)?;

let client_options = ClientOptions::new().with_node(NODE_LOCAL)?;

let stronghold_password = "some_hopefully_secure_password".to_owned();

// Create directory if not existing, because stronghold panics otherwise
std::fs::create_dir_all(storage_path).ok();
let stronghold = StrongholdSecretManager::builder()
.password(stronghold_password.clone())
.build("test-storage/backup_and_restore/1.stronghold")?;

stronghold.store_mnemonic(Mnemonic::from("inhale gorilla deny three celery song category owner lottery rent author wealth penalty crawl hobby obtain glad warm early rain clutch slab august bleak".to_string())).await.unwrap();

let wallet = Wallet::builder()
.with_secret_manager(SecretManager::Stronghold(stronghold))
.with_client_options(client_options.clone())
.with_bip_path(Bip44::new(SHIMMER_COIN_TYPE))
.with_storage_path("test-storage/backup_and_restore/1")
.finish()
.await?;

wallet
.backup_to_stronghold_snapshot(
PathBuf::from("test-storage/backup_and_restore/backup.stronghold"),
stronghold_password.clone(),
)
.await?;

// restore from backup

let stronghold = StrongholdSecretManager::builder()
.password(stronghold_password.clone())
.build("test-storage/backup_and_restore/2.stronghold")?;

stronghold.store_mnemonic(Mnemonic::from("surprise own liquid gold embrace indoor cereal magnet wink purse similar unusual setup woman catch chuckle critic wet weasel ahead wasp cruise luggage pig".to_string())).await.unwrap();

let restored_wallet = Wallet::builder()
.with_storage_path("test-storage/backup_and_restore/2")
.with_secret_manager(SecretManager::Stronghold(stronghold))
.with_client_options(ClientOptions::new().with_ignore_node_health().with_node(NODE_LOCAL)?)
// Build with a different coin type, to check if it gets replaced by the one from the backup
.with_bip_path(Bip44::new(IOTA_COIN_TYPE))
.finish()
.await?;

// Wrong password fails
restored_wallet
.restore_from_stronghold_snapshot(
PathBuf::from("test-storage/backup_and_restore/backup.stronghold"),
"wrong password".to_owned(),
None,
None,
)
.await
.unwrap_err();

// Correct password works, even after trying with a wrong one before
restored_wallet
.restore_from_stronghold_snapshot(
PathBuf::from("test-storage/backup_and_restore/backup.stronghold"),
stronghold_password,
None,
None,
)
.await?;

// Validate restored data

// Restored coin type is used
assert_eq!(restored_wallet.bip_path().await.unwrap().coin_type, SHIMMER_COIN_TYPE);

// compare restored client options
let client_options = restored_wallet.client_options().await;
let node_dto = NodeDto::Node(Node::from(Url::parse(NODE_LOCAL).unwrap()));
assert!(client_options.node_manager_builder.nodes.contains(&node_dto));

assert_eq!(wallet.address().await.clone(), restored_wallet.address().await.clone());

// secret manager is the same
assert_eq!(
wallet
.secret_manager()
.read()
.await
.generate_ed25519_addresses(GetAddressesOptions {
coin_type: SHIMMER_COIN_TYPE,
range: 0..1,
..Default::default()
})
.await?,
restored_wallet
.secret_manager()
.read()
.await
.generate_ed25519_addresses(GetAddressesOptions {
coin_type: SHIMMER_COIN_TYPE,
range: 0..1,
..Default::default()
})
.await?,
);
tear_down(storage_path)
}

// // Backup and restore with Stronghold and MnemonicSecretManager
// #[tokio::test]
Expand Down
Loading