Skip to content

Commit 6624b56

Browse files
author
Oliver E. Anderson
authored
Improve robustness of the Wasm bindings (#1129)
1 parent da69764 commit 6624b56

14 files changed

+166
-349
lines changed

.github/workflows/shared-build-wasm.yml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,6 @@ jobs:
6767
- name: Build WASM bindings
6868
run: npm run build
6969

70-
- name: Run unit tests
71-
if: ${{ inputs.run-unit-tests }}
72-
run: npm run test:unit
73-
7470
- name: Run Node unit tests
7571
if: ${{ inputs.run-unit-tests }}
7672
run: npm run test:unit:node

bindings/wasm/Cargo.toml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,11 @@ features = ["client", "revocation-bitmap", "resolver"]
3838

3939
[dev-dependencies]
4040
rand = "0.8.5"
41-
wasm-bindgen-test = { version = "0.3" }
4241

4342
[target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dependencies]
4443
getrandom = { version = "0.2", default-features = false, features = ["js"] }
4544
instant = { version = "0.1", default-features = false, features = ["wasm-bindgen"] }
4645

47-
[package.metadata.wasm-pack.profile.release]
48-
wasm-opt = true
49-
5046
[profile.release]
5147
opt-level = 's'
5248
lto = true

bindings/wasm/docs/api-reference.md

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,6 @@ See <code>IVerifierOptions</code>.</p>
109109
## Members
110110

111111
<dl>
112-
<dt><a href="#StateMetadataEncoding">StateMetadataEncoding</a></dt>
113-
<dd></dd>
114-
<dt><a href="#KeyType">KeyType</a></dt>
115-
<dd></dd>
116112
<dt><a href="#MethodRelationship">MethodRelationship</a></dt>
117113
<dd></dd>
118114
<dt><a href="#StatusCheck">StatusCheck</a></dt>
@@ -157,6 +153,10 @@ This variant is the default used if no other variant is specified when construct
157153
<dt><a href="#FirstError">FirstError</a></dt>
158154
<dd><p>Return after the first error occurs.</p>
159155
</dd>
156+
<dt><a href="#StateMetadataEncoding">StateMetadataEncoding</a></dt>
157+
<dd></dd>
158+
<dt><a href="#KeyType">KeyType</a></dt>
159+
<dd></dd>
160160
</dl>
161161

162162
## Functions
@@ -391,6 +391,7 @@ A method-agnostic DID Document.
391391
* [.signData(data, privateKey, methodQuery, options)](#CoreDocument+signData) ⇒ <code>any</code>
392392
* [.clone()](#CoreDocument+clone)[<code>CoreDocument</code>](#CoreDocument)
393393
* [._shallowCloneInternal()](#CoreDocument+_shallowCloneInternal)[<code>CoreDocument</code>](#CoreDocument)
394+
* [._strongCountInternal()](#CoreDocument+_strongCountInternal) ⇒ <code>number</code>
394395
* [.toJSON()](#CoreDocument+toJSON) ⇒ <code>any</code>
395396
* _static_
396397
* [.fromJSON(json)](#CoreDocument.fromJSON)[<code>CoreDocument</code>](#CoreDocument)
@@ -719,6 +720,13 @@ Deep clones the `CoreDocument`.
719720
### Warning
720721
This is for internal use only. Do not rely on or call this method.
721722

723+
**Kind**: instance method of [<code>CoreDocument</code>](#CoreDocument)
724+
<a name="CoreDocument+_strongCountInternal"></a>
725+
726+
### coreDocument.\_strongCountInternal() ⇒ <code>number</code>
727+
### Warning
728+
This is for internal use only. Do not rely on or call this method.
729+
722730
**Kind**: instance method of [<code>CoreDocument</code>](#CoreDocument)
723731
<a name="CoreDocument+toJSON"></a>
724732

@@ -1801,6 +1809,7 @@ Deserializes an instance from a JSON object.
18011809
* [.unrevokeCredentials(serviceQuery, indices)](#IotaDocument+unrevokeCredentials)
18021810
* [.clone()](#IotaDocument+clone)[<code>IotaDocument</code>](#IotaDocument)
18031811
* [._shallowCloneInternal()](#IotaDocument+_shallowCloneInternal)[<code>IotaDocument</code>](#IotaDocument)
1812+
* [._strongCountInternal()](#IotaDocument+_strongCountInternal) ⇒ <code>number</code>
18041813
* [.toJSON()](#IotaDocument+toJSON) ⇒ <code>any</code>
18051814
* [.toCoreDocument()](#IotaDocument+toCoreDocument)[<code>CoreDocument</code>](#CoreDocument)
18061815
* _static_
@@ -2197,6 +2206,13 @@ Returns a deep clone of the `IotaDocument`.
21972206
### Warning
21982207
This is for internal use only. Do not rely on or call this method.
21992208

2209+
**Kind**: instance method of [<code>IotaDocument</code>](#IotaDocument)
2210+
<a name="IotaDocument+_strongCountInternal"></a>
2211+
2212+
### iotaDocument.\_strongCountInternal() ⇒ <code>number</code>
2213+
### Warning
2214+
This is for internal use only. Do not rely on or call this method.
2215+
22002216
**Kind**: instance method of [<code>IotaDocument</code>](#IotaDocument)
22012217
<a name="IotaDocument+toJSON"></a>
22022218

@@ -3933,14 +3949,6 @@ This is possible because Ed25519 is birationally equivalent to Curve25519 used b
39333949
| --- | --- |
39343950
| publicKey | <code>Uint8Array</code> |
39353951

3936-
<a name="StateMetadataEncoding"></a>
3937-
3938-
## StateMetadataEncoding
3939-
**Kind**: global variable
3940-
<a name="KeyType"></a>
3941-
3942-
## KeyType
3943-
**Kind**: global variable
39443952
<a name="MethodRelationship"></a>
39453953

39463954
## MethodRelationship
@@ -4022,6 +4030,14 @@ Return all errors that occur during validation.
40224030
## FirstError
40234031
Return after the first error occurs.
40244032

4033+
**Kind**: global variable
4034+
<a name="StateMetadataEncoding"></a>
4035+
4036+
## StateMetadataEncoding
4037+
**Kind**: global variable
4038+
<a name="KeyType"></a>
4039+
4040+
## KeyType
40254041
**Kind**: global variable
40264042
<a name="start"></a>
40274043

bindings/wasm/lib/append_functions.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
import { CoreDocument, IotaDocument, IToCoreDocument } from "~identity_wasm";
1+
import { CoreDID, CoreDocument, IotaDID, IotaDocument, IToCoreDID, IToCoreDocument } from "~identity_wasm";
22
type GetCoreDocument = (arg: IToCoreDocument) => CoreDocument;
33
type MaybeGetIotaDocument = (arg: IToCoreDocument) => IotaDocument | void;
4+
type GetCoreDidClone = (arg: IToCoreDID) => CoreDID;
45

56
declare global {
67
var _getCoreDocumentInternal: GetCoreDocument;
78
var _maybeGetIotaDocumentInternal: MaybeGetIotaDocument;
9+
var _getCoreDidCloneInternal: GetCoreDidClone;
810
}
911
function _getCoreDocumentInternal(arg: IToCoreDocument): CoreDocument {
1012
if (arg instanceof CoreDocument) {
@@ -22,6 +24,17 @@ function _maybeGetIotaDocumentInternal(arg: IToCoreDocument): IotaDocument | voi
2224
}
2325
}
2426

27+
function _getCoreDidCloneInternal(arg: IToCoreDID): CoreDID {
28+
if (arg instanceof IotaDID || arg instanceof CoreDID) {
29+
return arg.toCoreDid();
30+
} else {
31+
// Pass deep clone to avoid nulling out pointer.
32+
return arg.toCoreDid().clone();
33+
}
34+
}
35+
2536
globalThis._getCoreDocumentInternal = _getCoreDocumentInternal;
2637

2738
globalThis._maybeGetIotaDocumentInternal = _maybeGetIotaDocumentInternal;
39+
40+
globalThis._getCoreDidCloneInternal = _getCoreDidCloneInternal;

bindings/wasm/package.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,12 @@
1818
"build:docs": "node ./build/docs",
1919
"build": "npm run build:web && npm run build:nodejs && npm run build:docs",
2020
"example:node": "ts-node --project tsconfig.node.json -r tsconfig-paths/register ./examples/src/main.ts",
21-
"test": "npm run test:unit && npm run test:unit:node && npm run test:examples",
21+
"test": "npm run test:unit:node && npm run test:examples",
2222
"test:examples": "npm run test:node && npm run test:readme",
2323
"test:node": "ts-mocha -r tsconfig-paths/register -p tsconfig.node.json ./examples/src/tests/*.ts --parallel --jobs 4 --retries 3 --timeout 180000 --exit",
2424
"test:browser:parallel": "cypress-parallel -s test:browser -t 4 -d cypress/e2e -a '\"--quiet\"'",
2525
"test:browser": "cypress run --headless",
2626
"test:readme": "mocha ./tests/txm_readme.js --retries 3 --timeout 180000 --exit",
27-
"test:unit": "wasm-pack test --release --node",
2827
"test:unit:node": "ts-mocha -p tsconfig.node.json ./tests/*.ts --parallel --exit",
2928
"cypress": "cypress open",
3029
"fmt": "dprint fmt"
@@ -70,8 +69,7 @@
7069
"tsconfig-paths": "^4.1.0",
7170
"txm": "^8.0.1",
7271
"typescript": "^4.7.2",
73-
"wasm-opt": "^1.3.0",
74-
"wasm-pack": "0.10.1"
72+
"wasm-opt": "^1.3.0"
7573
},
7674
"dependencies": {
7775
"@iota/types": "^1.0.0-beta.11",

bindings/wasm/src/did/wasm_core_did.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,13 +134,23 @@ impl From<CoreDID> for WasmCoreDID {
134134
}
135135
}
136136

137+
impl From<&IToCoreDID> for CoreDID {
138+
fn from(value: &IToCoreDID) -> Self {
139+
get_core_did_clone(value).0
140+
}
141+
}
142+
137143
#[wasm_bindgen]
138144
extern "C" {
139145
#[wasm_bindgen(typescript_type = "CoreDID | IToCoreDID")]
140146
pub type IToCoreDID;
141147

142-
#[wasm_bindgen(method, js_name= toCoreDid)]
143-
pub fn to_core_did(this: &IToCoreDID) -> WasmCoreDID;
148+
// Specially crafted JS function called internally that ensures
149+
// Custom DID implementations built on `CoreDID` don't get nulled
150+
// out by Rust. Also avoids double clones when passing an instance of `CoreDID`
151+
// or `IotaDID`.
152+
#[wasm_bindgen(js_name = _getCoreDidCloneInternal, skip_typescript)]
153+
pub fn get_core_did_clone(input: &IToCoreDID) -> WasmCoreDID;
144154

145155
}
146156

bindings/wasm/src/did/wasm_core_document.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,13 @@ impl WasmCoreDocument {
533533
WasmCoreDocument(self.0.clone())
534534
}
535535

536+
/// ### Warning
537+
/// This is for internal use only. Do not rely on or call this method.
538+
#[wasm_bindgen(js_name = _strongCountInternal)]
539+
pub fn strong_count(&self) -> usize {
540+
Rc::strong_count(&self.0)
541+
}
542+
536543
// ===========================================================================
537544
// Serialization
538545
// ===========================================================================

bindings/wasm/src/did/wasm_verification_method.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::did::WasmMethodType;
1010
use crate::error::Result;
1111
use crate::error::WasmResult;
1212
use identity_iota::crypto::PublicKey;
13+
use identity_iota::did::CoreDID;
1314
use identity_iota::verification::VerificationMethod;
1415
use wasm_bindgen::prelude::*;
1516

@@ -31,7 +32,7 @@ impl WasmVerificationMethod {
3132
fragment: String,
3233
) -> Result<WasmVerificationMethod> {
3334
let public_key: PublicKey = PublicKey::from(publicKey);
34-
VerificationMethod::new(did.to_core_did().0, keyType.into(), &public_key, &fragment)
35+
VerificationMethod::new(CoreDID::from(did), keyType.into(), &public_key, &fragment)
3536
.map(Self)
3637
.wasm_result()
3738
}

bindings/wasm/src/iota/iota_document.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,13 @@ impl WasmIotaDocument {
637637
WasmIotaDocument(self.0.clone())
638638
}
639639

640+
/// ### Warning
641+
/// This is for internal use only. Do not rely on or call this method.
642+
#[wasm_bindgen(js_name = _strongCountInternal)]
643+
pub fn strong_count(&self) -> usize {
644+
Rc::strong_count(&self.0)
645+
}
646+
640647
// ===========================================================================
641648
// Serialization
642649
// ===========================================================================

bindings/wasm/tests/core.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ const VALID_DID_EXAMPLE = "did:example:123";
1717
const KEY_BYTES = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
1818
25, 26, 27, 28, 29, 30, 31, 32]);
1919

20+
class MockDID {
21+
inner: CoreDID;
22+
constructor(inner: CoreDID) {
23+
this.inner = inner;
24+
}
25+
toCoreDid(): CoreDID {
26+
return this.inner;
27+
}
28+
}
29+
2030
describe("CoreDID", function() {
2131
describe("#parse", function() {
2232
it("iota", () => {
@@ -94,6 +104,19 @@ describe("CoreDID", function() {
94104
assert.deepStrictEqual(CoreDID.validMethodId("method[brackets]"), false);
95105
});
96106
});
107+
describe("#callingToCoreDid from Rust does not null out DIDs", function() {
108+
it("should work", () => {
109+
let didStr = "did:example:network:123";
110+
let did = CoreDID.parse(didStr);
111+
let mockDid = new MockDID(did);
112+
const method = new VerificationMethod(mockDid, KeyType.Ed25519, KEY_BYTES, "key-0");
113+
// Check that the VerificationMethod constructor does not null out mockDid.inner.
114+
assert.deepStrictEqual(mockDid.inner.toString() as string, didStr as string);
115+
// Also check for `CoreDID`
116+
let method1 = new VerificationMethod(did, KeyType.Ed25519, KEY_BYTES, "key-1");
117+
assert.deepStrictEqual(did.toString() as string, didStr as string);
118+
});
119+
});
97120
});
98121

99122
describe("CoreDocument", function() {

bindings/wasm/tests/credentials.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,9 @@ describe("CredentialValidator, PresentationValidator", function() {
289289
VerifierOptions.default(),
290290
)
291291
);
292+
// Check that we are not leaking memory in Rust.
293+
assert.deepStrictEqual(issuerDoc._strongCountInternal() as number, 1);
294+
assert.deepStrictEqual(subjectDoc._strongCountInternal() as number, 1);
292295

293296
assert.doesNotThrow(() =>
294297
CredentialValidator.validate(
@@ -322,6 +325,8 @@ describe("CredentialValidator, PresentationValidator", function() {
322325
mockInheritedDocument.toCoreDocumentCalled,
323326
false,
324327
);
328+
// Also check that we don't leak memory.
329+
assert.deepStrictEqual(mockInheritedDocument._strongCountInternal() as number, 1);
325330

326331
// Characterisation test: Check that toCoreDocument DOES get called
327332
// when passing a mere implementer of IToCoreDocument (without inheriting from CoreDocument)
@@ -340,6 +345,9 @@ describe("CredentialValidator, PresentationValidator", function() {
340345
true,
341346
);
342347

348+
// Also check that we don't leak memory.
349+
assert.deepStrictEqual(mockIToCoreDocument.inner._strongCountInternal() as number, 1);
350+
343351
// Characterisation test: Check that toCoreDocument does not get called
344352
// when passing `IotaDocument` (we use inheritance as a way of mocking a normal IotaDocument).
345353
let mockIotaDoc = new MockInheritedIotaDocument("iota");
@@ -386,6 +394,9 @@ describe("CredentialValidator, PresentationValidator", function() {
386394
VerifierOptions.default(),
387395
)
388396
);
397+
// Check that we don't leak memory.
398+
assert.deepStrictEqual(subjectDoc._strongCountInternal() as number, 1);
399+
389400
assert.doesNotThrow(() =>
390401
PresentationValidator.validate(
391402
signedPresentation,
@@ -395,6 +406,10 @@ describe("CredentialValidator, PresentationValidator", function() {
395406
FailFast.FirstError,
396407
)
397408
);
409+
// Check that we don't leak memory.
410+
assert.deepStrictEqual(subjectDoc._strongCountInternal() as number, 1);
411+
assert.deepStrictEqual(issuerDoc._strongCountInternal() as number, 1);
412+
398413
assert.deepStrictEqual(
399414
PresentationValidator.extractHolder(signedPresentation).toString(),
400415
subjectDID.toString(),

bindings/wasm/tests/iota.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ describe("IotaDocument", function() {
8888
const fragment = "new-method-1";
8989
const scope = MethodScope.AssertionMethod();
9090
const method = new VerificationMethod(doc.id(), KeyType.Ed25519, aliasIdBytes, fragment);
91-
9291
// Add.
9392
doc.insertMethod(method, scope);
9493
// Resolve.
@@ -227,4 +226,11 @@ describe("IotaDocument", function() {
227226
assert.deepStrictEqual(doc.properties(), properties);
228227
});
229228
});
229+
describe("#callingToCoreDid from Rust does not null out IotaDID", function() {
230+
it("should work", () => {
231+
const did = new IotaDID(aliasIdBytes, networkName);
232+
const method = new VerificationMethod(did, KeyType.Ed25519, aliasIdBytes, "key-0");
233+
assert.deepStrictEqual(did.toString(), "did:" + IotaDID.METHOD + ":" + networkName + ":" + aliasIdHex);
234+
});
235+
});
230236
});

0 commit comments

Comments
 (0)