@nuggetslife/vc 0.0.21 → 0.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,17 @@
1
+ {
2
+ "@context": [
3
+ "https://www.w3.org/2018/credentials/v1",
4
+ "https://w3id.org/citizenship/v1",
5
+ "https://w3id.org/security/bbs/v1"
6
+ ],
7
+ "id": "https://issuer.example.com/credentials/minimal-001",
8
+ "type": ["VerifiableCredential", "PermanentResidentCard"],
9
+ "issuer": "did:example:489398593",
10
+ "issuanceDate": "2024-01-15T10:00:00Z",
11
+ "credentialSubject": {
12
+ "id": "did:example:user123",
13
+ "type": ["PermanentResident", "Person"],
14
+ "givenName": "Alice",
15
+ "familyName": "Johnson"
16
+ }
17
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "@context": [
3
+ "https://www.w3.org/2018/credentials/v1",
4
+ "https://w3id.org/citizenship/v1",
5
+ "https://w3id.org/security/bbs/v1"
6
+ ],
7
+ "id": "https://issuer.example.com/credentials/rich-001",
8
+ "type": ["VerifiableCredential", "PermanentResidentCard"],
9
+ "issuer": "did:example:489398593",
10
+ "identifier": "RICH-001",
11
+ "name": "Extended Resident Card",
12
+ "description": "Credential with many fields to test BBS+ signatures over larger statement counts.",
13
+ "issuanceDate": "2023-06-20T08:30:00Z",
14
+ "expirationDate": "2033-06-20T08:30:00Z",
15
+ "credentialSubject": {
16
+ "id": "did:example:user456",
17
+ "type": ["PermanentResident", "Person"],
18
+ "givenName": "Robert",
19
+ "familyName": "Williams",
20
+ "gender": "Male",
21
+ "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
22
+ "residentSince": "2018-03-15",
23
+ "lprCategory": "C12",
24
+ "lprNumber": "888-777-666",
25
+ "commuterClassification": "C2",
26
+ "birthCountry": "Canada",
27
+ "birthDate": "1985-11-23"
28
+ }
29
+ }
@@ -0,0 +1,36 @@
1
+ {
2
+ "@context": [
3
+ "https://www.w3.org/2018/credentials/v1",
4
+ "https://w3id.org/citizenship/v1",
5
+ "https://w3id.org/security/bbs/v1"
6
+ ],
7
+ "id": "https://issuer.oidp.uscis.gov/credentials/83627465",
8
+ "type": [
9
+ "PermanentResidentCard",
10
+ "VerifiableCredential"
11
+ ],
12
+ "description": "Government of Example Permanent Resident Card.",
13
+ "identifier": "83627465",
14
+ "name": "Permanent Resident Card",
15
+ "credentialSubject": {
16
+ "id": "did:example:b34ca6cd37bbf23",
17
+ "type": [
18
+ "Person",
19
+ "PermanentResident"
20
+ ],
21
+ "familyName": "SMITH",
22
+ "gender": "Male",
23
+ "givenName": "JOHN"
24
+ },
25
+ "expirationDate": "2029-12-03T12:19:52Z",
26
+ "issuanceDate": "2019-12-03T12:19:52Z",
27
+ "issuer": "did:example:489398593",
28
+ "proof": {
29
+ "type": "BbsBlsSignatureProof2020",
30
+ "created": "2026-02-24T18:31:50Z",
31
+ "nonce": "Mb2PKdz8oLnBh2DaIYDN2qp0B/BzqlZ0bomtWLnPkCuUX+LOLozA67gzRX8IFW+prJ4=",
32
+ "proofPurpose": "assertionMethod",
33
+ "proofValue": "ABkB/wbvlXznxDJB6mUBWnQAVCFn1BOG3oGhhrmH4CkfgHwIko6cE2ib8BSnHoIgsvxJbf8ql85AoX28XZNkogIRP9k1rvTS0TN1OBMx20q0+4FQX/jhGs7tVgXoeDJsTIe/kFtOhpoixTV0hNpMpwR3/zF+npQFipsk3Awc0v0Ne/Xc5ExLytwpS+UdtYnCBZWbgTAfAAAAdK8ARGBjzOUY0MsGvW37vMsegFaUHN8iu+vjk/RSzcb45sCwzA3I/dZIfonaPMKmBQAAAAIdxKYEnrgvyA3UPoQR6yAx9mBquPkFq8y9EnMloOj6eVi3eLIrFfaChASa2INbf43O09CGGXcC7ocGmG6GEg8GrgHAIPHm7ecOwUFEhJT8CC5j7t2z4MFrltfk1xW+PD+gsZ4WSjuTuqhHOBJNWXMcAAAACT/T2kIOroFI9tWJrprwQ+NK9W59cgn7V6UV/ynxlhBnDvFcCd7hYv5yDUuQuz9AiSxEveiMViWkHTBDFZ/Z/LhKYHOGGCKG9CJ9OVstwzzfe1jpfTYy4ZnLuuoJCSvcOm72DGumCHr2JVolSYEv5WKxXbN4MY46zL7hLBY6p88zXi7wzo8Odj+jMcptD0vLsKv0FjdSRK4XVXW2FEIy4IASrRFPuZo0Pb30wG3MzFnq7c17sSLUz/2EA929J2YxfmzulFTvsJm4LfwCJ7VA0iY6Suq1qBVy+fJnFMlqDwGoBl4izvhq9J9jBFVysZBdKlK4rwJtj0WqY75lnCPT/30ap7eC0YNnULtSHn3aYNF2JlO8wyu8zDODuXvRF2i5Kw==",
34
+ "verificationMethod": "did:example:489398593#test"
35
+ }
36
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "@context": [
3
+ "https://www.w3.org/2018/credentials/v1",
4
+ "https://w3id.org/citizenship/v1",
5
+ "https://w3id.org/security/bbs/v1"
6
+ ],
7
+ "id": "https://issuer.example.com/credentials/minimal-001",
8
+ "type": [
9
+ "VerifiableCredential",
10
+ "PermanentResidentCard"
11
+ ],
12
+ "issuer": "did:example:489398593",
13
+ "issuanceDate": "2024-01-15T10:00:00Z",
14
+ "credentialSubject": {
15
+ "id": "did:example:user123",
16
+ "type": [
17
+ "PermanentResident",
18
+ "Person"
19
+ ],
20
+ "givenName": "Alice",
21
+ "familyName": "Johnson"
22
+ },
23
+ "proof": {
24
+ "type": "BbsBlsSignature2020",
25
+ "created": "2026-02-24T18:31:50Z",
26
+ "proofPurpose": "assertionMethod",
27
+ "proofValue": "h9O11RGxkmaZQhOZQQRkuy0mGJ+1krOlSO9UMAc1zQf8O7wv2Fi3Ev8UMZxmX2Q4MKVg9X44OT7IyqxonIH7xLuNJjBkG7KYma9urLMBCo4x5JoTWPaG1p7URXapIpy1ng+avITVXJin9XQoxPxyNA==",
28
+ "verificationMethod": "did:example:489398593#test"
29
+ }
30
+ }
@@ -0,0 +1,42 @@
1
+ {
2
+ "@context": [
3
+ "https://www.w3.org/2018/credentials/v1",
4
+ "https://w3id.org/citizenship/v1",
5
+ "https://w3id.org/security/bbs/v1"
6
+ ],
7
+ "id": "https://issuer.oidp.uscis.gov/credentials/83627465",
8
+ "type": [
9
+ "VerifiableCredential",
10
+ "PermanentResidentCard"
11
+ ],
12
+ "issuer": "did:example:489398593",
13
+ "identifier": "83627465",
14
+ "name": "Permanent Resident Card",
15
+ "description": "Government of Example Permanent Resident Card.",
16
+ "issuanceDate": "2019-12-03T12:19:52Z",
17
+ "expirationDate": "2029-12-03T12:19:52Z",
18
+ "credentialSubject": {
19
+ "id": "did:example:b34ca6cd37bbf23",
20
+ "type": [
21
+ "PermanentResident",
22
+ "Person"
23
+ ],
24
+ "givenName": "JOHN",
25
+ "familyName": "SMITH",
26
+ "gender": "Male",
27
+ "image": "data:image/png;base64,iVBORw0KGgokJggg==",
28
+ "residentSince": "2015-01-01",
29
+ "lprCategory": "C09",
30
+ "lprNumber": "999-999-999",
31
+ "commuterClassification": "C1",
32
+ "birthCountry": "Bahamas",
33
+ "birthDate": "1958-07-17"
34
+ },
35
+ "proof": {
36
+ "type": "BbsBlsSignature2020",
37
+ "created": "2026-02-24T18:31:50Z",
38
+ "proofPurpose": "assertionMethod",
39
+ "proofValue": "lvx5LJssQ6BVqMX3931+8EApBMmkYwydWl7xUeNBMnVNZSamwHmS5WLlqicauVofYUHlKfzccE4m7waZyoLEkBLFiK2g54Q2i+CdtYBgDdkUDsoULSBMcH1MwGHwdjfXpldFNFrHFx/IAvLVniyeMQ==",
40
+ "verificationMethod": "did:example:489398593#test"
41
+ }
42
+ }
@@ -0,0 +1,42 @@
1
+ {
2
+ "@context": [
3
+ "https://www.w3.org/2018/credentials/v1",
4
+ "https://w3id.org/citizenship/v1",
5
+ "https://w3id.org/security/bbs/v1"
6
+ ],
7
+ "id": "https://issuer.example.com/credentials/rich-001",
8
+ "type": [
9
+ "VerifiableCredential",
10
+ "PermanentResidentCard"
11
+ ],
12
+ "issuer": "did:example:489398593",
13
+ "identifier": "RICH-001",
14
+ "name": "Extended Resident Card",
15
+ "description": "Credential with many fields to test BBS+ signatures over larger statement counts.",
16
+ "issuanceDate": "2023-06-20T08:30:00Z",
17
+ "expirationDate": "2033-06-20T08:30:00Z",
18
+ "credentialSubject": {
19
+ "id": "did:example:user456",
20
+ "type": [
21
+ "PermanentResident",
22
+ "Person"
23
+ ],
24
+ "givenName": "Robert",
25
+ "familyName": "Williams",
26
+ "gender": "Male",
27
+ "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
28
+ "residentSince": "2018-03-15",
29
+ "lprCategory": "C12",
30
+ "lprNumber": "888-777-666",
31
+ "commuterClassification": "C2",
32
+ "birthCountry": "Canada",
33
+ "birthDate": "1985-11-23"
34
+ },
35
+ "proof": {
36
+ "type": "BbsBlsSignature2020",
37
+ "created": "2026-02-24T18:31:50Z",
38
+ "proofPurpose": "assertionMethod",
39
+ "proofValue": "h9Ax+SZHQsWCv0rXIFglLNAIJSNXF8yp7+MC+tlZsSzp2CWFPhRv8/dkgLTt9DRdBZv2+0Ch0WCIYewp/jE2bGDy4XALHFGj8hM5lW5hB4kio0Kglkol4OlKw+eZ8ujstHAB9XhFu7/XwAcKOB02TQ==",
40
+ "verificationMethod": "did:example:489398593#test"
41
+ }
42
+ }
package/test.mjs CHANGED
@@ -240,6 +240,44 @@ test('full demo_single flow: sign → verify → derive → verify derived', asy
240
240
  assert.equal(derivedVerifyResult.verified, true, `derived verify failed: ${derivedVerifyResult.error}`);
241
241
  });
242
242
 
243
+ test('demo_multi flow: sign twice → verify → derive → verify derived', async () => {
244
+ // Step 1: First sign
245
+ const signed = await ldSign({
246
+ document: inputDocument,
247
+ keyPair,
248
+ contexts,
249
+ });
250
+ assert.ok(signed.proof, 'signed document has proof');
251
+ assert.ok(!Array.isArray(signed.proof), 'single sign produces a single proof (not array)');
252
+
253
+ // Step 2: Second sign (creates multi-proof array)
254
+ const multiSigned = await ldSign({
255
+ document: signed,
256
+ keyPair,
257
+ contexts,
258
+ });
259
+ assert.ok(Array.isArray(multiSigned.proof), 'double sign produces proof array');
260
+ assert.equal(multiSigned.proof.length, 2, 'proof array has 2 proofs');
261
+ assert.equal(multiSigned.proof[0].type, 'BbsBlsSignature2020');
262
+ assert.equal(multiSigned.proof[1].type, 'BbsBlsSignature2020');
263
+
264
+ // Step 3: Verify multi-proof document
265
+ const verifyResult = await ldVerify({ document: multiSigned, contexts });
266
+ assert.equal(verifyResult.verified, true, `multi-proof verify failed: ${verifyResult.error}`);
267
+
268
+ // Step 4: Derive selective disclosure from multi-proof document
269
+ const derived = await ldDeriveProof({
270
+ document: multiSigned,
271
+ revealDocument,
272
+ contexts,
273
+ });
274
+ assert.ok(derived.proof, 'derived document has proof');
275
+
276
+ // Step 5: Verify derived proof
277
+ const derivedVerifyResult = await ldVerify({ document: derived, contexts });
278
+ assert.equal(derivedVerifyResult.verified, true, `multi-proof derived verify failed: ${derivedVerifyResult.error}`);
279
+ });
280
+
243
281
 
244
282
  //
245
283
  // BbsBlsSignature2020 class-based API tests
@@ -0,0 +1,287 @@
1
+ // Backward compatibility tests: verify that VCs signed by the old @mattrglobal
2
+ // library still verify correctly with the new NAPI implementation.
3
+ //
4
+ // These tests use static "golden file" fixtures generated by
5
+ // generate_golden_files.mjs using @mattrglobal/jsonld-signatures-bbs.
6
+ // The golden files represent VCs that may already exist in user wallets.
7
+ //
8
+ // Key findings:
9
+ // - BbsBlsSignature2020 (original signed VCs) are fully cross-compatible
10
+ // - BbsBlsSignatureProof2020 (derived proofs) are NOT cross-compatible between
11
+ // implementations, but this is acceptable because derived proofs are generated
12
+ // on-the-fly for presentations and never stored in wallets. The critical path
13
+ // is: load wallet VC → derive new proof with current implementation → verify.
14
+ //
15
+ // Run: cd vc/js && node test_backward_compat.mjs
16
+
17
+ import test from 'node:test';
18
+ import assert from 'node:assert';
19
+ import { readFileSync } from 'node:fs';
20
+ import { resolve, dirname } from 'node:path';
21
+ import { fileURLToPath } from 'node:url';
22
+ import { createRequire } from 'node:module';
23
+
24
+ // NAPI binding (new implementation)
25
+ import { ldVerify, ldDeriveProof } from './index.js';
26
+
27
+ // @mattrglobal library (old implementation) — for generation verification
28
+ const require = createRequire(import.meta.url);
29
+ const { BbsBlsSignature2020 } = require('@mattrglobal/jsonld-signatures-bbs');
30
+ const { verify: mattrVerify, purposes, extendContextLoader } = require('jsonld-signatures');
31
+
32
+ const __dirname = dirname(fileURLToPath(import.meta.url));
33
+ const dataDir = resolve(__dirname, 'test-data');
34
+ const goldenDir = resolve(dataDir, 'golden');
35
+
36
+ const loadJson = (name) => JSON.parse(readFileSync(resolve(dataDir, name), 'utf8'));
37
+ const loadGolden = (name) => JSON.parse(readFileSync(resolve(goldenDir, name), 'utf8'));
38
+
39
+ // Load context files
40
+ const keyPair = loadJson('keyPair.json');
41
+ const controllerDocument = loadJson('controllerDocument.json');
42
+ const citizenVocab = loadJson('citizenVocab.json');
43
+ const bbsContext = loadJson('bbs.json');
44
+ const credentialsContext = loadJson('credentialsContext.json');
45
+ const suiteContext = loadJson('suiteContext.json');
46
+ const revealDocument = loadJson('deriveProofFrame.json');
47
+
48
+ // NAPI contexts map
49
+ const napiContexts = {
50
+ "did:example:489398593#test": keyPair,
51
+ "did:example:489398593": controllerDocument,
52
+ "https://w3id.org/citizenship/v1": citizenVocab,
53
+ "https://w3id.org/security/bbs/v1": bbsContext,
54
+ "https://www.w3.org/2018/credentials/v1": credentialsContext,
55
+ "https://w3id.org/security/suites/jws-2020/v1": suiteContext,
56
+ };
57
+
58
+ // @mattrglobal document loader
59
+ const customDocLoader = (url) => {
60
+ const context = napiContexts[url];
61
+ if (context) return { contextUrl: null, document: context, documentUrl: url };
62
+ throw new Error(`Attempted to remote load context: '${url}'`);
63
+ };
64
+ const documentLoader = extendContextLoader(customDocLoader);
65
+
66
+ // Load golden files (signed by @mattrglobal)
67
+ const signedPRC = loadGolden('mattrglobal-signed-prc.json');
68
+ const signedMinimal = loadGolden('mattrglobal-signed-minimal.json');
69
+ const signedRich = loadGolden('mattrglobal-signed-rich.json');
70
+
71
+
72
+ //
73
+ // Wallet VC verification — the critical path
74
+ // VCs in user wallets have BbsBlsSignature2020 proofs signed by the old library.
75
+ // The new NAPI implementation must verify these without re-signing.
76
+ //
77
+
78
+ test('wallet-compat: NAPI ldVerify accepts @mattrglobal-signed VC', async () => {
79
+ const result = await ldVerify({
80
+ document: signedPRC,
81
+ contexts: napiContexts,
82
+ });
83
+
84
+ assert.equal(result.verified, true,
85
+ `NAPI ldVerify failed on @mattrglobal-signed VC: ${result.error || 'unknown error'}`);
86
+ });
87
+
88
+ test('wallet-compat: @mattrglobal verifies its own golden-file VC', async () => {
89
+ const result = await mattrVerify(signedPRC, {
90
+ suite: new BbsBlsSignature2020(),
91
+ purpose: new purposes.AssertionProofPurpose(),
92
+ documentLoader,
93
+ });
94
+
95
+ assert.equal(result.verified, true,
96
+ `@mattrglobal verify failed on its own golden-file VC: ${JSON.stringify(result.error)}`);
97
+ });
98
+
99
+
100
+ //
101
+ // Selective disclosure — the real-world flow for wallet VCs:
102
+ // 1. Load signed VC from wallet (signed by old library)
103
+ // 2. Derive a proof using the current (NAPI) implementation
104
+ // 3. Verify the derived proof using the current (NAPI) implementation
105
+ //
106
+
107
+ test('wallet-compat: NAPI ldDeriveProof works on @mattrglobal-signed VC', async () => {
108
+ const derived = await ldDeriveProof({
109
+ document: signedPRC,
110
+ revealDocument,
111
+ contexts: napiContexts,
112
+ });
113
+
114
+ assert.ok(derived, 'ldDeriveProof returned a result');
115
+ assert.ok(derived.proof, 'derived document has a proof');
116
+ assert.equal(derived.proof.type, 'BbsBlsSignatureProof2020');
117
+
118
+ // Verify selective disclosure: only requested fields present
119
+ assert.equal(derived.credentialSubject.givenName, 'JOHN');
120
+ assert.equal(derived.credentialSubject.familyName, 'SMITH');
121
+ assert.equal(derived.credentialSubject.gender, 'Male');
122
+ assert.equal(derived.credentialSubject.birthDate, undefined, 'birthDate should be hidden');
123
+ assert.equal(derived.credentialSubject.lprNumber, undefined, 'lprNumber should be hidden');
124
+ });
125
+
126
+ test('wallet-compat: NAPI verifies its own derived proof from @mattrglobal-signed VC', async () => {
127
+ const derived = await ldDeriveProof({
128
+ document: signedPRC,
129
+ revealDocument,
130
+ contexts: napiContexts,
131
+ });
132
+
133
+ const result = await ldVerify({
134
+ document: derived,
135
+ contexts: napiContexts,
136
+ });
137
+
138
+ assert.equal(result.verified, true,
139
+ `NAPI ldVerify failed on NAPI-derived proof: ${result.error || 'unknown error'}`);
140
+ });
141
+
142
+
143
+ //
144
+ // Tamper detection — NAPI must reject modified @mattrglobal-signed VCs
145
+ //
146
+
147
+ test('wallet-compat: NAPI rejects tampered @mattrglobal-signed VC', async () => {
148
+ const tampered = JSON.parse(JSON.stringify(signedPRC));
149
+ tampered.credentialSubject.givenName = 'TAMPERED';
150
+
151
+ const result = await ldVerify({
152
+ document: tampered,
153
+ contexts: napiContexts,
154
+ });
155
+
156
+ assert.equal(result.verified, false, 'tampered VC should not verify');
157
+ });
158
+
159
+ test('wallet-compat: NAPI rejects @mattrglobal-signed VC with altered proof', async () => {
160
+ const altered = JSON.parse(JSON.stringify(signedPRC));
161
+ // Flip a character in the proof value
162
+ altered.proof.proofValue = 'X' + altered.proof.proofValue.slice(1);
163
+
164
+ // The Rust BBS crate may panic on malformed proof data rather than returning
165
+ // verified: false. Both behaviors correctly reject the tampered proof.
166
+ try {
167
+ const result = await ldVerify({
168
+ document: altered,
169
+ contexts: napiContexts,
170
+ });
171
+ assert.equal(result.verified, false, 'altered proof should not verify');
172
+ } catch (err) {
173
+ // Panic or error thrown — proof was rejected, which is the correct behavior
174
+ assert.ok(true, `proof correctly rejected with error: ${err.message}`);
175
+ }
176
+ });
177
+
178
+
179
+ //
180
+ // Multiple selective disclosure frames — verify different field combinations
181
+ // work when deriving from an @mattrglobal-signed VC
182
+ //
183
+
184
+ test('wallet-compat: derive minimal proof (type only) from @mattrglobal-signed VC', async () => {
185
+ const minimalFrame = {
186
+ "@context": signedPRC["@context"],
187
+ "type": ["VerifiableCredential", "PermanentResidentCard"],
188
+ "credentialSubject": {
189
+ "@explicit": true,
190
+ "type": ["PermanentResident", "Person"],
191
+ }
192
+ };
193
+
194
+ const derived = await ldDeriveProof({
195
+ document: signedPRC,
196
+ revealDocument: minimalFrame,
197
+ contexts: napiContexts,
198
+ });
199
+
200
+ assert.ok(derived.proof, 'derived document has a proof');
201
+ assert.equal(derived.proof.type, 'BbsBlsSignatureProof2020');
202
+ // Only type should be present, no PII fields
203
+ assert.equal(derived.credentialSubject.givenName, undefined);
204
+ assert.equal(derived.credentialSubject.familyName, undefined);
205
+
206
+ // Verify the minimal derived proof
207
+ const result = await ldVerify({ document: derived, contexts: napiContexts });
208
+ assert.equal(result.verified, true,
209
+ `minimal derived proof failed verification: ${result.error || 'unknown error'}`);
210
+ });
211
+
212
+
213
+ //
214
+ // VC variant tests — different field counts exercise different BBS+ statement counts.
215
+ // BBS+ signatures encode each field as a separate "message" (statement). Verifying
216
+ // VCs with different field counts ensures the NAPI implementation handles varying
217
+ // statement counts correctly, matching the old library's behavior.
218
+ //
219
+
220
+ test('variant: NAPI ldVerify accepts @mattrglobal-signed minimal VC (few fields)', async () => {
221
+ const result = await ldVerify({
222
+ document: signedMinimal,
223
+ contexts: napiContexts,
224
+ });
225
+
226
+ assert.equal(result.verified, true,
227
+ `NAPI ldVerify failed on minimal VC: ${result.error || 'unknown error'}`);
228
+ });
229
+
230
+ test('variant: NAPI ldVerify accepts @mattrglobal-signed rich VC (many fields)', async () => {
231
+ const result = await ldVerify({
232
+ document: signedRich,
233
+ contexts: napiContexts,
234
+ });
235
+
236
+ assert.equal(result.verified, true,
237
+ `NAPI ldVerify failed on rich VC: ${result.error || 'unknown error'}`);
238
+ });
239
+
240
+ test('variant: NAPI ldDeriveProof + ldVerify on minimal VC', async () => {
241
+ // Minimal VC only has givenName and familyName — derive with type-only frame
242
+ const minimalFrame = {
243
+ "@context": signedMinimal["@context"],
244
+ "type": ["VerifiableCredential", "PermanentResidentCard"],
245
+ "credentialSubject": {
246
+ "@explicit": true,
247
+ "type": ["PermanentResident", "Person"],
248
+ "givenName": {},
249
+ }
250
+ };
251
+
252
+ const derived = await ldDeriveProof({
253
+ document: signedMinimal,
254
+ revealDocument: minimalFrame,
255
+ contexts: napiContexts,
256
+ });
257
+
258
+ assert.ok(derived.proof, 'derived document has a proof');
259
+ assert.equal(derived.credentialSubject.givenName, 'Alice');
260
+ assert.equal(derived.credentialSubject.familyName, undefined, 'familyName should be hidden');
261
+
262
+ const result = await ldVerify({ document: derived, contexts: napiContexts });
263
+ assert.equal(result.verified, true,
264
+ `minimal VC derived proof failed: ${result.error || 'unknown error'}`);
265
+ });
266
+
267
+ test('variant: NAPI ldDeriveProof + ldVerify on rich VC', async () => {
268
+ // Rich VC has many fields — derive revealing only a few
269
+ const derived = await ldDeriveProof({
270
+ document: signedRich,
271
+ revealDocument,
272
+ contexts: napiContexts,
273
+ });
274
+
275
+ assert.ok(derived.proof, 'derived document has a proof');
276
+ assert.equal(derived.credentialSubject.givenName, 'Robert');
277
+ assert.equal(derived.credentialSubject.familyName, 'Williams');
278
+ assert.equal(derived.credentialSubject.gender, 'Male');
279
+ // Fields not in reveal frame should be hidden
280
+ assert.equal(derived.credentialSubject.birthDate, undefined, 'birthDate should be hidden');
281
+ assert.equal(derived.credentialSubject.lprNumber, undefined, 'lprNumber should be hidden');
282
+ assert.equal(derived.credentialSubject.birthCountry, undefined, 'birthCountry should be hidden');
283
+
284
+ const result = await ldVerify({ document: derived, contexts: napiContexts });
285
+ assert.equal(result.verified, true,
286
+ `rich VC derived proof failed: ${result.error || 'unknown error'}`);
287
+ });