Skip to content

Commit

Permalink
feat: add get_seed() for stronghold
Browse files Browse the repository at this point in the history
  • Loading branch information
Thoralf-M committed Dec 31, 2024
1 parent f8d31a9 commit d0b177e
Show file tree
Hide file tree
Showing 18 changed files with 471 additions and 59 deletions.
368 changes: 321 additions & 47 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions bindings/core/src/method/secret_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ pub enum SecretManagerMethod {
#[derivative(Debug(format_with = "OmittedDebug::omitted_fmt"))]
mnemonic: String,
},
/// Get the seed from the Stronghold vault, that was stored by `StoreMnemonic`.
#[cfg(feature = "stronghold")]
#[cfg_attr(docsrs, doc(cfg(feature = "stronghold")))]
GetSeed,
}

#[cfg(test)]
Expand Down
9 changes: 9 additions & 0 deletions bindings/core/src/method_handler/secret_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ pub(crate) async fn call_secret_manager_method_internal(
return Err(iota_sdk::client::Error::SecretManagerMismatch.into());
}
}
#[cfg(feature = "stronghold")]
SecretManagerMethod::GetSeed => {
if let SecretManager::Stronghold(secret_manager) = &*secret_manager {
let hex_seed = secret_manager.get_seed().await?;
Response::MnemonicHexSeed(hex_seed)
} else {
return Err(iota_sdk::client::Error::SecretManagerMismatch.into());
}
}
};
Ok(response)
}
1 change: 1 addition & 0 deletions bindings/core/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ pub enum Response {
/// - [`ParseBech32Address`](crate::method::UtilsMethod::ParseBech32Address)
ParsedBech32Address(AddressDto),
/// Response for:
/// - [`GetSeed`](crate::method::SecretManagerMethod::GetSeed)
/// - [`MnemonicToHexSeed`](crate::method::UtilsMethod::MnemonicToHexSeed)
MnemonicHexSeed(#[derivative(Debug(format_with = "OmittedDebug::omitted_fmt"))] String),
/// Response for:
Expand Down
6 changes: 6 additions & 0 deletions bindings/nodejs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Security -->

## 1.1.5 - 2025-01-07

### Added

- `SecretManager::getSeed()`;

## 1.1.5 - 2024-01-29

### Added
Expand Down
11 changes: 11 additions & 0 deletions bindings/nodejs/lib/secret_manager/secret-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,17 @@ export class SecretManager {
return JSON.parse(response).payload;
}

/**
* Get the seed from the Stronghold vault, that was stored by `storeMnemonic()`.
*/
async getSeed(): Promise<HexEncodedString> {
const response = await this.methodHandler.callMethod({
name: 'getSeed',
});

return JSON.parse(response).payload;
}

/**
* Sign a transaction.
*
Expand Down
2 changes: 2 additions & 0 deletions bindings/nodejs/lib/types/secret_manager/bridge/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
__GetLedgerNanoStatusMethod__,
__SignTransactionMethod__,
__StoreMnemonicMethod__,
__GetSeedMethod__,
__SignatureUnlockMethod__,
__SignEd25519Method__,
__SignSecp256k1EcdsaMethod__,
Expand All @@ -16,5 +17,6 @@ export type __SecretManagerMethods__ =
| __SignTransactionMethod__
| __SignatureUnlockMethod__
| __StoreMnemonicMethod__
| __GetSeedMethod__
| __SignEd25519Method__
| __SignSecp256k1EcdsaMethod__;
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ export interface __StoreMnemonicMethod__ {
};
}

export interface __GetSeedMethod__ {
name: 'getSeed';
}

export interface __SignEd25519Method__ {
name: 'signEd25519';
data: {
Expand Down
4 changes: 4 additions & 0 deletions bindings/nodejs/tests/wallet/wallet.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ describe('Wallet', () => {
});

expect(account.getMetadata().index).toStrictEqual(0);

const secretManager = await wallet.getSecretManager();
const seed = await secretManager.getSeed();
expect(seed).toStrictEqual("0x35c11ac650f8f00d5c66d1d0ac1d9a3ac536ed03d705931cce7275d08167091608fa7e09013bc1f3990fe3611c07a2685631b6dd25f49ac64b80132a35228d1a");

await wallet.destroy()
removeDir(storagePath)
Expand Down
6 changes: 6 additions & 0 deletions bindings/python/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Security -->

## 1.1.5 - 2025-01-07

### Added

- `SecretManager::get_seed()`;

## 1.1.4 - 2024-05-03

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion bindings/python/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "iota-sdk-python"
version = "1.1.4"
version = "1.1.5"
authors = ["IOTA Stiftung"]
edition = "2021"
description = "Python bindings for the IOTA SDK library"
Expand Down
5 changes: 5 additions & 0 deletions bindings/python/iota_sdk/secret_manager/secret_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,11 @@ def store_mnemonic(self, mnemonic: str):
'mnemonic': mnemonic
})

def get_seed(self) -> HexStr:
"""Get the seed from the Stronghold vault, that was stored by `StoreMnemonic`.
"""
return self._call_method('getSeed')

def sign_ed25519(self, message: HexStr, chain: Bip44) -> Ed25519Signature:
"""Signs a message with an Ed25519 private key.
Expand Down
2 changes: 1 addition & 1 deletion bindings/python/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def get_py_version_cfgs():

setup(
name="iota_sdk",
version="1.1.4",
version="1.1.5",
classifiers=[
"License :: SPDX-License-Identifier :: Apache-2.0",
"Intended Audience :: Developers",
Expand Down
34 changes: 34 additions & 0 deletions bindings/python/tests/test_wallet_stronghold.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Copyright 2024 IOTA Stiftung
# SPDX-License-Identifier: Apache-2.0

import shutil
import unittest
from iota_sdk import Wallet, StrongholdSecretManager, CoinType, ClientOptions


class WalletStronghold(unittest.TestCase):
def test_wallet_stronghold(self):
db_path = './test_wallet_stronghold'
shutil.rmtree(db_path, ignore_errors=True)

client_options = ClientOptions(nodes=[])

secret_manager = StrongholdSecretManager('./test_wallet_stronghold/wallet.stronghold',
'some_hopefully_secure_password')

wallet = Wallet(db_path,
client_options, CoinType.IOTA, secret_manager)
wallet.store_mnemonic(
"acoustic trophy damage hint search taste love bicycle foster cradle brown govern endless depend situate athlete pudding blame question genius transfer van random vast")

account = wallet.create_account('Alice')
addresses = account.addresses()
assert 'smr1qpg2xkj66wwgn8p2ggnp7p582gj8g6p79us5hve2tsudzpsr2ap4sp36wye' == addresses[
0].address

seed = wallet.get_secret_manager().get_seed()
assert seed == '0x65d378f26a101366d2b2bc982de128382f260205a8b99266fdcee14cc12f4680eb66171c27be01066c3ea30c9c0b87e27fb90f8cab9ac7b8e205f259d275240f'

# Destroy the wallet
wallet.destroy()
shutil.rmtree(db_path, ignore_errors=True)
6 changes: 6 additions & 0 deletions sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Security -->

## 1.1.6 - 2025-MM-DD

### Added

- `StrongholdAdapter::get_seed()`;

## 1.1.5 - 2024-05-22

### Added
Expand Down
3 changes: 2 additions & 1 deletion sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ futures = { version = "0.3.30", default-features = false, features = [
heck = { version = "0.4.1", default-features = false, optional = true }
instant = { version = "0.1.12", default-features = false, optional = true }
iota-ledger-nano = { version = "1.0.1", default-features = false, optional = true }
iota_stronghold = { version = "2.1.0", default-features = false, optional = true }
iota_stronghold = { git = "https://github.com/Thoralf-M/stronghold.rs", rev = "ccda33a7f52b3ebcd915cac6c5466409a12188ad", default-features = false, optional = true }
log = { version = "0.4.20", default-features = false, optional = true }
num_cpus = { version = "1.16.0", default-features = false, optional = true }
once_cell = { version = "1.19.0", default-features = false, optional = true }
Expand Down Expand Up @@ -182,6 +182,7 @@ storage = [
]
stronghold = [
"iota_stronghold",
"iota_stronghold?/insecure",
"iota-crypto/chacha",
"dep:time",
"dep:anymap",
Expand Down
27 changes: 20 additions & 7 deletions sdk/src/client/stronghold/secret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crypto::{
};
use instant::Duration;
use iota_stronghold::{
procedures::{self, Curve, KeyType, Slip10DeriveInput},
procedures::{self, Curve, GetSecret, KeyType, Slip10DeriveInput},
Location,
};

Expand Down Expand Up @@ -493,6 +493,21 @@ impl StrongholdAdapter {

Ok(())
}

/// Execute [GetSecret](procedures::GetSecret) procedure in Stronghold to get the hex encoded seed, that's stored
/// when calling `store_mnemonic()`.
pub async fn get_seed(&self) -> Result<String, Error> {
let client = self.stronghold.lock().await.get_client(PRIVATE_DATA_CLIENT_PATH)?;

let seed_location = Location::generic(SECRET_VAULT_PATH, SEED_RECORD_PATH);
let seed_bytes = client.execute_procedure(GetSecret {
location: seed_location,
})?;

let hex_encoded_seed = prefix_hex::encode(seed_bytes);

Ok(hex_encoded_seed)
}
}

#[cfg(test)]
Expand Down Expand Up @@ -592,12 +607,10 @@ mod tests {
stronghold_adapter.clear_key().await;

// Address generation returns an error when the key is cleared.
assert!(
stronghold_adapter
.generate_ed25519_addresses(IOTA_COIN_TYPE, 0, 0..1, None,)
.await
.is_err()
);
assert!(stronghold_adapter
.generate_ed25519_addresses(IOTA_COIN_TYPE, 0, 0..1, None,)
.await
.is_err());

stronghold_adapter.set_password("drowssap".to_owned()).await.unwrap();

Expand Down
36 changes: 34 additions & 2 deletions sdk/tests/client/secret_manager/stronghold.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
// Copyright 2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use crypto::keys::bip39::Mnemonic;
use iota_sdk::client::{
api::GetAddressesOptions, constants::SHIMMER_TESTNET_BECH32_HRP, secret::SecretManager, Result,
api::GetAddressesOptions,
constants::SHIMMER_TESTNET_BECH32_HRP,
secret::{stronghold::StrongholdSecretManager, SecretManager},
Result,
};
use iota_stronghold::engine::snapshot::try_set_encrypt_work_factor;
use pretty_assertions::assert_eq;

#[tokio::test]
Expand Down Expand Up @@ -58,7 +63,7 @@ async fn stronghold_mnemonic_missing() -> Result<()> {
// Cleanup of a possibly failed run
std::fs::remove_dir_all("stronghold_mnemonic_missing").ok();

let stronghold_secret_manager = iota_sdk::client::secret::stronghold::StrongholdSecretManager::builder()
let stronghold_secret_manager = StrongholdSecretManager::builder()
.password("some_hopefully_secure_password".to_owned())
.build("stronghold_mnemonic_missing/test.stronghold")?;

Expand All @@ -81,3 +86,30 @@ async fn stronghold_mnemonic_missing() -> Result<()> {
std::fs::remove_dir_all("stronghold_mnemonic_missing").ok();
Ok(())
}

#[tokio::test]
async fn stronghold_seed() -> Result<()> {
std::fs::remove_dir_all("stronghold_seed").ok();
try_set_encrypt_work_factor(0).unwrap();

let stronghold_secret_manager = StrongholdSecretManager::builder()
.password("some_hopefully_secure_password".to_owned())
.build("stronghold_seed/test.stronghold")?;

let mnemonic = Mnemonic::from(
"acoustic trophy damage hint search taste love bicycle foster cradle brown govern endless depend situate athlete pudding blame question genius transfer van random vast".to_owned(),
);

stronghold_secret_manager
.store_mnemonic(mnemonic.clone())
.await
.unwrap();
let hex_seed = stronghold_secret_manager.get_seed().await.unwrap();
assert_eq!(
hex_seed,
"0x65d378f26a101366d2b2bc982de128382f260205a8b99266fdcee14cc12f4680eb66171c27be01066c3ea30c9c0b87e27fb90f8cab9ac7b8e205f259d275240f"
);

std::fs::remove_dir_all("stronghold_seed").ok();
Ok(())
}

0 comments on commit d0b177e

Please sign in to comment.