@nuggetslife/vc 0.0.23 → 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,195 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert';
3
+ import {
4
+ bbsIetfKeygen,
5
+ bbs2023Sign,
6
+ bbs2023Derive,
7
+ bbs2023Verify,
8
+ } from './index.js';
9
+
10
+ // =====================================================================
11
+ // Helpers
12
+ // =====================================================================
13
+
14
+ function makeKeyPair() {
15
+ const kp = bbsIetfKeygen();
16
+ return { secretKey: kp.secretKey, publicKey: kp.publicKey };
17
+ }
18
+
19
+ function sampleCredential() {
20
+ return {
21
+ '@context': [
22
+ 'https://www.w3.org/2018/credentials/v1',
23
+ {
24
+ name: 'http://schema.org/name',
25
+ age: 'http://schema.org/age',
26
+ email: 'http://schema.org/email',
27
+ alumniOf: 'http://schema.org/alumniOf',
28
+ },
29
+ ],
30
+ type: ['VerifiableCredential'],
31
+ issuer: 'did:example:issuer',
32
+ issuanceDate: '2024-01-01T00:00:00Z',
33
+ credentialSubject: {
34
+ id: 'did:example:user123',
35
+ name: 'Alice',
36
+ age: 30,
37
+ email: 'alice@example.com',
38
+ alumniOf: 'Example University',
39
+ },
40
+ };
41
+ }
42
+
43
+ // =====================================================================
44
+ // BBS-2023 Round-Trip: Sign → Verify Base Proof
45
+ // =====================================================================
46
+
47
+ test('bbs-2023: sign and verify base proof', () => {
48
+ const kp = makeKeyPair();
49
+ const doc = sampleCredential();
50
+
51
+ const signed = bbs2023Sign({
52
+ document: doc,
53
+ secretKey: kp.secretKey,
54
+ publicKey: kp.publicKey,
55
+ mandatoryPointers: ['/issuer', '/issuanceDate'],
56
+ verificationMethod: 'did:example:issuer#bbs-key-1',
57
+ proofPurpose: 'assertionMethod',
58
+ });
59
+
60
+ assert.ok(signed.proof, 'signed document has proof');
61
+ assert.equal(signed.proof.type, 'DataIntegrityProof');
62
+ assert.equal(signed.proof.cryptosuite, 'bbs-2023');
63
+ assert.ok(signed.proof.proofValue.startsWith('u'), 'proofValue starts with multibase u');
64
+
65
+ // Verify
66
+ const result = bbs2023Verify(signed, kp.publicKey);
67
+ assert.ok(result.verified, 'base proof verified');
68
+ assert.equal(result.error, null);
69
+ });
70
+
71
+ // =====================================================================
72
+ // BBS-2023 Full Round-Trip: Sign → Derive → Verify
73
+ // =====================================================================
74
+
75
+ test('bbs-2023: sign → derive → verify round-trip', () => {
76
+ const kp = makeKeyPair();
77
+ const doc = sampleCredential();
78
+
79
+ // Issuer signs
80
+ const signed = bbs2023Sign({
81
+ document: doc,
82
+ secretKey: kp.secretKey,
83
+ publicKey: kp.publicKey,
84
+ mandatoryPointers: ['/issuer', '/issuanceDate'],
85
+ });
86
+
87
+ // Holder derives selective disclosure
88
+ const derived = bbs2023Derive({
89
+ document: signed,
90
+ selectivePointers: ['/credentialSubject/name'],
91
+ });
92
+
93
+ assert.ok(derived.proof, 'derived document has proof');
94
+ assert.equal(derived.proof.cryptosuite, 'bbs-2023');
95
+
96
+ // Verifier verifies
97
+ const result = bbs2023Verify(derived, kp.publicKey);
98
+ assert.ok(result.verified, 'derived proof verified');
99
+ assert.equal(result.error, null);
100
+ });
101
+
102
+ // =====================================================================
103
+ // Wrong Key Rejection
104
+ // =====================================================================
105
+
106
+ test('bbs-2023: verification fails with wrong key', () => {
107
+ const kp = makeKeyPair();
108
+ const wrongKp = makeKeyPair();
109
+ const doc = sampleCredential();
110
+
111
+ const signed = bbs2023Sign({
112
+ document: doc,
113
+ secretKey: kp.secretKey,
114
+ publicKey: kp.publicKey,
115
+ mandatoryPointers: ['/issuer'],
116
+ });
117
+
118
+ const result = bbs2023Verify(signed, wrongKp.publicKey);
119
+ assert.equal(result.verified, false, 'should not verify with wrong key');
120
+ });
121
+
122
+ // =====================================================================
123
+ // Mandatory-Only (No Selective Disclosure)
124
+ // =====================================================================
125
+
126
+ test('bbs-2023: derive with no selective pointers', () => {
127
+ const kp = makeKeyPair();
128
+ const doc = sampleCredential();
129
+
130
+ const signed = bbs2023Sign({
131
+ document: doc,
132
+ secretKey: kp.secretKey,
133
+ publicKey: kp.publicKey,
134
+ mandatoryPointers: ['/issuer', '/issuanceDate'],
135
+ });
136
+
137
+ // Derive with no selective pointers — only mandatory fields disclosed
138
+ const derived = bbs2023Derive({
139
+ document: signed,
140
+ selectivePointers: [],
141
+ });
142
+
143
+ const result = bbs2023Verify(derived, kp.publicKey);
144
+ assert.ok(result.verified, 'mandatory-only derived proof verified');
145
+ });
146
+
147
+ // =====================================================================
148
+ // All Fields Selective
149
+ // =====================================================================
150
+
151
+ test('bbs-2023: derive revealing all fields', () => {
152
+ const kp = makeKeyPair();
153
+ const doc = sampleCredential();
154
+
155
+ const signed = bbs2023Sign({
156
+ document: doc,
157
+ secretKey: kp.secretKey,
158
+ publicKey: kp.publicKey,
159
+ mandatoryPointers: ['/issuer'],
160
+ });
161
+
162
+ // Reveal everything that's non-mandatory
163
+ const derived = bbs2023Derive({
164
+ document: signed,
165
+ selectivePointers: [
166
+ '/credentialSubject/name',
167
+ '/credentialSubject/age',
168
+ '/credentialSubject/email',
169
+ '/credentialSubject/alumniOf',
170
+ '/issuanceDate',
171
+ ],
172
+ });
173
+
174
+ const result = bbs2023Verify(derived, kp.publicKey);
175
+ assert.ok(result.verified, 'all-selective derived proof verified');
176
+ });
177
+
178
+ // =====================================================================
179
+ // No Mandatory Pointers
180
+ // =====================================================================
181
+
182
+ test('bbs-2023: sign with no mandatory pointers', () => {
183
+ const kp = makeKeyPair();
184
+ const doc = sampleCredential();
185
+
186
+ const signed = bbs2023Sign({
187
+ document: doc,
188
+ secretKey: kp.secretKey,
189
+ publicKey: kp.publicKey,
190
+ mandatoryPointers: [],
191
+ });
192
+
193
+ const result = bbs2023Verify(signed, kp.publicKey);
194
+ assert.ok(result.verified, 'base proof with no mandatory pointers verified');
195
+ });
@@ -0,0 +1,168 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert';
3
+ import {
4
+ bbsIetfKeygen,
5
+ bbsIetfSign,
6
+ bbsIetfVerify,
7
+ bbsIetfProofGen,
8
+ bbsIetfProofVerify,
9
+ } from './index.js';
10
+
11
+ // =====================================================================
12
+ // Helper
13
+ // =====================================================================
14
+
15
+ function makeKeyPair(ciphersuite) {
16
+ // Generate a 32-byte IKM
17
+ const ikm = Buffer.alloc(32);
18
+ for (let i = 0; i < 32; i++) ikm[i] = i + 1;
19
+
20
+ const kp = bbsIetfKeygen(ikm, null, ciphersuite);
21
+ return {
22
+ secretKey: Buffer.from(kp.secretKey, 'hex'),
23
+ publicKey: Buffer.from(kp.publicKey, 'hex'),
24
+ };
25
+ }
26
+
27
+ // =====================================================================
28
+ // KeyGen Tests
29
+ // =====================================================================
30
+
31
+ test('BBS IETF keygen produces deterministic keys', () => {
32
+ const ikm = Buffer.alloc(32, 42);
33
+ const kp1 = bbsIetfKeygen(ikm);
34
+ const kp2 = bbsIetfKeygen(ikm);
35
+ assert.equal(kp1.secretKey, kp2.secretKey, 'same IKM -> same SK');
36
+ assert.equal(kp1.publicKey, kp2.publicKey, 'same IKM -> same PK');
37
+ });
38
+
39
+ test('BBS IETF keygen with random IKM', () => {
40
+ const kp = bbsIetfKeygen();
41
+ assert.ok(kp.secretKey, 'SK is present');
42
+ assert.ok(kp.publicKey, 'PK is present');
43
+ assert.equal(kp.secretKey.length, 64, 'SK is 32 bytes hex');
44
+ assert.equal(kp.publicKey.length, 192, 'PK is 96 bytes hex');
45
+ });
46
+
47
+ // =====================================================================
48
+ // Sign / Verify Round-Trip (SHA-256)
49
+ // =====================================================================
50
+
51
+ test('BBS IETF sign/verify round-trip (SHA-256)', () => {
52
+ const kp = makeKeyPair('SHA-256');
53
+ const messages = ['message1', 'message2', 'message3'];
54
+ const header = Buffer.from('test-header');
55
+
56
+ const signature = bbsIetfSign(kp.secretKey, kp.publicKey, header, messages, 'SHA-256');
57
+ assert.equal(signature.length, 80, 'signature is 80 bytes');
58
+
59
+ const verified = bbsIetfVerify(kp.publicKey, signature, header, messages, 'SHA-256');
60
+ assert.ok(verified, 'valid signature verifies');
61
+ });
62
+
63
+ // =====================================================================
64
+ // Sign / Verify Round-Trip (SHAKE-256)
65
+ // =====================================================================
66
+
67
+ test('BBS IETF sign/verify round-trip (SHAKE-256)', () => {
68
+ const ikm = Buffer.alloc(32, 99);
69
+ const kp = bbsIetfKeygen(ikm, null, 'SHAKE-256');
70
+ const sk = Buffer.from(kp.secretKey, 'hex');
71
+ const pk = Buffer.from(kp.publicKey, 'hex');
72
+
73
+ const messages = ['hello', 'world'];
74
+ const signature = bbsIetfSign(sk, pk, null, messages, 'SHAKE-256');
75
+ assert.equal(signature.length, 80);
76
+
77
+ const verified = bbsIetfVerify(pk, signature, null, messages, 'SHAKE-256');
78
+ assert.ok(verified, 'SHAKE-256 signature verifies');
79
+ });
80
+
81
+ // =====================================================================
82
+ // Wrong Key Rejection
83
+ // =====================================================================
84
+
85
+ test('BBS IETF wrong key rejected', () => {
86
+ const kp1 = makeKeyPair('SHA-256');
87
+ const kp2Raw = bbsIetfKeygen(); // different key (random IKM)
88
+ const kp2 = {
89
+ secretKey: Buffer.from(kp2Raw.secretKey, 'hex'),
90
+ publicKey: Buffer.from(kp2Raw.publicKey, 'hex'),
91
+ };
92
+
93
+ const messages = ['msg1', 'msg2'];
94
+ const signature = bbsIetfSign(kp1.secretKey, kp1.publicKey, null, messages, 'SHA-256');
95
+
96
+ const verified = bbsIetfVerify(kp2.publicKey, signature, null, messages, 'SHA-256');
97
+ assert.ok(!verified, 'wrong key should not verify');
98
+ });
99
+
100
+ // =====================================================================
101
+ // Selective Disclosure (ProofGen -> ProofVerify)
102
+ // =====================================================================
103
+
104
+ test('BBS IETF selective disclosure proof', () => {
105
+ const kp = makeKeyPair('SHA-256');
106
+ const messages = ['claim-a', 'claim-b', 'claim-c', 'claim-d'];
107
+ const header = Buffer.from('proof-header');
108
+ const ph = Buffer.from('presentation-header');
109
+
110
+ // Sign all messages
111
+ const signature = bbsIetfSign(kp.secretKey, kp.publicKey, header, messages, 'SHA-256');
112
+
113
+ // Generate proof disclosing messages at indices 0 and 2
114
+ const disclosedIndices = [0, 2];
115
+ const proof = bbsIetfProofGen(
116
+ kp.publicKey, signature, header, ph, messages, disclosedIndices, 'SHA-256'
117
+ );
118
+ assert.ok(proof.length > 0, 'proof generated');
119
+
120
+ // Build disclosed messages map
121
+ const disclosedMessages = {};
122
+ for (const idx of disclosedIndices) {
123
+ disclosedMessages[idx] = messages[idx];
124
+ }
125
+
126
+ // Verify proof
127
+ const verified = bbsIetfProofVerify(
128
+ kp.publicKey, proof, header, ph, disclosedMessages, messages.length, 'SHA-256'
129
+ );
130
+ assert.ok(verified, 'proof verifies with correct disclosed messages');
131
+ });
132
+
133
+ // =====================================================================
134
+ // Proof with wrong disclosed message
135
+ // =====================================================================
136
+
137
+ test('BBS IETF proof rejects wrong disclosed message', () => {
138
+ const kp = makeKeyPair('SHA-256');
139
+ const messages = ['msg0', 'msg1', 'msg2'];
140
+ const header = Buffer.from('h');
141
+
142
+ const signature = bbsIetfSign(kp.secretKey, kp.publicKey, header, messages, 'SHA-256');
143
+
144
+ const proof = bbsIetfProofGen(
145
+ kp.publicKey, signature, header, null, messages, [1], 'SHA-256'
146
+ );
147
+
148
+ // Provide wrong message for disclosed index
149
+ const verified = bbsIetfProofVerify(
150
+ kp.publicKey, proof, header, null, { 1: 'wrong-message' }, messages.length, 'SHA-256'
151
+ );
152
+ assert.ok(!verified, 'wrong disclosed message should not verify');
153
+ });
154
+
155
+ // =====================================================================
156
+ // Cross-ciphersuite: SHA-256 vs SHAKE-256
157
+ // =====================================================================
158
+
159
+ test('BBS IETF cross-ciphersuite: SHA-256 sig not verifiable with SHAKE-256', () => {
160
+ const kp = makeKeyPair('SHA-256');
161
+ const messages = ['test'];
162
+
163
+ const signature = bbsIetfSign(kp.secretKey, kp.publicKey, null, messages, 'SHA-256');
164
+
165
+ // Try verifying with SHAKE-256 — should fail
166
+ const verified = bbsIetfVerify(kp.publicKey, signature, null, messages, 'SHAKE-256');
167
+ assert.ok(!verified, 'SHA-256 signature should not verify under SHAKE-256');
168
+ });
@@ -0,0 +1,197 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert';
3
+ import {
4
+ JoseNamedCurve,
5
+ generateJwk,
6
+ sdJwtIssue,
7
+ sdJwtPresent,
8
+ sdJwtVerify,
9
+ } from './index.js';
10
+
11
+ function makeKeyPair(curve = JoseNamedCurve.P256) {
12
+ const jwk = generateJwk(curve);
13
+ const { d, ...pubJwk } = jwk;
14
+ return { privateJwk: jwk, publicJwk: pubJwk };
15
+ }
16
+
17
+ // =====================================================================
18
+ // SD-JWT Round-Trip Tests
19
+ // =====================================================================
20
+
21
+ test('SD-JWT round-trip with ES256', () => {
22
+ const issuer = makeKeyPair(JoseNamedCurve.P256);
23
+
24
+ const claims = { sub: 'user123', name: 'Alice', email: 'alice@example.com', age: 30 };
25
+ const result = sdJwtIssue(claims, ['name', 'email'], issuer.privateJwk, 'ES256');
26
+
27
+ assert.ok(result.sdJwt, 'sd_jwt is present');
28
+ assert.ok(result.sdJwt.includes('~'), 'sd_jwt contains tilde separators');
29
+ assert.equal(result.disclosures.length, 2, 'two disclosures');
30
+
31
+ // Present all disclosures
32
+ const allEncoded = result.disclosures.map(d => d.encoded);
33
+ const presentation = sdJwtPresent(result.sdJwt, allEncoded);
34
+
35
+ assert.ok(presentation.presentation, 'presentation is present');
36
+
37
+ // Verify
38
+ const verified = sdJwtVerify(presentation.presentation, issuer.publicJwk);
39
+ assert.ok(verified.verified, 'verification succeeded');
40
+ assert.equal(verified.claims.sub, 'user123');
41
+ assert.equal(verified.claims.name, 'Alice');
42
+ assert.equal(verified.claims.email, 'alice@example.com');
43
+ assert.equal(verified.claims.age, 30);
44
+ assert.equal(verified.error, null);
45
+ });
46
+
47
+ test('SD-JWT round-trip with EdDSA (Ed25519)', () => {
48
+ const issuer = makeKeyPair(JoseNamedCurve.Ed25519);
49
+
50
+ const claims = { sub: 'user456', name: 'Bob' };
51
+ const result = sdJwtIssue(claims, ['name'], issuer.privateJwk, 'EdDSA');
52
+
53
+ assert.equal(result.disclosures.length, 1);
54
+
55
+ const allEncoded = result.disclosures.map(d => d.encoded);
56
+ const presentation = sdJwtPresent(result.sdJwt, allEncoded);
57
+ const verified = sdJwtVerify(presentation.presentation, issuer.publicJwk);
58
+
59
+ assert.ok(verified.verified);
60
+ assert.equal(verified.claims.name, 'Bob');
61
+ });
62
+
63
+ // =====================================================================
64
+ // Selective Reveal
65
+ // =====================================================================
66
+
67
+ test('SD-JWT selective reveal: only name disclosed', () => {
68
+ const issuer = makeKeyPair();
69
+
70
+ const claims = { sub: 'user123', name: 'Alice', email: 'alice@example.com', age: 30 };
71
+ const result = sdJwtIssue(claims, ['name', 'email', 'age'], issuer.privateJwk, 'ES256');
72
+
73
+ // Only reveal name
74
+ const nameDisclosure = result.disclosures.find(d => d.claimName === 'name');
75
+ const presentation = sdJwtPresent(result.sdJwt, [nameDisclosure.encoded]);
76
+ const verified = sdJwtVerify(presentation.presentation, issuer.publicJwk);
77
+
78
+ assert.ok(verified.verified);
79
+ assert.equal(verified.claims.sub, 'user123', 'sub is always visible');
80
+ assert.equal(verified.claims.name, 'Alice', 'name is disclosed');
81
+ assert.equal(verified.claims.email, undefined, 'email is NOT disclosed');
82
+ assert.equal(verified.claims.age, undefined, 'age is NOT disclosed');
83
+ });
84
+
85
+ // =====================================================================
86
+ // Wrong Key Rejection
87
+ // =====================================================================
88
+
89
+ test('SD-JWT verification fails with wrong issuer key', () => {
90
+ const issuer = makeKeyPair();
91
+ const wrongKey = makeKeyPair();
92
+
93
+ const claims = { sub: 'user', name: 'Test' };
94
+ const result = sdJwtIssue(claims, ['name'], issuer.privateJwk, 'ES256');
95
+
96
+ const allEncoded = result.disclosures.map(d => d.encoded);
97
+ const presentation = sdJwtPresent(result.sdJwt, allEncoded);
98
+
99
+ assert.throws(() => {
100
+ sdJwtVerify(presentation.presentation, wrongKey.publicJwk);
101
+ }, 'should throw with wrong key');
102
+ });
103
+
104
+ // =====================================================================
105
+ // Nested Objects
106
+ // =====================================================================
107
+
108
+ test('SD-JWT with nested object disclosure', () => {
109
+ const issuer = makeKeyPair();
110
+
111
+ const claims = {
112
+ sub: 'user123',
113
+ address: {
114
+ street: '123 Main St',
115
+ city: 'Springfield',
116
+ state: 'IL',
117
+ },
118
+ };
119
+ const result = sdJwtIssue(
120
+ claims,
121
+ ['address.street', 'address.city'],
122
+ issuer.privateJwk,
123
+ 'ES256',
124
+ );
125
+
126
+ assert.equal(result.disclosures.length, 2);
127
+
128
+ // Reveal only street
129
+ const streetDisclosure = result.disclosures.find(d => d.claimName === 'street');
130
+ const presentation = sdJwtPresent(result.sdJwt, [streetDisclosure.encoded]);
131
+ const verified = sdJwtVerify(presentation.presentation, issuer.publicJwk);
132
+
133
+ assert.ok(verified.verified);
134
+ assert.equal(verified.claims.address.street, '123 Main St');
135
+ assert.equal(verified.claims.address.state, 'IL', 'state always visible');
136
+ assert.equal(verified.claims.address.city, undefined, 'city not disclosed');
137
+ });
138
+
139
+ // =====================================================================
140
+ // Key Binding
141
+ // =====================================================================
142
+
143
+ test('SD-JWT with Key Binding JWT', () => {
144
+ const issuer = makeKeyPair();
145
+ const holder = makeKeyPair();
146
+
147
+ const claims = { sub: 'user123', name: 'Alice' };
148
+ const result = sdJwtIssue(
149
+ claims,
150
+ ['name'],
151
+ issuer.privateJwk,
152
+ 'ES256',
153
+ holder.publicJwk,
154
+ );
155
+
156
+ const allEncoded = result.disclosures.map(d => d.encoded);
157
+ const presentation = sdJwtPresent(
158
+ result.sdJwt,
159
+ allEncoded,
160
+ holder.privateJwk,
161
+ 'ES256',
162
+ 'https://verifier.example.com',
163
+ 'nonce123',
164
+ );
165
+
166
+ const verified = sdJwtVerify(
167
+ presentation.presentation,
168
+ issuer.publicJwk,
169
+ holder.publicJwk,
170
+ 'https://verifier.example.com',
171
+ 'nonce123',
172
+ );
173
+
174
+ assert.ok(verified.verified);
175
+ assert.equal(verified.claims.name, 'Alice');
176
+ assert.equal(verified.error, null);
177
+ });
178
+
179
+ // =====================================================================
180
+ // Decoys
181
+ // =====================================================================
182
+
183
+ test('SD-JWT with decoy digests', () => {
184
+ const issuer = makeKeyPair();
185
+
186
+ const claims = { sub: 'user123', name: 'Alice' };
187
+ const result = sdJwtIssue(claims, ['name'], issuer.privateJwk, 'ES256', null, 3);
188
+
189
+ assert.equal(result.disclosures.length, 1, 'only 1 real disclosure');
190
+
191
+ const allEncoded = result.disclosures.map(d => d.encoded);
192
+ const presentation = sdJwtPresent(result.sdJwt, allEncoded);
193
+ const verified = sdJwtVerify(presentation.presentation, issuer.publicJwk);
194
+
195
+ assert.ok(verified.verified);
196
+ assert.equal(verified.claims.name, 'Alice');
197
+ });