-
Notifications
You must be signed in to change notification settings - Fork 90
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Wasm bindings for
KeyIdStorage
(#1147)
- Loading branch information
Showing
8 changed files
with
338 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// Copyright 2020-2023 IOTA Stiftung | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
use super::WasmMethodDigest; | ||
use crate::common::PromiseString; | ||
use crate::error::JsValueResult; | ||
use identity_iota::storage::key_id_storage::KeyIdStorage; | ||
use identity_iota::storage::key_id_storage::KeyIdStorageResult; | ||
use identity_iota::storage::key_id_storage::MethodDigest; | ||
use identity_iota::storage::key_storage::KeyId; | ||
use js_sys::Promise; | ||
use wasm_bindgen::prelude::*; | ||
use wasm_bindgen_futures::JsFuture; | ||
|
||
#[wasm_bindgen] | ||
extern "C" { | ||
#[wasm_bindgen(typescript_type = "KeyIdStorage")] | ||
pub type WasmKeyIdStorage; | ||
|
||
#[wasm_bindgen(method, js_name = insertKeyId)] | ||
pub fn insert_key_id(this: &WasmKeyIdStorage, method_digest: WasmMethodDigest, key_id: String) -> Promise; | ||
|
||
#[wasm_bindgen(method, js_name = getKeyId)] | ||
pub fn get_key_id(this: &WasmKeyIdStorage, method_digest: WasmMethodDigest) -> PromiseString; | ||
|
||
#[wasm_bindgen(method, js_name = deleteKeyId)] | ||
pub fn delete_key_id(this: &WasmKeyIdStorage, method_digest: WasmMethodDigest) -> Promise; | ||
} | ||
|
||
#[async_trait::async_trait(?Send)] | ||
impl KeyIdStorage for WasmKeyIdStorage { | ||
async fn insert_key_id(&self, method_digest: MethodDigest, key_id: KeyId) -> KeyIdStorageResult<()> { | ||
let promise: Promise = Promise::resolve(&WasmKeyIdStorage::insert_key_id( | ||
self, | ||
WasmMethodDigest(method_digest), | ||
key_id.clone().into(), | ||
)); | ||
let result: JsValueResult = JsFuture::from(promise).await.into(); | ||
result.into() | ||
} | ||
|
||
async fn get_key_id(&self, method_digest: &MethodDigest) -> KeyIdStorageResult<KeyId> { | ||
let promise: Promise = Promise::resolve(&WasmKeyIdStorage::get_key_id( | ||
self, | ||
WasmMethodDigest(method_digest.clone()), | ||
)); | ||
let result: JsValueResult = JsFuture::from(promise).await.into(); | ||
result.into() | ||
} | ||
|
||
async fn delete_key_id(&self, method_digest: &MethodDigest) -> KeyIdStorageResult<()> { | ||
let promise: Promise = Promise::resolve(&WasmKeyIdStorage::delete_key_id( | ||
self, | ||
WasmMethodDigest(method_digest.clone()), | ||
)); | ||
let result: JsValueResult = JsFuture::from(promise).await.into(); | ||
result.into() | ||
} | ||
} | ||
|
||
#[wasm_bindgen(typescript_custom_section)] | ||
const JWK_STORAGE: &'static str = r#" | ||
/** | ||
* Key value Storage for key ids under [`MethodDigest`]. | ||
*/ | ||
interface KeyIdStorage { | ||
/** | ||
* Insert a key id into the `KeyIdStorage` under the given `MethodDigest`. | ||
* | ||
* If an entry for `key` already exists in the storage an error must be returned | ||
* immediately without altering the state of the storage. | ||
*/ | ||
insertKeyId: (methodDigest: MethodDigest, keyId: string) => Promise<void>; | ||
/** | ||
* Obtain the key id associated with the given [`MethodDigest`]. | ||
*/ | ||
getKeyId: (methodDigest: MethodDigest) => Promise<string>; | ||
/** | ||
* Delete the [`KeyId`] associated with the given [`MethodDigest`] from the [`KeyIdStorage`]. | ||
* | ||
* If `key` is not found in storage, an Error must be returned. | ||
*/ | ||
deleteKeyId: (methodDigest: MethodDigest) => Promise<void>; | ||
}"#; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// Copyright 2020-2023 IOTA Stiftung | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
use identity_iota::storage::key_id_storage::MethodDigest; | ||
use js_sys::Uint8Array; | ||
use wasm_bindgen::prelude::*; | ||
|
||
use crate::did::WasmVerificationMethod; | ||
use crate::error::Result; | ||
use crate::error::WasmResult; | ||
|
||
/// Unique identifier of a [`VerificationMethod`]. | ||
/// | ||
/// NOTE: | ||
/// This class does not have a JSON representation, | ||
/// use the methods `pack` and `unpack` instead. | ||
#[wasm_bindgen(js_name = MethodDigest, inspectable)] | ||
pub struct WasmMethodDigest(pub(crate) MethodDigest); | ||
|
||
#[wasm_bindgen(js_class = MethodDigest)] | ||
impl WasmMethodDigest { | ||
#[wasm_bindgen(constructor)] | ||
pub fn new(verification_method: &WasmVerificationMethod) -> Result<WasmMethodDigest> { | ||
Ok(Self(MethodDigest::new(&verification_method.0).wasm_result()?)) | ||
} | ||
|
||
/// Packs `MethodDigest` into bytes. | ||
#[wasm_bindgen] | ||
pub fn pack(&self) -> Uint8Array { | ||
let bytes: &[u8] = &self.0.pack(); | ||
bytes.into() | ||
} | ||
|
||
/// Unpacks bytes into [`MethodDigest`]. | ||
#[wasm_bindgen] | ||
pub fn unpack(bytes: &Uint8Array) -> Result<WasmMethodDigest> { | ||
let bytes: Vec<u8> = bytes.to_vec(); | ||
Ok(Self(MethodDigest::unpack(bytes).wasm_result()?)) | ||
} | ||
} | ||
|
||
impl_wasm_clone!(WasmMethodDigest, MethodDigest); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
const assert = require("assert"); | ||
import { CoreDID, KeyIdStorage, KeyPair, KeyType, MethodDigest, VerificationMethod } from "../node"; | ||
|
||
describe("Method digest", () => { | ||
it("should have consistent hashing", () => { | ||
let verificationMethodJson = { | ||
id: "did:example:HHoh9NQC9AUsK15Jyyq53VTujxEUizKDXRXd7zbT1B5u#frag_1", | ||
controller: "did:example:HHoh9NQC9AUsK15Jyyq53VTujxEUizKDXRXd7zbT1B5u", | ||
type: "Ed25519VerificationKey2018", | ||
publicKeyMultibase: "zHHoh9NQC9AUsK15Jyyq53VTujxEUizKDXRXd7zbT1B5u", | ||
}; | ||
|
||
let verificationMethod = VerificationMethod.fromJSON(verificationMethodJson); | ||
let methodDigest = new MethodDigest(verificationMethod); | ||
|
||
let packed = methodDigest.pack(); | ||
// Packed bytes must be consistent between Rust and Wasm, see Rust tests for `MethodDigest`. | ||
let packedExpected = new Uint8Array([0, 74, 60, 10, 199, 76, 205, 180, 133]); | ||
assert.deepStrictEqual(packed, packedExpected); | ||
}); | ||
}); | ||
|
||
describe("Key Id Storage", () => { | ||
it("should work", async () => { | ||
const KEY_ID = "my-key-id"; | ||
let vm: VerificationMethod = createVerificationMethod(); | ||
let methodDigest: MethodDigest = new MethodDigest(vm); | ||
|
||
let memstore = new KeyIdMemStore(); | ||
|
||
// Deletion of non saved key id results in error. | ||
assert.rejects(memstore.deleteKeyId(methodDigest)); | ||
|
||
// Store key id. | ||
await memstore.insertKeyId(methodDigest, KEY_ID); | ||
|
||
// Double insertion results in error. | ||
assert.rejects(memstore.insertKeyId(methodDigest, KEY_ID)); | ||
|
||
// Restore key id from a `MethodDigest` with the same data but not the same reference. | ||
let methodDigestClone = MethodDigest.unpack(methodDigest.pack()); | ||
let key_id_restored: string = await memstore.getKeyId(methodDigestClone); | ||
|
||
// Check restored key id. | ||
assert.equal(KEY_ID, key_id_restored); | ||
|
||
// Delete stored key id. | ||
await memstore.deleteKeyId(methodDigest); | ||
|
||
// Double deletion results in error. | ||
assert.rejects(memstore.deleteKeyId(methodDigest)); | ||
}); | ||
}); | ||
|
||
function createVerificationMethod(): VerificationMethod { | ||
let id = CoreDID.parse("did:example:abc123"); | ||
let keypair = new KeyPair(KeyType.Ed25519); | ||
let method = new VerificationMethod(id, keypair.type(), keypair.public(), "#key-1"); | ||
return method; | ||
} | ||
|
||
/** | ||
* Converts a `MethodDigest` to a base64 encoded string. | ||
*/ | ||
function methodDigestToString(methodDigest: MethodDigest): string { | ||
let arrayBuffer = methodDigest.pack().buffer; | ||
let buffer = Buffer.from(arrayBuffer); | ||
return buffer.toString("base64"); | ||
} | ||
|
||
/** | ||
* Creates a `MethodDigest` from a base64 encoded string. | ||
*/ | ||
function stringToMethodDigest(input: string): MethodDigest { | ||
let buffer = Buffer.from(input, "base64"); | ||
let byteArray = Uint8Array.from(buffer); | ||
return MethodDigest.unpack(byteArray); | ||
} | ||
|
||
class KeyIdMemStore implements KeyIdStorage { | ||
private _keyIds: Map<string, string>; | ||
|
||
constructor() { | ||
this._keyIds = new Map<string, string>(); | ||
} | ||
|
||
public async insertKeyId(methodDigest: MethodDigest, keyId: string): Promise<void> { | ||
let methodDigestAsString: string = methodDigestToString(methodDigest); | ||
let value = this._keyIds.get(methodDigestAsString); | ||
if (value !== undefined) { | ||
throw new Error("KeyId already exists"); | ||
} | ||
this._keyIds.set(methodDigestAsString, keyId); | ||
} | ||
|
||
public async getKeyId(methodDigest: MethodDigest): Promise<string> { | ||
let methodDigestAsString: string = methodDigestToString(methodDigest); | ||
let value = this._keyIds.get(methodDigestAsString); | ||
if (value == undefined) { | ||
throw new Error("KeyId not found"); | ||
} | ||
return value; | ||
} | ||
|
||
public async deleteKeyId(methodDigest: MethodDigest): Promise<void> { | ||
let methodDigestAsString: string = methodDigestToString(methodDigest); | ||
let success = this._keyIds.delete(methodDigestAsString); | ||
if (success) { | ||
return; | ||
} else { | ||
throw new Error("KeyId not found!"); | ||
} | ||
} | ||
} |
Oops, something went wrong.