diff --git a/bindings/stronghold-nodejs/Cargo.lock b/bindings/stronghold-nodejs/Cargo.lock index 989f3b1093..cfd7a20ea0 100644 --- a/bindings/stronghold-nodejs/Cargo.lock +++ b/bindings/stronghold-nodejs/Cargo.lock @@ -134,7 +134,7 @@ dependencies = [ "bee-pow", "bee-ternary", "bytemuck", - "digest", + "digest 0.9.0", "hex", "iota-crypto 0.9.1", "serde", @@ -185,7 +185,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a4e37d16930f5459780f5621038b6382b9bb37c19016f39fb6b5808d831f174" dependencies = [ "crypto-mac 0.8.0", - "digest", + "digest 0.9.0", "opaque-debug", ] @@ -198,6 +198,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] + [[package]] name = "bumpalo" version = "3.10.0" @@ -297,6 +306,16 @@ dependencies = [ "libc", ] +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "crypto-mac" version = "0.8.0" @@ -343,7 +362,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" dependencies = [ "byteorder", - "digest", + "digest 0.9.0", "rand_core 0.5.1", "subtle", "zeroize", @@ -394,6 +413,17 @@ dependencies = [ "generic-array", ] +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer 0.10.2", + "crypto-common", + "subtle", +] + [[package]] name = "dirs" version = "4.0.0" @@ -444,10 +474,24 @@ dependencies = [ "curve25519-dalek", "hex", "rand_core 0.5.1", - "sha2", + "sha2 0.9.9", "thiserror", ] +[[package]] +name = "ed25519-zebra" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403ef3e961ab98f0ba902771d29f842058578bb1ce7e3c59dad5a6a93e784c69" +dependencies = [ + "curve25519-dalek", + "hex", + "rand_core 0.6.3", + "sha2 0.9.9", + "thiserror", + "zeroize", +] + [[package]] name = "fern" version = "0.6.1" @@ -634,12 +678,11 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hkdf" -version = "0.11.0" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" dependencies = [ - "digest", - "hmac", + "hmac 0.12.1", ] [[package]] @@ -649,12 +692,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" dependencies = [ "crypto-mac 0.11.1", - "digest", + "digest 0.9.0", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.3", ] [[package]] name = "identity-diff" -version = "0.5.0" +version = "0.6.0" dependencies = [ "serde", "serde_json", @@ -664,7 +716,7 @@ dependencies = [ [[package]] name = "identity-stronghold-nodejs" -version = "0.5.0" +version = "0.6.0" dependencies = [ "identity_account_storage", "identity_core", @@ -678,7 +730,7 @@ dependencies = [ [[package]] name = "identity_account_storage" -version = "0.5.0" +version = "0.6.0" dependencies = [ "async-trait", "futures", @@ -701,7 +753,7 @@ dependencies = [ [[package]] name = "identity_core" -version = "0.5.0" +version = "0.6.0" dependencies = [ "identity-diff", "iota-crypto 0.8.0", @@ -719,7 +771,7 @@ dependencies = [ [[package]] name = "identity_did" -version = "0.5.0" +version = "0.6.0" dependencies = [ "did_url", "form_urlencoded", @@ -732,7 +784,7 @@ dependencies = [ [[package]] name = "identity_iota_core" -version = "0.5.0" +version = "0.6.0" dependencies = [ "bee-message", "identity_core", @@ -778,15 +830,13 @@ dependencies = [ "blake2", "chacha20poly1305", "curve25519-dalek", - "digest", - "ed25519-zebra", + "digest 0.9.0", + "ed25519-zebra 2.2.0", "generic-array", "getrandom 0.2.6", - "hmac", - "pbkdf2", - "serde", - "sha2", - "unicode-normalization", + "hmac 0.11.0", + "pbkdf2 0.8.0", + "sha2 0.9.9", "x25519-dalek", ] @@ -798,20 +848,44 @@ checksum = "8c98a3248cde6b42cb479a52089fe4fc20d12de51b8edd923294cd5240ec89b0" dependencies = [ "bee-ternary", "blake2", - "digest", - "ed25519-zebra", + "digest 0.9.0", + "ed25519-zebra 2.2.0", "lazy_static", ] +[[package]] +name = "iota-crypto" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f03717e934972fad6f1c9b4cd25f662e1753b58a7f76e3dceadeb646e034b252" +dependencies = [ + "aead", + "aes", + "aes-gcm", + "chacha20poly1305", + "curve25519-dalek", + "digest 0.10.3", + "ed25519-zebra 3.0.0", + "generic-array", + "getrandom 0.2.6", + "hmac 0.12.1", + "pbkdf2 0.11.0", + "serde", + "sha2 0.10.2", + "unicode-normalization", + "x25519-dalek", + "zeroize", +] + [[package]] name = "iota_stronghold" -version = "0.5.1" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "273197e986052cac7fed586142e40497ffec2e3536a7416d23bf694f90068b96" +checksum = "89ebdd443fde5c02437469c1a318aa4d56cba83db3913743862a0e3b54ab5736" dependencies = [ "bincode", "hkdf", - "iota-crypto 0.8.0", + "iota-crypto 0.12.1", "serde", "stronghold-derive", "stronghold-rlu", @@ -1051,6 +1125,15 @@ dependencies = [ "crypto-mac 0.11.1", ] +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest 0.10.3", +] + [[package]] name = "percent-encoding" version = "2.1.0" @@ -1285,13 +1368,24 @@ version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ - "block-buffer", + "block-buffer 0.9.0", "cfg-if", "cpufeatures 0.2.2", - "digest", + "digest 0.9.0", "opaque-debug", ] +[[package]] +name = "sha2" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.2", + "digest 0.10.3", +] + [[package]] name = "slab" version = "0.4.6" @@ -1351,9 +1445,9 @@ dependencies = [ [[package]] name = "stronghold-utils" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d8493a083506300a7a112d927f48068e4c733f0b46c63569dea6ab6dfe175c" +checksum = "be1ce0d6205e8ea89771f2b32d64aeaecbc930320919e95ec4efcc0aa96cd951" dependencies = [ "rand", "stronghold-derive", diff --git a/bindings/stronghold-nodejs/js/stronghold.ts b/bindings/stronghold-nodejs/js/stronghold.ts index 329211140f..2a463d3dd5 100644 --- a/bindings/stronghold-nodejs/js/stronghold.ts +++ b/bindings/stronghold-nodejs/js/stronghold.ts @@ -1,4 +1,4 @@ -import { NapiStronghold, NapiDid, NapiKeyLocation, NapiChainState, NapiDocument, NapiKeyType, NapiDidLocation, NapiEncryptedData, NapiEncryptionAlgorithm, NapiCekAlgorithm } from '../napi-dist/napi'; +import { NapiStronghold, NapiDid, NapiKeyLocation, NapiKeyType, NapiDidLocation, NapiEncryptedData, NapiEncryptionAlgorithm, NapiCekAlgorithm } from '../napi-dist/napi'; import { DID, KeyLocation, Signature, ChainState, Storage, KeyType, Document, EncryptedData, EncryptionAlgorithm, CekAlgorithm } from "@iota/identity-wasm/node"; export class Stronghold implements Storage { @@ -115,38 +115,20 @@ export class Stronghold implements Storage { return Uint8Array.from(decryptedData); } - public async chainStateGet(did: DID): Promise { + public async blobGet(did: DID): Promise { const napiDID: NapiDid = NapiDid.fromJSON(did.toJSON()); - const napiChainState: NapiChainState | undefined = await this.napiStronghold.chainStateGet(napiDID); + const value: number[] | undefined = await this.napiStronghold.blobGet(napiDID); - if (napiChainState) { - return ChainState.fromJSON(napiChainState.toJSON()) + if (value) { + return Uint8Array.from(value) } else { return undefined; } } - public async chainStateSet(did: DID, chainState: ChainState): Promise { + public async blobSet(did: DID, value: Uint8Array): Promise { const napiDID: NapiDid = NapiDid.fromJSON(did.toJSON()); - const napiChainState: NapiChainState = NapiChainState.fromJSON(chainState.toJSON()); - return this.napiStronghold.chainStateSet(napiDID, napiChainState); - } - - public async documentGet(did: DID): Promise { - const napiDID: NapiDid = NapiDid.fromJSON(did.toJSON()); - const napiDocument: NapiDocument | undefined = await this.napiStronghold.documentGet(napiDID); - - if (napiDocument) { - return Document.fromJSON(napiDocument.toJSON()) - } else { - return undefined; - } - } - - public async documentSet(did: DID, document: Document): Promise { - const napiDID: NapiDid = NapiDid.fromJSON(did.toJSON()); - const napiDocument: NapiDocument = NapiDocument.fromJSON(document.toJSON()); - return this.napiStronghold.documentSet(napiDID, napiDocument); + return this.napiStronghold.blobSet(napiDID, Array.from(value)); } public async flushChanges() { diff --git a/bindings/stronghold-nodejs/src/stronghold.rs b/bindings/stronghold-nodejs/src/stronghold.rs index 30e32ab789..5a35dfcd5a 100644 --- a/bindings/stronghold-nodejs/src/stronghold.rs +++ b/bindings/stronghold-nodejs/src/stronghold.rs @@ -15,10 +15,8 @@ use napi_derive::napi; use crate::error::NapiResult; use crate::types::NapiCekAlgorithm; -use crate::types::NapiChainState; use crate::types::NapiDid; use crate::types::NapiDidLocation; -use crate::types::NapiDocument; use crate::types::NapiEncryptedData; use crate::types::NapiEncryptionAlgorithm; use crate::types::NapiKeyLocation; @@ -236,38 +234,22 @@ impl NapiStronghold { Ok(data.into_iter().map(u32::from).collect()) } - /// Returns the chain state of the identity specified by `did`. + /// Returns the blob stored by the identity specified by `did`. #[napi] - pub async fn chain_state_get(&self, did: &NapiDid) -> Result> { + pub async fn blob_get(&self, did: &NapiDid) -> Result>> { self .0 - .chain_state_get(&did.0) + .blob_get(&did.0) .await .napi_result() - .map(|opt_chain_state| opt_chain_state.map(|chain_state| chain_state.into())) + .map(|opt_value| opt_value.map(|value| value.into_iter().map(u32::from).collect())) } - /// Set the chain state of the identity specified by `did`. + /// Stores an arbitrary blob for the identity specified by `did`. #[napi] - pub async fn chain_state_set(&self, did: &NapiDid, chain_state: &NapiChainState) -> Result<()> { - self.0.chain_state_set(&did.0, &chain_state.0).await.napi_result() - } - - /// Returns the document of the identity specified by `did`. - #[napi] - pub async fn document_get(&self, did: &NapiDid) -> Result> { - self - .0 - .document_get(&did.0) - .await - .napi_result() - .map(|opt_document| opt_document.map(|doc| doc.into())) - } - - /// Sets a new state for the identity specified by `did`. - #[napi] - pub async fn document_set(&self, did: &NapiDid, state: &NapiDocument) -> Result<()> { - self.0.document_set(&did.0, &state.0).await.napi_result() + pub async fn blob_set(&self, did: &NapiDid, blob: Vec) -> Result<()> { + let blob: Vec = blob.try_into_bytes()?; + self.0.blob_set(&did.0, blob).await.napi_result() } /// Persists any unsaved changes. diff --git a/bindings/wasm/examples-account/src/custom_storage.ts b/bindings/wasm/examples-account/src/custom_storage.ts index cf6b58864d..b0b3b2856d 100644 --- a/bindings/wasm/examples-account/src/custom_storage.ts +++ b/bindings/wasm/examples-account/src/custom_storage.ts @@ -1,7 +1,7 @@ // Copyright 2020-2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { ChainState, DID, Document, Ed25519, KeyLocation, KeyPair, KeyType, Signature, Storage, StorageTestSuite, EncryptionAlgorithm, CekAlgorithm, EncryptedData } from '../../node/identity_wasm.js'; +import { DID, Ed25519, KeyLocation, KeyPair, KeyType, Signature, Storage, StorageTestSuite, EncryptionAlgorithm, CekAlgorithm, EncryptedData } from '../../node/identity_wasm.js'; /** An insecure, in-memory `Storage` implementation that serves as an example. This can be passed to the `AccountBuilder` to create accounts with this as the storage. */ @@ -10,17 +10,14 @@ export class MemStore implements Storage { // We use strings as keys rather than DIDs or KeyLocations because Maps use // referential equality for object keys, and thus a primitive type needs to be used instead. - // The map from DIDs to chain states. - private _chainStates: Map; - // The map from DIDs to DID documents. - private _documents: Map; - // The map from DIDs to vaults. + // The map from DIDs to state. + private _blobs: Map; + // Map of DID state blobs. private _vaults: Map>; /** Creates a new, empty `MemStore` instance. */ constructor() { - this._chainStates = new Map(); - this._documents = new Map(); + this._blobs = new Map(); this._vaults = new Map(); } @@ -67,8 +64,7 @@ export class MemStore implements Storage { // so we only need to do work if the DID still exists. // The return value signals whether the DID was actually removed during this operation. if (this._vaults.has(did.toString())) { - this._chainStates.delete(did.toString()); - this._documents.delete(did.toString()); + this._blobs.delete(did.toString()); this._vaults.delete(did.toString()); return true; } @@ -197,24 +193,14 @@ export class MemStore implements Storage { throw new Error('not yet implemented') } - public async chainStateGet(did: DID): Promise { - // Lookup the chain state of the given DID. - return this._chainStates.get(did.toString()); + public async blobGet(did: DID): Promise { + // Lookup the state of the given DID. + return this._blobs.get(did.toString()); } - public async chainStateSet(did: DID, chainState: ChainState): Promise { - // Set the chain state of the given DID. - this._chainStates.set(did.toString(), chainState); - } - - public async documentGet(did: DID): Promise { - // Lookup the DID document of the given DID. - return this._documents.get(did.toString()) - } - - public async documentSet(did: DID, document: Document): Promise { - // Set the DID document of the given DID. - this._documents.set(did.toString(), document); + public async blobSet(did: DID, value: Uint8Array): Promise { + // Set the state of the given DID. + this._blobs.set(did.toString(), value); } public async flushChanges(): Promise { diff --git a/bindings/wasm/src/account/storage/traits.rs b/bindings/wasm/src/account/storage/traits.rs index aec23609f8..c9f067d753 100644 --- a/bindings/wasm/src/account/storage/traits.rs +++ b/bindings/wasm/src/account/storage/traits.rs @@ -5,7 +5,6 @@ use core::fmt::Debug; use core::fmt::Formatter; use identity_iota::account_storage::CekAlgorithm; -use identity_iota::account_storage::ChainState; use identity_iota::account_storage::EncryptedData; use identity_iota::account_storage::EncryptionAlgorithm; use identity_iota::account_storage::Error as AccountStorageError; @@ -17,7 +16,6 @@ use identity_iota::crypto::PrivateKey; use identity_iota::crypto::PublicKey; use identity_iota::iota_core::IotaDID; use identity_iota::iota_core::NetworkName; -use identity_iota::prelude::IotaDocument; use identity_iota::prelude::KeyType; use js_sys::Array; use js_sys::Promise; @@ -26,7 +24,6 @@ use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; use wasm_bindgen_futures::JsFuture; -use crate::account::identity::WasmChainState; use crate::account::types::WasmCekAlgorithm; use crate::account::types::WasmEncryptedData; use crate::account::types::WasmEncryptionAlgorithm; @@ -35,7 +32,6 @@ use crate::common::PromiseBool; use crate::common::PromiseVoid; use crate::crypto::WasmKeyType; use crate::did::WasmDID; -use crate::did::WasmDocument; use crate::error::JsValueResult; #[wasm_bindgen] @@ -44,10 +40,6 @@ extern "C" { pub type PromisePublicKey; #[wasm_bindgen(typescript_type = "Promise")] pub type PromiseSignature; - #[wasm_bindgen(typescript_type = "Promise")] - pub type PromiseOptionChainState; - #[wasm_bindgen(typescript_type = "Promise")] - pub type PromiseOptionDocument; #[wasm_bindgen(typescript_type = "Promise")] pub type PromiseKeyLocation; #[wasm_bindgen(typescript_type = "Promise>")] @@ -58,6 +50,8 @@ extern "C" { pub type PromiseEncryptedData; #[wasm_bindgen(typescript_type = "Promise")] pub type PromiseData; + #[wasm_bindgen(typescript_type = "Promise")] + pub type PromiseOptionBytes; } #[wasm_bindgen] @@ -111,14 +105,10 @@ extern "C" { cek_algorithm: WasmCekAlgorithm, private_key: WasmKeyLocation, ) -> Uint8Array; - #[wasm_bindgen(method, js_name = chainStateGet)] - pub fn chain_state_get(this: &WasmStorage, did: WasmDID) -> PromiseOptionChainState; - #[wasm_bindgen(method, js_name = chainStateSet)] - pub fn chain_state_set(this: &WasmStorage, did: WasmDID, chain_state: WasmChainState) -> PromiseVoid; - #[wasm_bindgen(method, js_name = documentGet)] - pub fn document_get(this: &WasmStorage, did: WasmDID) -> PromiseOptionDocument; - #[wasm_bindgen(method, js_name = documentSet)] - pub fn document_set(this: &WasmStorage, did: WasmDID, document: WasmDocument) -> PromiseVoid; + #[wasm_bindgen(method, js_name = blobGet)] + pub fn blob_get(this: &WasmStorage, did: WasmDID) -> PromiseOptionBytes; + #[wasm_bindgen(method, js_name = blobSet)] + pub fn blob_set(this: &WasmStorage, did: WasmDID, blob: Vec) -> PromiseVoid; #[wasm_bindgen(method, js_name = flushChanges)] pub fn flush_changes(this: &WasmStorage) -> PromiseVoid; } @@ -286,40 +276,19 @@ impl Storage for WasmStorage { Ok(data) } - async fn chain_state_get(&self, did: &IotaDID) -> AccountStorageResult> { - let promise: Promise = Promise::resolve(&self.chain_state_get(did.clone().into())); + async fn blob_get(&self, did: &IotaDID) -> AccountStorageResult>> { + let promise: Promise = Promise::resolve(&self.blob_get(did.clone().into())); let result: JsValueResult = JsFuture::from(promise).await.into(); let js_value: JsValue = result.to_account_error()?; if js_value.is_null() || js_value.is_undefined() { return Ok(None); } - let chain_state: ChainState = js_value - .into_serde() - .map_err(|err| AccountStorageError::SerializationError(err.to_string()))?; - Ok(Some(chain_state)) + let value: Vec = uint8array_to_bytes(js_value)?; + Ok(Some(value)) } - async fn chain_state_set(&self, did: &IotaDID, chain_state: &ChainState) -> AccountStorageResult<()> { - let promise: Promise = Promise::resolve(&self.chain_state_set(did.clone().into(), chain_state.clone().into())); - let result: JsValueResult = JsFuture::from(promise).await.into(); - result.into() - } - - async fn document_get(&self, did: &IotaDID) -> AccountStorageResult> { - let promise: Promise = Promise::resolve(&self.document_get(did.clone().into())); - let result: JsValueResult = JsFuture::from(promise).await.into(); - let js_value: JsValue = result.to_account_error()?; - if js_value.is_null() || js_value.is_undefined() { - return Ok(None); - } - let document: IotaDocument = js_value - .into_serde() - .map_err(|err| AccountStorageError::SerializationError(err.to_string()))?; - Ok(Some(document)) - } - - async fn document_set(&self, did: &IotaDID, document: &IotaDocument) -> AccountStorageResult<()> { - let promise: Promise = Promise::resolve(&self.document_set(did.clone().into(), document.clone().into())); + async fn blob_set(&self, did: &IotaDID, blob: Vec) -> AccountStorageResult<()> { + let promise: Promise = Promise::resolve(&self.blob_set(did.clone().into(), blob)); let result: JsValueResult = JsFuture::from(promise).await.into(); result.into() } @@ -416,17 +385,11 @@ interface Storage { */ dataDecrypt: (did: DID, data: EncryptedData, encryptionAlgorithm: EncryptionAlgorithm, cekAlgorithm: CekAlgorithm, privateKey: KeyLocation) => Promise; - /** Returns the chain state of the identity specified by `did`. */ - chainStateGet: (did: DID) => Promise; - - /** Set the chain state of the identity specified by `did`. */ - chainStateSet: (did: DID, chainState: ChainState) => Promise; - - /** Returns the document of the identity specified by `did`. */ - documentGet: (did: DID) => Promise; + /** Returns the blob stored by the identity specified by `did`. */ + blobGet: (did: DID) => Promise; - /** Sets a new state for the identity specified by `did`. */ - documentSet: (did: DID, document: Document) => Promise; + /** Stores an arbitrary blob for the identity specified by `did`. */ + blobSet: (did: DID, blob: Uint8Array) => Promise; /** Persists any unsaved changes. */ flushChanges: () => Promise; diff --git a/documentation/docs/libraries/rust/getting_started.md b/documentation/docs/libraries/rust/getting_started.md index ecd62a51d5..67f46ca233 100644 --- a/documentation/docs/libraries/rust/getting_started.md +++ b/documentation/docs/libraries/rust/getting_started.md @@ -10,8 +10,8 @@ keywords: ## Requirements -- [Rust](https://www.rust-lang.org/) (>= 1.60) -- [Cargo](https://doc.rust-lang.org/cargo/) (>= 1.60) +- [Rust](https://www.rust-lang.org/) (>= 1.62) +- [Cargo](https://doc.rust-lang.org/cargo/) (>= 1.62) ## Include the Library diff --git a/identity_account/src/account/account.rs b/identity_account/src/account/account.rs index 5365a783bc..03f5964df6 100644 --- a/identity_account/src/account/account.rs +++ b/identity_account/src/account/account.rs @@ -12,6 +12,8 @@ use identity_account_storage::crypto::RemoteKey; use identity_account_storage::identity::ChainState; use identity_account_storage::storage::Storage; use identity_account_storage::types::KeyLocation; +use identity_core::convert::FromJson; +use identity_core::convert::ToJson; use identity_core::crypto::KeyType; use identity_core::crypto::ProofOptions; use identity_core::crypto::SetSignature; @@ -32,6 +34,7 @@ use serde::Serialize; use crate::account::AccountBuilder; use crate::account::PublishOptions; use crate::types::IdentitySetup; +use crate::types::IdentityState; use crate::types::IdentityUpdater; use crate::updates::create_identity; use crate::updates::Update; @@ -126,12 +129,14 @@ where } // Ensure the identity exists in storage - let document: IotaDocument = setup.storage.document_get(&did).await?.ok_or(Error::IdentityNotFound)?; - let chain_state: ChainState = setup - .storage - .chain_state_get(&did) - .await? - .ok_or(Error::IdentityNotFound)?; + let identity_state_bytes: Vec = setup.storage.blob_get(&did).await?.ok_or(Error::IdentityNotFound)?; + let identity_state: IdentityState = IdentityState::from_json_slice(&identity_state_bytes)?; + let chain_state: ChainState = identity_state + .chain_state()? + .ok_or_else(|| Error::InvalidIdentityState("missing chain state".to_owned()))?; + let document: IotaDocument = identity_state + .document()? + .ok_or_else(|| Error::InvalidIdentityState("missing document".to_owned()))?; Self::with_setup(setup, chain_state, document).await } @@ -316,12 +321,17 @@ where // TODO: An account always holds a valid identity, // so if None is returned, that's a broken invariant. // This should be mapped to a fatal error in the future. - self + let identity_state_bytes: Vec = self .storage() .deref() - .document_get(self.did()) + .blob_get(self.did()) .await? - .ok_or(Error::IdentityNotFound) + .ok_or(Error::IdentityNotFound)?; + let identity_state: IdentityState = IdentityState::from_json_slice(&identity_state_bytes)?; + + identity_state + .document()? + .ok_or_else(|| Error::InvalidIdentityState("document not found".to_owned())) } pub(crate) async fn process_update(&mut self, update: Update) -> Result<()> { @@ -410,8 +420,8 @@ where } async fn store_state(&self) -> Result<()> { - self.storage.document_set(self.did(), &self.document).await?; - self.storage.chain_state_set(self.did(), self.chain_state()).await?; + let identity_state: IdentityState = IdentityState::new(Some(&self.document), Some(&self.chain_state))?; + self.storage.blob_set(self.did(), identity_state.to_json_vec()?).await?; self.save(false).await?; diff --git a/identity_account/src/error.rs b/identity_account/src/error.rs index 369f95411d..ad08000fc8 100644 --- a/identity_account/src/error.rs +++ b/identity_account/src/error.rs @@ -36,6 +36,9 @@ pub enum Error { /// Caused by verification methods without fragments. #[error("method missing fragment")] MethodMissingFragment, + /// Caused by reaching an invalid state for the identity. + #[error("invalid identity state: {0}")] + InvalidIdentityState(String), } impl From for Error { diff --git a/identity_account/src/tests/updates.rs b/identity_account/src/tests/updates.rs index 3ac3b0a893..0bf57b726d 100644 --- a/identity_account/src/tests/updates.rs +++ b/identity_account/src/tests/updates.rs @@ -9,6 +9,7 @@ use identity_core::common::OneOrSet; use identity_core::common::OrderedSet; use identity_core::common::Timestamp; use identity_core::common::Url; +use identity_core::convert::FromJson; use identity_core::crypto::KeyPair; use identity_core::crypto::KeyType; use identity_core::crypto::PrivateKey; @@ -31,6 +32,7 @@ use crate::account::AccountSetup; use crate::error::Error; use crate::error::Result; use crate::types::IdentitySetup; +use crate::types::IdentityState; use crate::types::MethodContent; use crate::updates::Update; use crate::updates::UpdateError; @@ -131,12 +133,8 @@ async fn test_create_identity_already_exists() -> Result<()> { .await .unwrap(); - let initial_state = account_setup - .storage - .document_get(account.did()) - .await - .unwrap() - .unwrap(); + let initial_state: Vec = account_setup.storage.blob_get(account.did()).await?.unwrap(); + let initial_state: IdentityState = IdentityState::from_json_slice(&initial_state).unwrap(); let output = Account::create_identity(account_setup.clone(), identity_create).await; @@ -146,10 +144,9 @@ async fn test_create_identity_already_exists() -> Result<()> { )); // Ensure nothing was overwritten in storage - assert_eq!( - initial_state, - account_setup.storage.document_get(account.did()).await?.unwrap() - ); + let account_state: Vec = account_setup.storage.blob_get(account.did()).await?.unwrap(); + let account_state: IdentityState = IdentityState::from_json_slice(&account_state).unwrap(); + assert_eq!(initial_state.document()?, account_state.document()?); } Ok(()) } diff --git a/identity_account/src/types/identity_state.rs b/identity_account/src/types/identity_state.rs new file mode 100644 index 0000000000..26299cdc95 --- /dev/null +++ b/identity_account/src/types/identity_state.rs @@ -0,0 +1,58 @@ +// Copyright 2020-2022 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_account_storage::identity::ChainState; +use identity_core::convert::FromJson; +use identity_core::convert::ToJson; +use identity_iota_core::document::IotaDocument; +use serde::Deserialize; +use serde::Serialize; + +use crate::error::Result; + +/// Holds the internal state for the identity. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub(crate) struct IdentityState { + version: StateVersion, + document: Option>, + chain_state: Option>, +} + +impl IdentityState { + /// Creates a new [`IdentityState`]. + pub(crate) fn new(document: Option<&IotaDocument>, chain_state: Option<&ChainState>) -> Result { + let document: Option> = document.map(|iota_doc| iota_doc.to_json_vec()).transpose()?; + let chain_state: Option> = chain_state.map(|chain_state| chain_state.to_json_vec()).transpose()?; + Ok(IdentityState { + version: StateVersion::default(), + document, + chain_state, + }) + } + + /// Returns the deserialized [`IotaDocument`]. + pub(crate) fn document(&self) -> Result> { + match self.version { + StateVersion::V1 => Ok(self.document.as_ref().map(IotaDocument::from_json_slice).transpose()?), + } + } + + /// Returns the deserialized [`ChainState`]. + pub(crate) fn chain_state(&self) -> Result> { + match self.version { + StateVersion::V1 => Ok(self.chain_state.as_ref().map(ChainState::from_json_slice).transpose()?), + } + } + + #[allow(dead_code)] + /// Returns the [`StateVersion`] of the [`IdentityState`]. + pub(crate) fn version(&self) -> StateVersion { + self.version + } +} + +#[derive(Copy, Clone, Debug, Default, PartialEq, Deserialize, Serialize)] +pub(crate) enum StateVersion { + #[default] + V1 = 1, +} diff --git a/identity_account/src/types/mod.rs b/identity_account/src/types/mod.rs index 79211b0f20..b801500afa 100644 --- a/identity_account/src/types/mod.rs +++ b/identity_account/src/types/mod.rs @@ -2,9 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 pub use self::identity_setup::*; +pub(crate) use self::identity_state::IdentityState; pub use self::identity_updater::*; pub use self::method_content::*; mod identity_setup; +mod identity_state; mod identity_updater; mod method_content; diff --git a/identity_account_storage/src/storage/memstore.rs b/identity_account_storage/src/storage/memstore.rs index ef27a591d2..23b19f0b5d 100644 --- a/identity_account_storage/src/storage/memstore.rs +++ b/identity_account_storage/src/storage/memstore.rs @@ -21,7 +21,6 @@ use identity_core::crypto::Sign; #[cfg(feature = "encryption")] use identity_core::crypto::X25519; use identity_iota_core::did::IotaDID; -use identity_iota_core::document::IotaDocument; use identity_iota_core::tangle::NetworkName; use std::sync::RwLockReadGuard; use std::sync::RwLockWriteGuard; @@ -29,7 +28,6 @@ use zeroize::Zeroize; use crate::error::Error; use crate::error::Result; -use crate::identity::ChainState; use crate::storage::Storage; #[cfg(feature = "encryption")] use crate::types::CekAlgorithm; @@ -41,10 +39,6 @@ use crate::types::KeyLocation; use crate::types::Signature; use crate::utils::Shared; -// The map from DIDs to chain states. -type ChainStates = HashMap; -// The map from DIDs to DID documents. -type Documents = HashMap; // The map from DIDs to vaults. type Vaults = HashMap; // The map from key locations to key pairs, that lives within a DID partition. @@ -54,9 +48,7 @@ type MemVault = HashMap; pub struct MemStore { // Controls whether to print the storages content when debugging. expand: bool, - // The `Shared` type is simply a light wrapper around `Rwlock`. - chain_states: Shared, - documents: Shared, + blobs: Shared>>, vaults: Shared, } @@ -65,8 +57,7 @@ impl MemStore { pub fn new() -> Self { Self { expand: false, - chain_states: Shared::new(HashMap::new()), - documents: Shared::new(HashMap::new()), + blobs: Shared::new(HashMap::new()), vaults: Shared::new(HashMap::new()), } } @@ -132,9 +123,7 @@ impl Storage for MemStore { // so we only need to do work if the DID still exists. // The return value signals whether the DID was actually removed during this operation. if self.vaults.write()?.remove(did).is_some() { - let _ = self.documents.write()?.remove(did); - let _ = self.chain_states.write()?.remove(did); - + let _ = self.blobs.write()?.remove(did); Ok(true) } else { Ok(false) @@ -381,28 +370,16 @@ impl Storage for MemStore { } } - async fn chain_state_get(&self, did: &IotaDID) -> Result> { - // Lookup the chain state of the given DID. - self.chain_states.read().map(|states| states.get(did).cloned()) - } - - async fn chain_state_set(&self, did: &IotaDID, chain_state: &ChainState) -> Result<()> { - // Set the chain state of the given DID. - self.chain_states.write()?.insert(did.clone(), chain_state.clone()); + async fn blob_set(&self, did: &IotaDID, value: Vec) -> Result<()> { + // Set the arbitrary value for the given DID. + self.blobs.write()?.insert(did.clone(), value); Ok(()) } - async fn document_get(&self, did: &IotaDID) -> Result> { - // Lookup the DID document of the given DID. - self.documents.read().map(|documents| documents.get(did).cloned()) - } - - async fn document_set(&self, did: &IotaDID, document: &IotaDocument) -> Result<()> { - // Set the DID document of the given DID. - self.documents.write()?.insert(did.clone(), document.clone()); - - Ok(()) + async fn blob_get(&self, did: &IotaDID) -> Result>> { + // Lookup the value stored of the given DID. + self.blobs.read().map(|data| data.get(did).cloned()) } async fn flush_changes(&self) -> Result<()> { @@ -532,8 +509,7 @@ impl Debug for MemStore { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { if self.expand { f.debug_struct("MemStore") - .field("chain_states", &self.chain_states) - .field("states", &self.documents) + .field("blobs", &self.blobs) .field("vaults", &self.vaults) .finish() } else { diff --git a/identity_account_storage/src/storage/stronghold.rs b/identity_account_storage/src/storage/stronghold.rs index 3a9762dedd..25fac69da2 100644 --- a/identity_account_storage/src/storage/stronghold.rs +++ b/identity_account_storage/src/storage/stronghold.rs @@ -14,7 +14,6 @@ use identity_core::crypto::PrivateKey; use identity_core::crypto::PublicKey; use identity_core::crypto::X25519; use identity_iota_core::did::IotaDID; -use identity_iota_core::document::IotaDocument; use identity_iota_core::tangle::NetworkName; use iota_stronghold::procedures; use iota_stronghold::procedures::ProcedureError; @@ -32,7 +31,6 @@ use zeroize::Zeroize; use crate::error::Error; use crate::error::Result; -use crate::identity::ChainState; use crate::storage::Storage; use crate::stronghold::ClientOperation; use crate::stronghold::ClientPath; @@ -52,8 +50,7 @@ static INDEX_CLIENT_PATH: &str = "$index"; // The key in the index store that contains the serialized index. // This happens to be the same as the client path, but for explicitness we define them separately. static INDEX_STORE_KEY: &str = INDEX_CLIENT_PATH; -static CHAIN_STATE_STORE_KEY: &str = "$chain_state"; -static DOCUMENT_STORE_KEY: &str = "$document"; +static BLOB_STORE_KEY: &str = "$blob"; // The static identifier for vaults inside clients. static VAULT_PATH: &[u8; 6] = b"$vault"; @@ -377,58 +374,24 @@ impl Storage for Stronghold { } } - async fn chain_state_get(&self, did: &IotaDID) -> Result> { - let client: Client = self.client(&ClientPath::from(did))?; - let store: Store = client.store(); - - let data: Option> = store - .get(CHAIN_STATE_STORE_KEY.as_bytes()) - .map_err(|err| StrongholdError::Store(StoreOperation::Get, err))?; - - match data { - None => return Ok(None), - Some(data) => Ok(Some(ChainState::from_json_slice(&data)?)), - } - } - - async fn chain_state_set(&self, did: &IotaDID, chain_state: &ChainState) -> Result<()> { - let json: Vec = chain_state.to_json_vec()?; - + async fn blob_set(&self, did: &IotaDID, blob: Vec) -> Result<()> { self.mutate_client(did, |client| { let store: Store = client.store(); store - .insert(CHAIN_STATE_STORE_KEY.as_bytes().to_vec(), json, None) + .insert(BLOB_STORE_KEY.as_bytes().to_vec(), blob, None) .map(|_| ()) .map_err(|err| StrongholdError::Store(StoreOperation::Insert, err).into()) }) } - async fn document_get(&self, did: &IotaDID) -> Result> { + async fn blob_get(&self, did: &IotaDID) -> Result>> { let client: Client = self.client(&ClientPath::from(did))?; let store: Store = client.store(); - let data: Option> = store - .get(DOCUMENT_STORE_KEY.as_bytes()) + .get(BLOB_STORE_KEY.as_bytes()) .map_err(|err| StrongholdError::Store(StoreOperation::Get, err))?; - - match data { - None => return Ok(None), - Some(data) => Ok(Some(IotaDocument::from_json_slice(&data)?)), - } - } - - async fn document_set(&self, did: &IotaDID, document: &IotaDocument) -> Result<()> { - let json: Vec = document.to_json_vec()?; - - self.mutate_client(did, |client| { - let store: Store = client.store(); - - store - .insert(DOCUMENT_STORE_KEY.as_bytes().to_vec(), json, None) - .map(|_| ()) - .map_err(|err| StrongholdError::Store(StoreOperation::Insert, err).into()) - }) + Ok(data) } async fn flush_changes(&self) -> Result<()> { diff --git a/identity_account_storage/src/storage/test_suite.rs b/identity_account_storage/src/storage/test_suite.rs index 63046e310a..9809459800 100644 --- a/identity_account_storage/src/storage/test_suite.rs +++ b/identity_account_storage/src/storage/test_suite.rs @@ -6,6 +6,8 @@ use function_name::named; use rand::distributions::DistString; use rand::rngs::OsRng; +use identity_core::convert::FromJson; +use identity_core::convert::ToJson; use identity_core::crypto::KeyPair; use identity_core::crypto::KeyType; use identity_core::crypto::PrivateKey; @@ -382,25 +384,9 @@ impl StorageTestSuite { .await .context("did_create returned an error")?; - let chain_state: Option = storage - .chain_state_get(&did) - .await - .context("chain_state_get returned an error")?; - - ensure!( - chain_state.is_none(), - "expected chain_state_get to return `None` for a new DID" - ); - - let document: Option = storage - .document_get(&did) - .await - .context("document_get returned an error")?; + let value: Option> = storage.blob_get(&did).await.context("blob_get returned an error")?; - ensure!( - document.is_none(), - "expected document_get to return `None` for a new DID" - ); + ensure!(value.is_none(), "expected blob_get to return `None` for a new DID"); let public_key: PublicKey = storage .key_public(&did, &location) @@ -411,42 +397,30 @@ impl StorageTestSuite { IotaVerificationMethod::new(did.clone(), KeyType::Ed25519, &public_key, &fragment).unwrap(); let expected_document: IotaDocument = IotaDocument::from_verification_method(method).unwrap(); - storage - .document_set(&did, &expected_document) - .await - .context("document_set returned an error")?; - - let document: IotaDocument = storage - .document_get(&did) + .blob_set(&did, expected_document.to_json_vec().unwrap()) .await - .context("document_get returned an error")? - .ok_or_else(|| anyhow::Error::msg("expected `Some(_)` to be returned, got `None`"))?; - + .context("blob_set returned an error")?; + let value: Option> = storage.blob_get(&did).await.context("blob_get returned an error")?; + let document: IotaDocument = IotaDocument::from_json_slice(&value.unwrap()).unwrap(); ensure_eq!( expected_document, document, - "expected document to be `{expected_document}`, got `{document}`" + "expected `{expected_document}`, got `{document}`" ); let mut expected_chain_state: ChainState = ChainState::new(); expected_chain_state.set_last_integration_message_id(MessageId::new([0xff; 32])); - storage - .chain_state_set(&did, &expected_chain_state) - .await - .context("chain_state_set returned an error")?; - - let chain_state: ChainState = storage - .chain_state_get(&did) + .blob_set(&did, expected_chain_state.to_json_vec().unwrap()) .await - .context("chain_state_get returned an error")? - .ok_or_else(|| anyhow::Error::msg("expected `Some(_)` to be returned, got `None`"))?; - + .context("blob_set returned an error")?; + let value: Option> = storage.blob_get(&did).await.context("blob_get returned an error")?; + let chain_state: ChainState = ChainState::from_json_slice(&value.unwrap()).unwrap(); ensure_eq!( expected_chain_state, chain_state, - "expected chain state to be `{expected_chain_state:?}`, got `{chain_state:?}`" + "expected `{expected_chain_state:?}`, got `{chain_state:?}`" ); Ok(()) @@ -474,7 +448,7 @@ impl StorageTestSuite { expected_chain_state.set_last_integration_message_id(MessageId::new([0xff; 32])); storage - .chain_state_set(&did, &expected_chain_state) + .blob_set(&did, expected_chain_state.to_json_vec().unwrap()) .await .context("chain_state_set returned an error")?; @@ -482,15 +456,9 @@ impl StorageTestSuite { ensure!(purged, "expected did `{did}` to have been purged"); - let chain_state: Option = storage - .chain_state_get(&did) - .await - .context("chain_state_get returned an error")?; + let value: Option> = storage.blob_get(&did).await.context("blob_get returned an error")?; - ensure!( - chain_state.is_none(), - "expected chain_state_get to return `None` after purging" - ); + ensure!(value.is_none(), "expected blob_get to return `None` after purging"); let exists: bool = storage .key_exists(&did, &location) diff --git a/identity_account_storage/src/storage/traits.rs b/identity_account_storage/src/storage/traits.rs index 7816ce1e3a..99c004e617 100644 --- a/identity_account_storage/src/storage/traits.rs +++ b/identity_account_storage/src/storage/traits.rs @@ -9,11 +9,9 @@ use identity_core::crypto::KeyType; use identity_core::crypto::PrivateKey; use identity_core::crypto::PublicKey; use identity_iota_core::did::IotaDID; -use identity_iota_core::document::IotaDocument; use identity_iota_core::tangle::NetworkName; use crate::error::Result; -use crate::identity::ChainState; #[cfg(feature = "encryption")] use crate::types::CekAlgorithm; #[cfg(feature = "encryption")] @@ -151,17 +149,11 @@ pub trait Storage: storage_sub_trait::StorageSendSyncMaybe + Debug { private_key: &KeyLocation, ) -> Result>; - /// Returns the chain state of the identity specified by `did`. - async fn chain_state_get(&self, did: &IotaDID) -> Result>; + /// Stores an arbitrary blob for the identity specified by `did`. + async fn blob_set(&self, did: &IotaDID, blob: Vec) -> Result<()>; - /// Set the chain state of the identity specified by `did`. - async fn chain_state_set(&self, did: &IotaDID, chain_state: &ChainState) -> Result<()>; - - /// Returns the [`IotaDocument`] of the identity specified by `did`. - async fn document_get(&self, did: &IotaDID) -> Result>; - - /// Sets a new state for the identity specified by `did`. - async fn document_set(&self, did: &IotaDID, state: &IotaDocument) -> Result<()>; + /// Returns the blob stored by the identity specified by `did`. + async fn blob_get(&self, did: &IotaDID) -> Result>>; /// Persists any unsaved changes. async fn flush_changes(&self) -> Result<()>;