@docknetwork/wallet-sdk-wasm 1.5.14 → 1.7.0
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.
- package/generate-docs.js +49 -0
- package/jsdoc.conf.json +29 -6
- package/lib/index.js +8 -1
- package/lib/index.mjs +8 -1
- package/lib/rpc-server.js +10 -1
- package/lib/rpc-server.mjs +10 -1
- package/lib/services/blockchain/cached-did-resolver.js +113 -0
- package/lib/services/blockchain/cached-did-resolver.mjs +109 -0
- package/lib/services/blockchain/index.js +11 -0
- package/lib/services/blockchain/index.mjs +11 -0
- package/lib/services/blockchain/service-rpc.js +12 -0
- package/lib/services/blockchain/service-rpc.mjs +12 -0
- package/lib/services/blockchain/service.js +140 -11
- package/lib/services/blockchain/service.mjs +140 -11
- package/lib/services/credential/bbs-revocation.js +11 -0
- package/lib/services/credential/bbs-revocation.mjs +11 -0
- package/lib/services/credential/config.js +4 -1
- package/lib/services/credential/config.mjs +4 -1
- package/lib/services/credential/index.js +14 -0
- package/lib/services/credential/index.mjs +14 -0
- package/lib/services/credential/sd-jwt.js +214 -0
- package/lib/services/credential/sd-jwt.mjs +200 -0
- package/lib/services/credential/service-rpc.js +9 -0
- package/lib/services/credential/service-rpc.mjs +9 -0
- package/lib/services/credential/service.js +324 -7
- package/lib/services/credential/service.mjs +324 -7
- package/lib/services/edv/service.js +145 -1
- package/lib/services/edv/service.mjs +145 -1
- package/lib/services/index.js +13 -0
- package/lib/services/index.mjs +13 -0
- package/lib/services/relay-service/service.js +124 -1
- package/lib/services/relay-service/service.mjs +124 -1
- package/lib/services/rpc-service-client.js +0 -3
- package/lib/services/rpc-service-client.mjs +0 -3
- package/lib/services/storage/index.js +19 -2
- package/lib/services/storage/index.mjs +24 -1
- package/lib/services/storage/service-rpc.js +7 -3
- package/lib/services/storage/service-rpc.mjs +7 -3
- package/lib/services/storage/service.js +4 -0
- package/lib/services/storage/service.mjs +4 -0
- package/lib/setup-nodejs.js +8 -1
- package/lib/setup-nodejs.mjs +8 -1
- package/lib/setup-tests.js +8 -1
- package/lib/setup-tests.mjs +8 -1
- package/lib/src/services/blockchain/cached-did-resolver.d.ts +28 -0
- package/lib/src/services/blockchain/cached-did-resolver.d.ts.map +1 -0
- package/lib/src/services/blockchain/cached-did-resolver.test.d.ts +2 -0
- package/lib/src/services/blockchain/cached-did-resolver.test.d.ts.map +1 -0
- package/lib/src/services/blockchain/service.d.ts +114 -17
- package/lib/src/services/blockchain/service.d.ts.map +1 -1
- package/lib/src/services/credential/config.d.ts.map +1 -1
- package/lib/src/services/credential/index.d.ts +3 -0
- package/lib/src/services/credential/index.d.ts.map +1 -1
- package/lib/src/services/credential/sd-jwt.test.d.ts +2 -0
- package/lib/src/services/credential/sd-jwt.test.d.ts.map +1 -0
- package/lib/src/services/credential/service.d.ts +274 -4
- package/lib/src/services/credential/service.d.ts.map +1 -1
- package/lib/src/services/edv/service.d.ts +151 -1
- package/lib/src/services/edv/service.d.ts.map +1 -1
- package/lib/src/services/relay-service/service.d.ts +129 -1
- package/lib/src/services/relay-service/service.d.ts.map +1 -1
- package/lib/src/services/rpc-service-client.d.ts +2 -2
- package/lib/src/services/rpc-service-client.d.ts.map +1 -1
- package/lib/src/services/storage/index.d.ts +1 -1
- package/lib/src/services/storage/index.d.ts.map +1 -1
- package/lib/src/services/storage/service-rpc.d.ts +9 -0
- package/lib/src/services/storage/service-rpc.d.ts.map +1 -0
- package/lib/src/services/storage/service.d.ts +1 -0
- package/lib/src/services/storage/service.d.ts.map +1 -1
- package/lib/src/services/util-crypto/service.d.ts +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/wallet/rpc-storage-interface.js +13 -3
- package/lib/wallet/rpc-storage-interface.mjs +11 -1
- package/lib/wallet/rpc-storage-wallet.js +10 -0
- package/lib/wallet/rpc-storage-wallet.mjs +10 -0
- package/package.json +13 -8
- package/src/services/blockchain/cached-did-resolver.test.ts +288 -0
- package/src/services/blockchain/cached-did-resolver.ts +126 -0
- package/src/services/blockchain/service-rpc.js +12 -0
- package/src/services/blockchain/service.ts +142 -11
- package/src/services/credential/config.ts +7 -1
- package/src/services/credential/sd-jwt.test.ts +718 -0
- package/src/services/credential/sd-jwt.ts +231 -0
- package/src/services/credential/service-rpc.js +9 -0
- package/src/services/credential/service.ts +328 -7
- package/src/services/edv/service.ts +153 -1
- package/src/services/relay-service/service.ts +130 -1
- package/src/services/rpc-service-client.js +0 -3
- package/src/services/storage/index.js +15 -1
- package/src/services/storage/service-rpc.js +7 -3
- package/src/services/storage/service.ts +5 -0
|
@@ -0,0 +1,718 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isSDJWTCredential,
|
|
3
|
+
sdJwtToW3C,
|
|
4
|
+
decodeSDJWTToW3C,
|
|
5
|
+
credentialToW3C,
|
|
6
|
+
verifySDJWT,
|
|
7
|
+
decodeSDJWT,
|
|
8
|
+
createSDJWTPresentation,
|
|
9
|
+
} from './sd-jwt';
|
|
10
|
+
|
|
11
|
+
// Test SD-JWT credential provided by the user
|
|
12
|
+
const TEST_SD_JWT = 'eyJ0eXAiOiJ2YytzZC1qd3QiLCJraWQiOiJkaWQ6Y2hlcWQ6dGVzdG5ldDpjMDg5MGYxYy1jN2JiLTRlYTYtYmU3YS04YzMxNDA0NzQzYjcja2V5cy0xIiwiYWxnIjoiRWREU0EifQ.eyJpYXQiOjE3NTk0MTQzOTQsImlzcyI6ImRpZDpjaGVxZDp0ZXN0bmV0OmMwODkwZjFjLWM3YmItNGVhNi1iZTdhLThjMzE0MDQ3NDNiNyNrZXlzLTEiLCJ2Y3QiOiJJbnRlcm5hbFRlc3RpbmciLCJfc2QiOlsiM3JVUGt1Mk5XckFFeTV3ZE9uVms5TkJBa0haMWE4RDB6Y2liMFNQdmthWSIsIkhjNHAxMnZTTWNMQ0piNVRYVlFrdFFMR2xjM0J3SDNWN2ltakV5ZDdvdzAiLCJNZWNNZEd6NjAxY3kwTTdvanRtSjR1LUI5LTJxSXAya1RvbFpDUm1GZ1pFIiwiZmo4WHdBb0lERmRsWmpEa0NTVzVpeXBPYUZBcVplTWZDRncwTWd3cHhOQSJdLCJfc2RfYWxnIjoic2hhLTI1NiJ9.FcYvNrldceL5BTNmoIaS4Mub8a5NcbiseUeSmmvUpOW8SUom-bchV5AEefrH1VMECbdc2whhk2sW4_jmZo7_Dw~WyI1YTRkZWY1MTAzYjRiYjc0IiwiaWQiLCJkaWQ6a2V5Ono2TWt1OVI4emRBOExENmhjRlhrbjQ3akxuZmNLWk5HbXdhVHJEbmFDQmtTYjhVbiJd~WyJiZDUyMjQ5ZjAyNjk3NWRkIiwiZGF0ZSIsIjIwMjUtMTAtMDkiXQ~WyJlMjcxNzExNzVkODdlMWE1IiwibmFtZSIsIm1heWNvbiJd~WyIwZDFiOTBlNTlhNmMyNDNiIiwibnVtYmVyIiwxMjNd~';
|
|
13
|
+
|
|
14
|
+
// Regular JWT (not SD-JWT) for testing
|
|
15
|
+
const REGULAR_JWT = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
|
|
16
|
+
|
|
17
|
+
// Regular JWT with dc+sd-jwt type
|
|
18
|
+
const DC_SD_JWT = 'eyJ0eXAiOiJkYytzZC1qd3QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
|
|
19
|
+
|
|
20
|
+
// Sample W3C credential for testing
|
|
21
|
+
const W3C_CREDENTIAL = {
|
|
22
|
+
'@context': ['https://www.w3.org/2018/credentials/v1'],
|
|
23
|
+
type: ['VerifiableCredential', 'TestCredential'],
|
|
24
|
+
issuer: 'did:example:issuer',
|
|
25
|
+
credentialSubject: {
|
|
26
|
+
id: 'did:example:subject',
|
|
27
|
+
name: 'Test Subject',
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
describe('SD-JWT Service', () => {
|
|
32
|
+
describe('isSDJWTCredential', () => {
|
|
33
|
+
it('should return true for vc+sd-jwt credential', () => {
|
|
34
|
+
const result = isSDJWTCredential(TEST_SD_JWT);
|
|
35
|
+
expect(result).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should return true for dc+sd-jwt credential', () => {
|
|
39
|
+
const result = isSDJWTCredential(DC_SD_JWT);
|
|
40
|
+
expect(result).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should return false for regular JWT', () => {
|
|
44
|
+
const result = isSDJWTCredential(REGULAR_JWT);
|
|
45
|
+
expect(result).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should handle JWT without typ field', () => {
|
|
49
|
+
const jwtWithoutTyp = 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U';
|
|
50
|
+
const result = isSDJWTCredential(jwtWithoutTyp);
|
|
51
|
+
expect(result).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('sdJwtToW3C', () => {
|
|
56
|
+
it('should convert decoded SD-JWT to W3C format with all fields', () => {
|
|
57
|
+
const decodedSDJWT = {
|
|
58
|
+
jwt: {
|
|
59
|
+
header: {
|
|
60
|
+
typ: 'vc+sd-jwt',
|
|
61
|
+
alg: 'EdDSA',
|
|
62
|
+
},
|
|
63
|
+
payload: {
|
|
64
|
+
iss: 'did:example:issuer',
|
|
65
|
+
vct: 'TestCredential',
|
|
66
|
+
iat: 1609459200, // 2021-01-01T00:00:00.000Z
|
|
67
|
+
exp: 1640995200, // 2022-01-01T00:00:00.000Z
|
|
68
|
+
jti: 'credential-id-123',
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
disclosures: [
|
|
72
|
+
{ key: 'id', value: 'did:example:subject' },
|
|
73
|
+
{ key: 'name', value: 'John Doe' },
|
|
74
|
+
{ key: 'age', value: 30 },
|
|
75
|
+
],
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const result = sdJwtToW3C(decodedSDJWT);
|
|
79
|
+
|
|
80
|
+
expect(result).toEqual({
|
|
81
|
+
'@context': ['https://www.w3.org/2018/credentials/v1'],
|
|
82
|
+
type: ['VerifiableCredential', 'TestCredential'],
|
|
83
|
+
issuer: 'did:example:issuer',
|
|
84
|
+
credentialSubject: {
|
|
85
|
+
id: 'did:example:subject',
|
|
86
|
+
name: 'John Doe',
|
|
87
|
+
age: 30,
|
|
88
|
+
},
|
|
89
|
+
issuanceDate: '2021-01-01T00:00:00.000Z',
|
|
90
|
+
expirationDate: '2022-01-01T00:00:00.000Z',
|
|
91
|
+
id: 'credential-id-123',
|
|
92
|
+
_sd_jwt: {
|
|
93
|
+
encoded: undefined,
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should store raw encoded SD-JWT when provided', () => {
|
|
99
|
+
const decodedSDJWT = {
|
|
100
|
+
jwt: {
|
|
101
|
+
header: {
|
|
102
|
+
typ: 'vc+sd-jwt',
|
|
103
|
+
alg: 'EdDSA',
|
|
104
|
+
},
|
|
105
|
+
payload: {
|
|
106
|
+
iss: 'did:example:issuer',
|
|
107
|
+
vct: 'TestCredential',
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
disclosures: [
|
|
111
|
+
{ key: 'name', value: 'Jane Doe' },
|
|
112
|
+
],
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const encodedSDJWT = 'eyJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJkaWQ6ZXhhbXBsZTppc3N1ZXIifQ.signature~disclosure1~disclosure2~';
|
|
116
|
+
const result = sdJwtToW3C(decodedSDJWT, encodedSDJWT);
|
|
117
|
+
|
|
118
|
+
expect(result._sd_jwt).toBeDefined();
|
|
119
|
+
expect(result._sd_jwt.encoded).toBe(encodedSDJWT);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should handle minimal SD-JWT without optional fields', () => {
|
|
123
|
+
const decodedSDJWT = {
|
|
124
|
+
jwt: {
|
|
125
|
+
header: {
|
|
126
|
+
typ: 'vc+sd-jwt',
|
|
127
|
+
alg: 'EdDSA',
|
|
128
|
+
},
|
|
129
|
+
payload: {
|
|
130
|
+
iss: 'did:example:issuer',
|
|
131
|
+
vct: 'TestCredential',
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
disclosures: [
|
|
135
|
+
{ key: 'name', value: 'Jane Doe' },
|
|
136
|
+
],
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const result = sdJwtToW3C(decodedSDJWT);
|
|
140
|
+
|
|
141
|
+
expect(result).toEqual({
|
|
142
|
+
'@context': ['https://www.w3.org/2018/credentials/v1'],
|
|
143
|
+
type: ['VerifiableCredential', 'TestCredential'],
|
|
144
|
+
issuer: 'did:example:issuer',
|
|
145
|
+
credentialSubject: {
|
|
146
|
+
name: 'Jane Doe',
|
|
147
|
+
},
|
|
148
|
+
_sd_jwt: {
|
|
149
|
+
encoded: undefined,
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should handle empty disclosures array', () => {
|
|
155
|
+
const decodedSDJWT = {
|
|
156
|
+
jwt: {
|
|
157
|
+
header: {
|
|
158
|
+
typ: 'vc+sd-jwt',
|
|
159
|
+
alg: 'EdDSA',
|
|
160
|
+
},
|
|
161
|
+
payload: {
|
|
162
|
+
iss: 'did:example:issuer',
|
|
163
|
+
vct: 'TestCredential',
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
disclosures: [],
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const result = sdJwtToW3C(decodedSDJWT);
|
|
170
|
+
|
|
171
|
+
expect(result.credentialSubject).toEqual({});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should use issuer field from payload if iss is not present', () => {
|
|
175
|
+
const decodedSDJWT = {
|
|
176
|
+
jwt: {
|
|
177
|
+
header: {
|
|
178
|
+
typ: 'vc+sd-jwt',
|
|
179
|
+
alg: 'EdDSA',
|
|
180
|
+
},
|
|
181
|
+
payload: {
|
|
182
|
+
issuer: 'did:example:issuer-from-issuer-field',
|
|
183
|
+
vct: 'TestCredential',
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
disclosures: [],
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const result = sdJwtToW3C(decodedSDJWT);
|
|
190
|
+
|
|
191
|
+
expect(result.issuer).toBe('did:example:issuer-from-issuer-field');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should handle disclosures without key (malformed)', () => {
|
|
195
|
+
const decodedSDJWT = {
|
|
196
|
+
jwt: {
|
|
197
|
+
header: {
|
|
198
|
+
typ: 'vc+sd-jwt',
|
|
199
|
+
alg: 'EdDSA',
|
|
200
|
+
},
|
|
201
|
+
payload: {
|
|
202
|
+
iss: 'did:example:issuer',
|
|
203
|
+
vct: 'TestCredential',
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
disclosures: [
|
|
207
|
+
{ key: 'name', value: 'Valid' },
|
|
208
|
+
{ value: 'Invalid' }, // Missing key
|
|
209
|
+
null, // Null disclosure
|
|
210
|
+
],
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const result = sdJwtToW3C(decodedSDJWT);
|
|
214
|
+
|
|
215
|
+
expect(result.credentialSubject).toEqual({
|
|
216
|
+
name: 'Valid',
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should use UnknownCredential when vct is missing', () => {
|
|
221
|
+
const decodedSDJWT = {
|
|
222
|
+
jwt: {
|
|
223
|
+
header: {
|
|
224
|
+
typ: 'vc+sd-jwt',
|
|
225
|
+
alg: 'EdDSA',
|
|
226
|
+
},
|
|
227
|
+
payload: {
|
|
228
|
+
iss: 'did:example:issuer',
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
disclosures: [],
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const result = sdJwtToW3C(decodedSDJWT);
|
|
235
|
+
|
|
236
|
+
expect(result.type).toEqual(['VerifiableCredential', 'UnknownCredential']);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe('decodeSDJWTToW3C', () => {
|
|
241
|
+
it('should decode the provided test SD-JWT and convert to W3C format', async () => {
|
|
242
|
+
const result = await decodeSDJWTToW3C(TEST_SD_JWT) as any;
|
|
243
|
+
|
|
244
|
+
// Verify the structure
|
|
245
|
+
expect(result).toHaveProperty('@context');
|
|
246
|
+
expect(result).toHaveProperty('type');
|
|
247
|
+
expect(result).toHaveProperty('issuer');
|
|
248
|
+
expect(result).toHaveProperty('credentialSubject');
|
|
249
|
+
expect(result).toHaveProperty('issuanceDate');
|
|
250
|
+
|
|
251
|
+
// Verify specific values from the test SD-JWT
|
|
252
|
+
expect(result.type).toEqual(['VerifiableCredential', 'InternalTesting']);
|
|
253
|
+
expect(result.issuer).toBe('did:cheqd:testnet:c0890f1c-c7bb-4ea6-be7a-8c31404743b7#keys-1');
|
|
254
|
+
|
|
255
|
+
// Verify credential subject has disclosed claims
|
|
256
|
+
expect(result.credentialSubject).toHaveProperty('id');
|
|
257
|
+
expect(result.credentialSubject).toHaveProperty('name');
|
|
258
|
+
expect(result.credentialSubject).toHaveProperty('date');
|
|
259
|
+
expect(result.credentialSubject).toHaveProperty('number');
|
|
260
|
+
|
|
261
|
+
// Verify disclosed values
|
|
262
|
+
expect(result.credentialSubject.id).toBe('did:key:z6Mku9R8zdA8LD6hcFXkn47jLnfcKZNGmwaTrDnaCBkSb8Un');
|
|
263
|
+
expect(result.credentialSubject.name).toBe('maycon');
|
|
264
|
+
expect(result.credentialSubject.date).toBe('2025-10-09');
|
|
265
|
+
expect(result.credentialSubject.number).toBe(123);
|
|
266
|
+
|
|
267
|
+
// Verify issuance date
|
|
268
|
+
expect(result.issuanceDate).toBe(new Date(1759414394 * 1000).toISOString());
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('should store raw encoded SD-JWT and decoded structure in metadata', async () => {
|
|
272
|
+
const result = await decodeSDJWTToW3C(TEST_SD_JWT) as any;
|
|
273
|
+
|
|
274
|
+
// Verify _sd_jwt metadata is present
|
|
275
|
+
expect(result).toHaveProperty('_sd_jwt');
|
|
276
|
+
expect(result._sd_jwt).toHaveProperty('encoded');
|
|
277
|
+
|
|
278
|
+
// Verify raw SD-JWT string is stored
|
|
279
|
+
expect(result._sd_jwt.encoded).toBe(TEST_SD_JWT);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('should handle SD-JWT with minimal disclosures', async () => {
|
|
283
|
+
// This test would need a valid minimal SD-JWT
|
|
284
|
+
// For now, we'll just test that the function is async and can be called
|
|
285
|
+
await expect(decodeSDJWTToW3C(TEST_SD_JWT)).resolves.toBeDefined();
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
describe('credentialToW3C', () => {
|
|
290
|
+
it('should return W3C credential as-is when passed an object with type', async () => {
|
|
291
|
+
const result = await credentialToW3C(W3C_CREDENTIAL);
|
|
292
|
+
expect(result).toEqual(W3C_CREDENTIAL);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('should decode SD-JWT string to W3C format', async () => {
|
|
296
|
+
const result = await credentialToW3C(TEST_SD_JWT);
|
|
297
|
+
|
|
298
|
+
expect(result).toHaveProperty('@context');
|
|
299
|
+
expect(result).toHaveProperty('type');
|
|
300
|
+
expect(result).toHaveProperty('issuer');
|
|
301
|
+
expect(result).toHaveProperty('credentialSubject');
|
|
302
|
+
expect(result.type).toEqual(['VerifiableCredential', 'InternalTesting']);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('should parse JSON string containing W3C credential', async () => {
|
|
306
|
+
const jsonString = JSON.stringify(W3C_CREDENTIAL);
|
|
307
|
+
const result = await credentialToW3C(jsonString);
|
|
308
|
+
expect(result).toEqual(W3C_CREDENTIAL);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('should throw error for unsupported credential format', async () => {
|
|
312
|
+
const invalidCredential = 'not-a-valid-jwt-or-credential';
|
|
313
|
+
|
|
314
|
+
await expect(credentialToW3C(invalidCredential)).rejects.toThrow(
|
|
315
|
+
'Unable to convert credential to W3C format'
|
|
316
|
+
);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('should throw error for empty string', async () => {
|
|
320
|
+
await expect(credentialToW3C('')).rejects.toThrow();
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should handle object without type field', async () => {
|
|
324
|
+
const invalidObject = {
|
|
325
|
+
someField: 'someValue',
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
await expect(credentialToW3C(invalidObject)).rejects.toThrow(
|
|
329
|
+
'Unable to convert credential to W3C format'
|
|
330
|
+
);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('should handle regular JWT (not SD-JWT)', async () => {
|
|
334
|
+
await expect(credentialToW3C(REGULAR_JWT)).rejects.toThrow(
|
|
335
|
+
'Unable to convert credential to W3C format'
|
|
336
|
+
);
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
describe('Integration tests', () => {
|
|
341
|
+
it('should properly decode and convert complete SD-JWT flow', async () => {
|
|
342
|
+
// Test the complete flow: check if SD-JWT -> decode -> convert to W3C
|
|
343
|
+
const isSDJWT = isSDJWTCredential(TEST_SD_JWT);
|
|
344
|
+
expect(isSDJWT).toBe(true);
|
|
345
|
+
|
|
346
|
+
const w3cCredential = await decodeSDJWTToW3C(TEST_SD_JWT) as any;
|
|
347
|
+
|
|
348
|
+
// Verify complete structure
|
|
349
|
+
expect(w3cCredential).toMatchObject({
|
|
350
|
+
'@context': expect.arrayContaining(['https://www.w3.org/2018/credentials/v1']),
|
|
351
|
+
type: expect.arrayContaining(['VerifiableCredential', 'InternalTesting']),
|
|
352
|
+
issuer: 'did:cheqd:testnet:c0890f1c-c7bb-4ea6-be7a-8c31404743b7#keys-1',
|
|
353
|
+
credentialSubject: {
|
|
354
|
+
id: 'did:key:z6Mku9R8zdA8LD6hcFXkn47jLnfcKZNGmwaTrDnaCBkSb8Un',
|
|
355
|
+
name: 'maycon',
|
|
356
|
+
date: '2025-10-09',
|
|
357
|
+
number: 123,
|
|
358
|
+
},
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// Verify SD-JWT metadata is stored for unwrapping
|
|
362
|
+
expect(w3cCredential._sd_jwt).toBeDefined();
|
|
363
|
+
expect(w3cCredential._sd_jwt.encoded).toBe(TEST_SD_JWT);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('should handle credentialToW3C with the test SD-JWT', async () => {
|
|
367
|
+
const result = await credentialToW3C(TEST_SD_JWT) as any;
|
|
368
|
+
|
|
369
|
+
expect(result.credentialSubject.name).toBe('maycon');
|
|
370
|
+
expect(result.credentialSubject.number).toBe(123);
|
|
371
|
+
expect(result.issuer).toBe('did:cheqd:testnet:c0890f1c-c7bb-4ea6-be7a-8c31404743b7#keys-1');
|
|
372
|
+
|
|
373
|
+
// Verify SD-JWT metadata for presentation unwrapping
|
|
374
|
+
expect(result._sd_jwt).toBeDefined();
|
|
375
|
+
expect(result._sd_jwt.encoded).toBe(TEST_SD_JWT);
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
describe('decodeSDJWT', () => {
|
|
380
|
+
it('should decode SD-JWT into structured format', async () => {
|
|
381
|
+
const decoded = await decodeSDJWT(TEST_SD_JWT);
|
|
382
|
+
|
|
383
|
+
expect(decoded).toHaveProperty('jwt');
|
|
384
|
+
expect(decoded).toHaveProperty('disclosures');
|
|
385
|
+
expect(decoded.jwt).toHaveProperty('header');
|
|
386
|
+
expect(decoded.jwt).toHaveProperty('payload');
|
|
387
|
+
expect(Array.isArray(decoded.disclosures)).toBe(true);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it('should decode header and payload correctly', async () => {
|
|
391
|
+
const decoded = await decodeSDJWT(TEST_SD_JWT);
|
|
392
|
+
|
|
393
|
+
expect(decoded.jwt.header.typ).toBe('vc+sd-jwt');
|
|
394
|
+
expect(decoded.jwt.header.alg).toBe('EdDSA');
|
|
395
|
+
expect(decoded.jwt.payload.iss).toBe('did:cheqd:testnet:c0890f1c-c7bb-4ea6-be7a-8c31404743b7#keys-1');
|
|
396
|
+
expect(decoded.jwt.payload.vct).toBe('InternalTesting');
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('should throw error for invalid SD-JWT', async () => {
|
|
400
|
+
await expect(decodeSDJWT('invalid-jwt-string')).rejects.toThrow();
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
describe('verifySDJWT', () => {
|
|
405
|
+
it('should verify valid SD-JWT successfully', async () => {
|
|
406
|
+
const result = await verifySDJWT(TEST_SD_JWT);
|
|
407
|
+
|
|
408
|
+
expect(result).toHaveProperty('verified');
|
|
409
|
+
expect(result.verified).toBe(true);
|
|
410
|
+
expect(result.error).toBeUndefined();
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('should fail verification for expired SD-JWT', async () => {
|
|
414
|
+
// Create an expired SD-JWT (exp in the past)
|
|
415
|
+
const expiredJWT = 'eyJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJkaWQ6ZXhhbXBsZTppc3N1ZXIiLCJ2Y3QiOiJUZXN0Q3JlZGVudGlhbCIsImV4cCI6MTYwOTQ1OTIwMCwiaWF0IjoxNjA5NDU5MjAwfQ.signature~';
|
|
416
|
+
|
|
417
|
+
const result = await verifySDJWT(expiredJWT);
|
|
418
|
+
|
|
419
|
+
expect(result.verified).toBe(false);
|
|
420
|
+
expect(result.error).toContain('expired');
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it('should fail verification for not-yet-valid SD-JWT', async () => {
|
|
424
|
+
// Create an SD-JWT with nbf in the future
|
|
425
|
+
const futureJWT = 'eyJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJkaWQ6ZXhhbXBsZTppc3N1ZXIiLCJ2Y3QiOiJUZXN0Q3JlZGVudGlhbCIsIm5iZiI6OTk5OTk5OTk5OSwiaWF0IjoxNjA5NDU5MjAwfQ.signature~';
|
|
426
|
+
|
|
427
|
+
const result = await verifySDJWT(futureJWT);
|
|
428
|
+
|
|
429
|
+
expect(result.verified).toBe(false);
|
|
430
|
+
expect(result.error).toContain('not yet valid');
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
it('should fail verification for invalid SD-JWT format', async () => {
|
|
434
|
+
const result = await verifySDJWT('invalid-jwt-string');
|
|
435
|
+
|
|
436
|
+
expect(result.verified).toBe(false);
|
|
437
|
+
expect(result.error).toBeDefined();
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it('should verify SD-JWT without expiration date', async () => {
|
|
441
|
+
// The TEST_SD_JWT doesn't have an expiration date, so it should verify
|
|
442
|
+
const result = await verifySDJWT(TEST_SD_JWT);
|
|
443
|
+
|
|
444
|
+
expect(result.verified).toBe(true);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it('should return error message on verification failure', async () => {
|
|
448
|
+
const result = await verifySDJWT('');
|
|
449
|
+
|
|
450
|
+
expect(result.verified).toBe(false);
|
|
451
|
+
expect(result).toHaveProperty('error');
|
|
452
|
+
expect(typeof result.error).toBe('string');
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
describe('createSDJWTPresentation', () => {
|
|
457
|
+
it('should create presentation with selective disclosure of specific attributes', async () => {
|
|
458
|
+
// Test selective disclosure - reveal only name and date, hide id and number
|
|
459
|
+
const presentation = await createSDJWTPresentation({
|
|
460
|
+
attributesToReveal: ['name', 'date'],
|
|
461
|
+
credential: TEST_SD_JWT,
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// Presentation should be a string
|
|
465
|
+
expect(typeof presentation).toBe('string');
|
|
466
|
+
|
|
467
|
+
// Should be a valid JWT format with disclosures
|
|
468
|
+
expect(presentation).toContain('~');
|
|
469
|
+
|
|
470
|
+
// Decode the presentation to verify only requested attributes are included
|
|
471
|
+
const decoded = await decodeSDJWT(presentation);
|
|
472
|
+
const disclosureKeys = decoded.disclosures.map((d: any) => d.key);
|
|
473
|
+
|
|
474
|
+
// Should include requested attributes
|
|
475
|
+
expect(disclosureKeys).toContain('name');
|
|
476
|
+
expect(disclosureKeys).toContain('date');
|
|
477
|
+
|
|
478
|
+
// Should NOT include unrequested attributes
|
|
479
|
+
expect(disclosureKeys).not.toContain('number');
|
|
480
|
+
// Note: 'id' might be included by default in some implementations
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it('should create presentation revealing only one attribute', async () => {
|
|
484
|
+
const presentation = await createSDJWTPresentation({
|
|
485
|
+
attributesToReveal: ['name'],
|
|
486
|
+
credential: TEST_SD_JWT,
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
expect(typeof presentation).toBe('string');
|
|
490
|
+
|
|
491
|
+
const decoded = await decodeSDJWT(presentation);
|
|
492
|
+
const disclosureKeys = decoded.disclosures.map((d: any) => d.key);
|
|
493
|
+
|
|
494
|
+
// Should only include the requested attribute
|
|
495
|
+
expect(disclosureKeys).toContain('name');
|
|
496
|
+
expect(disclosureKeys.filter((k: string) => k !== 'name').length).toBeLessThan(disclosureKeys.length);
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
it('should create presentation revealing all attributes', async () => {
|
|
500
|
+
const presentation = await createSDJWTPresentation({
|
|
501
|
+
attributesToReveal: ['id', 'name', 'date', 'number'],
|
|
502
|
+
credential: TEST_SD_JWT,
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
expect(typeof presentation).toBe('string');
|
|
506
|
+
|
|
507
|
+
const decoded = await decodeSDJWT(presentation);
|
|
508
|
+
const disclosureKeys = decoded.disclosures.map((d: any) => d.key);
|
|
509
|
+
|
|
510
|
+
// Should include all requested attributes
|
|
511
|
+
expect(disclosureKeys).toContain('id');
|
|
512
|
+
expect(disclosureKeys).toContain('name');
|
|
513
|
+
expect(disclosureKeys).toContain('date');
|
|
514
|
+
expect(disclosureKeys).toContain('number');
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it('should create presentation with empty attributes array (minimal disclosure)', async () => {
|
|
518
|
+
const presentation = await createSDJWTPresentation({
|
|
519
|
+
attributesToReveal: [],
|
|
520
|
+
credential: TEST_SD_JWT,
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
expect(typeof presentation).toBe('string');
|
|
524
|
+
expect(presentation).toContain('~');
|
|
525
|
+
|
|
526
|
+
// Should still be valid JWT format
|
|
527
|
+
const decoded = await decodeSDJWT(presentation);
|
|
528
|
+
expect(decoded).toHaveProperty('jwt');
|
|
529
|
+
expect(decoded).toHaveProperty('disclosures');
|
|
530
|
+
|
|
531
|
+
// Should have minimal or no disclosures
|
|
532
|
+
expect(Array.isArray(decoded.disclosures)).toBe(true);
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
it('should maintain credential integrity in presentation', async () => {
|
|
536
|
+
const presentation = await createSDJWTPresentation({
|
|
537
|
+
attributesToReveal: ['name', 'date'],
|
|
538
|
+
credential: TEST_SD_JWT,
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
const decoded = await decodeSDJWT(presentation);
|
|
542
|
+
|
|
543
|
+
// JWT payload should maintain core claims
|
|
544
|
+
expect(decoded.jwt.payload).toHaveProperty('iss');
|
|
545
|
+
expect(decoded.jwt.payload).toHaveProperty('vct');
|
|
546
|
+
expect(decoded.jwt.payload.vct).toBe('InternalTesting');
|
|
547
|
+
expect(decoded.jwt.payload.iss).toBe('did:cheqd:testnet:c0890f1c-c7bb-4ea6-be7a-8c31404743b7#keys-1');
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
it('should convert presentation to W3C format correctly', async () => {
|
|
551
|
+
const presentation = await createSDJWTPresentation({
|
|
552
|
+
attributesToReveal: ['name', 'number'],
|
|
553
|
+
credential: TEST_SD_JWT,
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
// Convert presentation to W3C format
|
|
557
|
+
const w3cPresentation = await decodeSDJWTToW3C(presentation) as any;
|
|
558
|
+
|
|
559
|
+
expect(w3cPresentation).toHaveProperty('credentialSubject');
|
|
560
|
+
expect(w3cPresentation.credentialSubject).toHaveProperty('name');
|
|
561
|
+
expect(w3cPresentation.credentialSubject).toHaveProperty('number');
|
|
562
|
+
|
|
563
|
+
// Should have the revealed values
|
|
564
|
+
expect(w3cPresentation.credentialSubject.name).toBe('maycon');
|
|
565
|
+
expect(w3cPresentation.credentialSubject.number).toBe(123);
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
it('should handle non-existent attribute gracefully', async () => {
|
|
569
|
+
// Request an attribute that doesn't exist in the credential
|
|
570
|
+
const presentation = await createSDJWTPresentation({
|
|
571
|
+
attributesToReveal: ['name', 'nonExistentAttribute'],
|
|
572
|
+
credential: TEST_SD_JWT,
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
expect(typeof presentation).toBe('string');
|
|
576
|
+
|
|
577
|
+
const decoded = await decodeSDJWT(presentation);
|
|
578
|
+
const disclosureKeys = decoded.disclosures.map((d: any) => d.key);
|
|
579
|
+
|
|
580
|
+
// Should include existing attributes
|
|
581
|
+
expect(disclosureKeys).toContain('name');
|
|
582
|
+
// Non-existent attribute should not break the process
|
|
583
|
+
expect(presentation).toBeTruthy();
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
it('should fail with invalid credential format', async () => {
|
|
587
|
+
await expect(
|
|
588
|
+
createSDJWTPresentation({
|
|
589
|
+
attributesToReveal: ['name'],
|
|
590
|
+
credential: 'invalid-jwt-format',
|
|
591
|
+
})
|
|
592
|
+
).rejects.toThrow();
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
it('should fail with empty credential', async () => {
|
|
596
|
+
await expect(
|
|
597
|
+
createSDJWTPresentation({
|
|
598
|
+
attributesToReveal: ['name'],
|
|
599
|
+
credential: '',
|
|
600
|
+
})
|
|
601
|
+
).rejects.toThrow();
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
it('should preserve presentation format for verification flow', async () => {
|
|
605
|
+
const presentation = await createSDJWTPresentation({
|
|
606
|
+
attributesToReveal: ['name', 'date'],
|
|
607
|
+
credential: TEST_SD_JWT,
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
// The presentation should be verifiable
|
|
611
|
+
const verificationResult = await verifySDJWT(presentation);
|
|
612
|
+
expect(verificationResult.verified).toBe(true);
|
|
613
|
+
});
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
describe('Presentation Integration Tests', () => {
|
|
617
|
+
it('should complete full flow: store credential -> create presentation -> verify', async () => {
|
|
618
|
+
// Step 1: Convert SD-JWT to W3C format (as done in addCredential)
|
|
619
|
+
const w3cCredential = await decodeSDJWTToW3C(TEST_SD_JWT) as any;
|
|
620
|
+
|
|
621
|
+
// Verify metadata is stored
|
|
622
|
+
expect(w3cCredential._sd_jwt).toBeDefined();
|
|
623
|
+
expect(w3cCredential._sd_jwt.encoded).toBe(TEST_SD_JWT);
|
|
624
|
+
|
|
625
|
+
// Step 2: Create presentation from stored credential (as done in verification-controller)
|
|
626
|
+
const presentation = await createSDJWTPresentation({
|
|
627
|
+
attributesToReveal: ['name', 'date'],
|
|
628
|
+
credential: w3cCredential._sd_jwt.encoded,
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
expect(typeof presentation).toBe('string');
|
|
632
|
+
|
|
633
|
+
// Step 3: Verify presentation
|
|
634
|
+
const verificationResult = await verifySDJWT(presentation);
|
|
635
|
+
expect(verificationResult.verified).toBe(true);
|
|
636
|
+
|
|
637
|
+
// Step 4: Decode presentation to check disclosed claims
|
|
638
|
+
const decoded = await decodeSDJWT(presentation);
|
|
639
|
+
const disclosureKeys = decoded.disclosures.map((d: any) => d.key);
|
|
640
|
+
|
|
641
|
+
expect(disclosureKeys).toContain('name');
|
|
642
|
+
expect(disclosureKeys).toContain('date');
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
it('should support multiple presentation creations from same credential', async () => {
|
|
646
|
+
// Create first presentation with some attributes
|
|
647
|
+
const presentation1 = await createSDJWTPresentation({
|
|
648
|
+
attributesToReveal: ['name'],
|
|
649
|
+
credential: TEST_SD_JWT,
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
// Create second presentation with different attributes
|
|
653
|
+
const presentation2 = await createSDJWTPresentation({
|
|
654
|
+
attributesToReveal: ['date', 'number'],
|
|
655
|
+
credential: TEST_SD_JWT,
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
// Both should be valid
|
|
659
|
+
expect(typeof presentation1).toBe('string');
|
|
660
|
+
expect(typeof presentation2).toBe('string');
|
|
661
|
+
|
|
662
|
+
// They should be different (different disclosures)
|
|
663
|
+
expect(presentation1).not.toBe(presentation2);
|
|
664
|
+
|
|
665
|
+
// Both should verify
|
|
666
|
+
const result1 = await verifySDJWT(presentation1);
|
|
667
|
+
const result2 = await verifySDJWT(presentation2);
|
|
668
|
+
|
|
669
|
+
expect(result1.verified).toBe(true);
|
|
670
|
+
expect(result2.verified).toBe(true);
|
|
671
|
+
|
|
672
|
+
// Check different attributes are disclosed
|
|
673
|
+
const decoded1 = await decodeSDJWT(presentation1);
|
|
674
|
+
const decoded2 = await decodeSDJWT(presentation2);
|
|
675
|
+
|
|
676
|
+
const keys1 = decoded1.disclosures.map((d: any) => d.key);
|
|
677
|
+
const keys2 = decoded2.disclosures.map((d: any) => d.key);
|
|
678
|
+
|
|
679
|
+
expect(keys1).toContain('name');
|
|
680
|
+
expect(keys2).toContain('date');
|
|
681
|
+
expect(keys2).toContain('number');
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
it('should match verification-controller usage pattern', async () => {
|
|
685
|
+
// Simulate the exact pattern used in verification-controller.ts
|
|
686
|
+
|
|
687
|
+
// Credential is stored in wallet as W3C with _sd_jwt metadata
|
|
688
|
+
const storedCredential = await decodeSDJWTToW3C(TEST_SD_JWT) as any;
|
|
689
|
+
|
|
690
|
+
// During presentation, extract encoded SD-JWT and create selective disclosure
|
|
691
|
+
const credentialSelection = {
|
|
692
|
+
credential: storedCredential,
|
|
693
|
+
attributesToReveal: ['name', 'date'],
|
|
694
|
+
};
|
|
695
|
+
|
|
696
|
+
// This is the exact pattern from verification-controller.ts:172-179
|
|
697
|
+
if (credentialSelection.credential._sd_jwt) {
|
|
698
|
+
const derivedCredential = await createSDJWTPresentation({
|
|
699
|
+
attributesToReveal: credentialSelection.attributesToReveal,
|
|
700
|
+
credential: credentialSelection.credential._sd_jwt.encoded,
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
// Verify the presentation is a string (not an array)
|
|
704
|
+
expect(typeof derivedCredential).toBe('string');
|
|
705
|
+
|
|
706
|
+
// Verify it can be decoded
|
|
707
|
+
const decoded = await decodeSDJWT(derivedCredential);
|
|
708
|
+
expect(decoded).toHaveProperty('jwt');
|
|
709
|
+
expect(decoded).toHaveProperty('disclosures');
|
|
710
|
+
|
|
711
|
+
// Verify disclosed attributes
|
|
712
|
+
const disclosedKeys = decoded.disclosures.map((d: any) => d.key);
|
|
713
|
+
expect(disclosedKeys).toContain('name');
|
|
714
|
+
expect(disclosedKeys).toContain('date');
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
});
|
|
718
|
+
});
|