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

Update Stronghold #691

Merged
merged 14 commits into from
Mar 9, 2022
5 changes: 3 additions & 2 deletions identity-account/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ identity-iota = { version = "=0.5.0-dev.4", path = "../identity-iota", default-f
itoa = { version = "0.4" }
log = { version = "0.4", default-features = false }
once_cell = { version = "1.7", default-features = false, features = ["std"] }
parking_lot = { version = "0.12" }
paste = { version = "1.0" }
serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] }
slog = { version = "2.7" }
Expand All @@ -36,12 +37,12 @@ features = ["blake2b", "ed25519", "hmac", "pbkdf", "sha", "slip10", "std"]

[dependencies.iota_stronghold]
git = "https://github.com/iotaledger/stronghold.rs"
rev = "aea8a9dc8c3fa12e5444c5b4bb3303876e4c1a2f"
rev = "969df405661ba4977f2cf30e9909cef7e30cefa2"
optional = true

[dependencies.stronghold_engine]
git = "https://github.com/iotaledger/stronghold.rs"
rev = "aea8a9dc8c3fa12e5444c5b4bb3303876e4c1a2f"
rev = "969df405661ba4977f2cf30e9909cef7e30cefa2"
optional = true

[dev-dependencies]
Expand Down
20 changes: 3 additions & 17 deletions identity-account/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

//! Errors that may occur when working with Identity Accounts.

use crate::stronghold::StrongholdError;

/// Alias for a `Result` with the error type [`Error`].
pub type Result<T, E = Error> = ::core::result::Result<T, E>;

Expand All @@ -27,25 +29,9 @@ pub enum Error {
/// Caused by attempting to perform an invalid IO operation.
#[error(transparent)]
IoError(#[from] std::io::Error),
/// Caused by errors from the [iota_stronghold] crate.
#[cfg(feature = "stronghold")]
#[error(transparent)]
StrongholdError(#[from] iota_stronghold::Error),
/// Caused by errors from an invalid Stronghold procedure.
#[error("Stronghold error: {0}")]
StrongholdResult(String),
/// Caused by attempting to parse an invalid Stronghold resource index.
#[error("Stronghold resource index malformed")]
InvalidResourceIndex,
/// Caused by attempting to access a Stronghold snapshot without a password.
#[error("Stronghold snapshot password not found")]
StrongholdPasswordNotSet,
/// Caused by receiving an unexpected return value from a Stronghold procedure.
#[error("Stronghold procedure returned unexpected type")]
StrongholdProcedureFailure,
/// Caused by an internal panic in the Stronghold runtime.
#[error("Stronghold mutex poisoned: {0}")]
StrongholdMutexPoisoned(&'static str),
StrongholdError(#[from] StrongholdError),
/// Caused by attempting to read a poisoned shared resource.
#[error("Shared resource poisoned: read")]
SharedReadPoisoned,
Expand Down
109 changes: 61 additions & 48 deletions identity-account/src/storage/stronghold.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright 2020-2021 IOTA Stiftung
// Copyright 2020-2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use crypto::keys::slip10::Chain;
use futures::executor;

use identity_core::convert::FromJson;
Expand All @@ -11,8 +10,12 @@ use identity_core::crypto::PublicKey;
use identity_did::did::DID;
use identity_did::verification::MethodType;
use identity_iota::did::IotaDID;
use iota_stronghold::procedures::Chain;
use iota_stronghold::procedures::Ed25519Sign;
use iota_stronghold::procedures::Slip10Derive;
use iota_stronghold::procedures::Slip10DeriveInput;
use iota_stronghold::procedures::Slip10Generate;
use iota_stronghold::Location;
use iota_stronghold::SLIP10DeriveInput;
use std::convert::TryFrom;
use std::io;
use std::path::Path;
Expand Down Expand Up @@ -79,11 +82,13 @@ impl Stronghold {
#[async_trait::async_trait]
impl Storage for Stronghold {
async fn set_password(&self, password: EncryptionKey) -> Result<()> {
self.snapshot.set_password(password).await
self.snapshot.set_password(password).await?;
Ok(())
}

async fn flush_changes(&self) -> Result<()> {
self.snapshot.save().await
self.snapshot.save().await?;
Ok(())
}

async fn key_new(&self, did: &IotaDID, location: &KeyLocation) -> Result<PublicKey> {
Expand Down Expand Up @@ -147,23 +152,21 @@ impl Storage for Stronghold {
async fn key_exists(&self, did: &IotaDID, location: &KeyLocation) -> Result<bool> {
let vault: Vault<'_> = self.vault(did);

match location.method() {
Ok(match location.method() {
MethodType::Ed25519VerificationKey2018 => vault.exists(location_skey(location)).await,
MethodType::MerkleKeyCollection2021 => todo!("[Stronghold::key_exists] Handle MerkleKeyCollection2021"),
}
}?)
}

async fn chain_state(&self, did: &IotaDID) -> Result<Option<ChainState>> {
// Load the chain-specific store
let store: Store<'_> = self.store(&fmt_did(did));
let data: Option<Vec<u8>> = store.get(location_chain_state()).await?;

let data: Vec<u8> = store.get(location_chain_state()).await?;

if data.is_empty() {
return Ok(None);
match data {
None => return Ok(None),
Some(data) => Ok(Some(ChainState::from_json_slice(&data)?)),
}

Ok(Some(ChainState::from_json_slice(&data)?))
}

async fn set_chain_state(&self, did: &IotaDID, chain_state: &ChainState) -> Result<()> {
Expand All @@ -182,15 +185,12 @@ impl Storage for Stronghold {
let store: Store<'_> = self.store(&fmt_did(did));

// Read the state from the stronghold snapshot
let data: Vec<u8> = store.get(location_state()).await?;
let data: Option<Vec<u8>> = store.get(location_state()).await?;

// No state data found
if data.is_empty() {
return Ok(None);
match data {
None => return Ok(None),
Some(data) => Ok(Some(IdentityState::from_json_slice(&data)?)),
}

// Deserialize and return
Ok(Some(IdentityState::from_json_slice(&data)?))
}

async fn set_state(&self, did: &IotaDID, state: &IdentityState) -> Result<()> {
Expand All @@ -216,23 +216,24 @@ impl Storage for Stronghold {

let bytes = store.get(location_published_generation()).await?;

if bytes.is_empty() {
return Ok(None);
match bytes {
None => return Ok(None),
Some(bytes) => {
let le_bytes: [u8; 4] = <[u8; 4]>::try_from(bytes.as_ref()).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidData,
format!(
"expected to read 4 bytes as the published generation, found {} instead",
bytes.len()
),
)
})?;

let gen = Generation::from_u32(u32::from_le_bytes(le_bytes));

Ok(Some(gen))
}
}

let le_bytes: [u8; 4] = <[u8; 4]>::try_from(bytes.as_ref()).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidData,
format!(
"expected to read 4 bytes as the published generation, found {} instead",
bytes.len()
),
)
})?;

let gen = Generation::from_u32(u32::from_le_bytes(le_bytes));

Ok(Some(gen))
}

async fn set_published_generation(&self, did: &IotaDID, index: Generation) -> Result<()> {
Expand All @@ -256,32 +257,44 @@ impl Drop for Stronghold {

async fn generate_ed25519(vault: &Vault<'_>, location: &KeyLocation) -> Result<PublicKey> {
// Generate a SLIP10 seed as the private key
vault
.slip10_generate(location_seed(location), default_hint(), None)
.await?;
let procedure: Slip10Generate = Slip10Generate {
output: location_seed(location),
hint: default_hint(),
size_bytes: None,
};
vault.execute(procedure).await?;

let chain: Chain = Chain::from_u32_hardened(vec![0, 0, 0]);
let seed: SLIP10DeriveInput = SLIP10DeriveInput::Seed(location_seed(location));
let seed: Slip10DeriveInput = Slip10DeriveInput::Seed(location_seed(location));

// Use the SLIP10 seed to derive a child key
vault
.slip10_derive(chain, seed, location_skey(location), default_hint())
.await?;
let procedure: Slip10Derive = Slip10Derive {
chain,
input: seed,
output: location_skey(location),
hint: default_hint(),
};
vault.execute(procedure).await?;

// Retrieve the public key of the derived child key
retrieve_ed25519(vault, location).await
}

async fn retrieve_ed25519(vault: &Vault<'_>, location: &KeyLocation) -> Result<PublicKey> {
vault
.ed25519_public_key(location_skey(location))
.await
.map(|public| public.to_vec().into())
let procedure: iota_stronghold::procedures::PublicKey = iota_stronghold::procedures::PublicKey {
ty: iota_stronghold::procedures::KeyType::Ed25519,
private_key: location_skey(location),
};
Ok(vault.execute(procedure).await.map(|public| public.to_vec().into())?)
}

async fn sign_ed25519(vault: &Vault<'_>, payload: Vec<u8>, location: &KeyLocation) -> Result<Signature> {
let public_key: PublicKey = retrieve_ed25519(vault, location).await?;
let signature: [u8; 64] = vault.ed25519_sign(payload, location_skey(location)).await?;
let procedure: Ed25519Sign = Ed25519Sign {
private_key: location_skey(location),
msg: payload,
};
let signature: [u8; 64] = vault.execute(procedure).await?;

Ok(Signature::new(public_key, signature.into()))
}
Expand Down
Loading