@sd-jwt/core 0.3.2-next.98 → 0.4.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/src/sdjwt.ts CHANGED
@@ -6,19 +6,18 @@ import {
6
6
  DisclosureFrame,
7
7
  Hasher,
8
8
  HasherAndAlg,
9
- KBOptions,
10
- KB_JWT_TYP,
9
+ PresentationFrame,
11
10
  SDJWTCompact,
12
11
  SD_DECOY,
13
12
  SD_DIGEST,
14
13
  SD_LIST_KEY,
15
14
  SD_SEPARATOR,
16
15
  SaltGenerator,
17
- Signer,
18
16
  kbHeader,
19
17
  kbPayload,
20
18
  } from '@sd-jwt/types';
21
19
  import { createHashMapping, getSDAlgAndPayload, unpack } from '@sd-jwt/decode';
20
+ import { transformPresentationFrame } from '@sd-jwt/present';
22
21
 
23
22
  export type SDJwtData<
24
23
  Header extends Record<string, unknown>,
@@ -117,7 +116,10 @@ export class SDJwt<
117
116
  });
118
117
  }
119
118
 
120
- public async present(keys: string[], hasher: Hasher): Promise<SDJWTCompact> {
119
+ public async present<T extends Record<string, unknown>>(
120
+ presentFrame: PresentationFrame<T> | undefined,
121
+ hasher: Hasher,
122
+ ): Promise<SDJWTCompact> {
121
123
  if (!this.jwt?.payload || !this.disclosures) {
122
124
  throw new SDJWTException('Invalid sd-jwt: jwt or disclosures is missing');
123
125
  }
@@ -130,15 +132,12 @@ export class SDJwt<
130
132
  hasher,
131
133
  );
132
134
 
133
- const presentableKeys = Object.keys(disclosureKeymap);
134
- const missingKeys = keys.filter((k) => !presentableKeys.includes(k));
135
- if (missingKeys.length > 0) {
136
- throw new SDJWTException(
137
- `Invalid sd-jwt: invalid present keys: ${missingKeys.join(', ')}`,
138
- );
139
- }
140
-
141
- const disclosures = keys.map((k) => hashmap[disclosureKeymap[k]]);
135
+ const keys = presentFrame
136
+ ? transformPresentationFrame(presentFrame)
137
+ : await this.presentableKeys(hasher);
138
+ const disclosures = keys
139
+ .map((k) => hashmap[disclosureKeymap[k]])
140
+ .filter((d) => d !== undefined);
142
141
  const presentSDJwt = new SDJwt({
143
142
  jwt: this.jwt,
144
143
  disclosures,
@@ -1,8 +1,9 @@
1
1
  import { SDJwtInstance, SdJwtPayload } from '../index';
2
- import { Signer, Verifier } from '@sd-jwt/types';
3
- import Crypto from 'node:crypto';
2
+ import { Signer, Verifier, KbVerifier, JwtPayload } from '@sd-jwt/types';
3
+ import Crypto, { KeyLike } from 'node:crypto';
4
4
  import { describe, expect, test } from 'vitest';
5
5
  import { digest, generateSalt } from '@sd-jwt/crypto-nodejs';
6
+ import { importJWK, exportJWK, JWK } from 'jose';
6
7
 
7
8
  export const createSignerVerifier = () => {
8
9
  const { privateKey, publicKey } = Crypto.generateKeyPairSync('ed25519');
@@ -52,15 +53,19 @@ describe('index', () => {
52
53
 
53
54
  expect(credential).toBeDefined();
54
55
 
55
- const presentation = await sdjwt.present(credential, ['foo'], {
56
- kb: {
57
- payload: {
58
- aud: '1',
59
- iat: 1,
60
- nonce: '342',
56
+ const presentation = await sdjwt.present(
57
+ credential,
58
+ { foo: true },
59
+ {
60
+ kb: {
61
+ payload: {
62
+ aud: '1',
63
+ iat: 1,
64
+ nonce: '342',
65
+ },
61
66
  },
62
67
  },
63
- });
68
+ );
64
69
 
65
70
  expect(presentation).toBeDefined();
66
71
  });
@@ -162,15 +167,19 @@ describe('index', () => {
162
167
  },
163
168
  );
164
169
 
165
- const presentation = await sdjwt.present(credential, ['foo'], {
166
- kb: {
167
- payload: {
168
- aud: '',
169
- iat: 1,
170
- nonce: '342',
170
+ const presentation = await sdjwt.present(
171
+ credential,
172
+ { foo: true },
173
+ {
174
+ kb: {
175
+ payload: {
176
+ aud: '',
177
+ iat: 1,
178
+ nonce: '342',
179
+ },
171
180
  },
172
181
  },
173
- });
182
+ );
174
183
 
175
184
  try {
176
185
  await sdjwt.verify(presentation);
@@ -181,38 +190,72 @@ describe('index', () => {
181
190
 
182
191
  test('verify with kbJwt', async () => {
183
192
  const { signer, verifier } = createSignerVerifier();
193
+
194
+ const { privateKey, publicKey } = Crypto.generateKeyPairSync('ed25519');
195
+
196
+ //TODO: maybe we can pass a minial class of the jwt to pass the token
197
+ const kbVerifier: KbVerifier = async (
198
+ data: string,
199
+ sig: string,
200
+ payload: JwtPayload,
201
+ ) => {
202
+ let publicKey: JsonWebKey;
203
+ if (payload.cnf) {
204
+ // use the key from the cnf
205
+ publicKey = payload.cnf.jwk;
206
+ } else {
207
+ throw Error('key binding not supported');
208
+ }
209
+ // get the key of the holder to verify the signature
210
+ return Crypto.verify(
211
+ null,
212
+ Buffer.from(data),
213
+ (await importJWK(publicKey as JWK, 'EdDSA')) as KeyLike,
214
+ Buffer.from(sig, 'base64url'),
215
+ );
216
+ };
217
+
218
+ const kbSigner = (data: string) => {
219
+ const sig = Crypto.sign(null, Buffer.from(data), privateKey);
220
+ return Buffer.from(sig).toString('base64url');
221
+ };
222
+
184
223
  const sdjwt = new SDJwtInstance<SdJwtPayload>({
185
224
  signer,
186
225
  signAlg: 'EdDSA',
187
226
  verifier,
188
227
  hasher: digest,
189
228
  saltGenerator: generateSalt,
190
- kbSigner: signer,
191
- kbVerifier: verifier,
229
+ kbSigner: kbSigner,
230
+ kbVerifier: kbVerifier,
192
231
  kbSignAlg: 'EdDSA',
193
232
  });
194
-
195
233
  const credential = await sdjwt.issue(
196
234
  {
197
235
  foo: 'bar',
198
- iss: 'Issuer',
199
236
  iat: new Date().getTime(),
200
- vct: '',
237
+ cnf: {
238
+ jwk: await exportJWK(publicKey),
239
+ },
201
240
  },
202
241
  {
203
242
  _sd: ['foo'],
204
243
  },
205
244
  );
206
245
 
207
- const presentation = await sdjwt.present(credential, ['foo'], {
208
- kb: {
209
- payload: {
210
- aud: '1',
211
- iat: 1,
212
- nonce: '342',
246
+ const presentation = await sdjwt.present(
247
+ credential,
248
+ { foo: true },
249
+ {
250
+ kb: {
251
+ payload: {
252
+ aud: '1',
253
+ iat: 1,
254
+ nonce: '342',
255
+ },
213
256
  },
214
257
  },
215
- });
258
+ );
216
259
 
217
260
  const results = await sdjwt.verify(presentation, ['foo'], true);
218
261
  expect(results).toBeDefined();
@@ -310,15 +353,19 @@ describe('index', () => {
310
353
  },
311
354
  );
312
355
 
313
- const presentation = await sdjwt.present(credential, ['foo'], {
314
- kb: {
315
- payload: {
316
- aud: '1',
317
- iat: 1,
318
- nonce: '342',
356
+ const presentation = await sdjwt.present(
357
+ credential,
358
+ { foo: true },
359
+ {
360
+ kb: {
361
+ payload: {
362
+ aud: '1',
363
+ iat: 1,
364
+ nonce: '342',
365
+ },
319
366
  },
320
367
  },
321
- });
368
+ );
322
369
  try {
323
370
  const results = await sdjwt.verify(presentation, ['foo'], true);
324
371
  } catch (e) {
@@ -350,15 +397,19 @@ describe('index', () => {
350
397
  },
351
398
  );
352
399
  try {
353
- const presentation = await sdjwt.present(credential, ['foo'], {
354
- kb: {
355
- payload: {
356
- aud: '1',
357
- iat: 1,
358
- nonce: '342',
400
+ const presentation = await sdjwt.present(
401
+ credential,
402
+ { foo: true },
403
+ {
404
+ kb: {
405
+ payload: {
406
+ aud: '1',
407
+ iat: 1,
408
+ nonce: '342',
409
+ },
359
410
  },
360
411
  },
361
- });
412
+ );
362
413
  } catch (e) {
363
414
  expect(e).toBeDefined();
364
415
  }
@@ -388,15 +439,19 @@ describe('index', () => {
388
439
  },
389
440
  );
390
441
 
391
- const presentation = await sdjwt.present(credential, ['foo'], {
392
- kb: {
393
- payload: {
394
- aud: '1',
395
- iat: 1,
396
- nonce: '342',
442
+ const presentation = await sdjwt.present(
443
+ credential,
444
+ { foo: true },
445
+ {
446
+ kb: {
447
+ payload: {
448
+ aud: '1',
449
+ iat: 1,
450
+ nonce: '342',
451
+ },
397
452
  },
398
453
  },
399
- });
454
+ );
400
455
  try {
401
456
  const results = await sdjwt.verify(presentation, ['foo'], true);
402
457
  } catch (e) {
@@ -427,15 +482,19 @@ describe('index', () => {
427
482
  },
428
483
  );
429
484
 
430
- const presentation = sdjwt.present(credential, ['foo'], {
431
- kb: {
432
- payload: {
433
- aud: '1',
434
- iat: 1,
435
- nonce: '342',
485
+ const presentation = sdjwt.present(
486
+ credential,
487
+ { foo: true },
488
+ {
489
+ kb: {
490
+ payload: {
491
+ aud: '1',
492
+ iat: 1,
493
+ nonce: '342',
494
+ },
436
495
  },
437
496
  },
438
- });
497
+ );
439
498
  expect(presentation).rejects.toThrow(
440
499
  'Key Binding sign algorithm not specified',
441
500
  );
@@ -465,7 +524,7 @@ describe('index', () => {
465
524
  expect(sdjwt.presentableKeys('')).rejects.toThrow('Hasher not found');
466
525
  expect(sdjwt.getClaims('')).rejects.toThrow('Hasher not found');
467
526
  expect(() => sdjwt.decode('')).toThrowError('Hasher not found');
468
- expect(sdjwt.present(credential, ['foo'])).rejects.toThrow(
527
+ expect(sdjwt.present(credential, { foo: true })).rejects.toThrow(
469
528
  'Hasher not found',
470
529
  );
471
530
  });
@@ -493,4 +552,42 @@ describe('index', () => {
493
552
  expect(keys).toBeDefined();
494
553
  expect(keys).toEqual(['foo']);
495
554
  });
555
+
556
+ test('present all disclosures with kb jwt', async () => {
557
+ const { signer } = createSignerVerifier();
558
+ const sdjwt = new SDJwtInstance<SdJwtPayload>({
559
+ signer,
560
+ kbSigner: signer,
561
+ hasher: digest,
562
+ saltGenerator: generateSalt,
563
+ signAlg: 'EdDSA',
564
+ kbSignAlg: 'EdDSA',
565
+ });
566
+ const credential = await sdjwt.issue(
567
+ {
568
+ foo: 'bar',
569
+ iss: 'Issuer',
570
+ iat: new Date().getTime(),
571
+ vct: '',
572
+ },
573
+ {
574
+ _sd: ['foo'],
575
+ },
576
+ );
577
+
578
+ const presentation = await sdjwt.present(credential, undefined, {
579
+ kb: {
580
+ payload: {
581
+ aud: '1',
582
+ iat: 1,
583
+ nonce: '342',
584
+ },
585
+ },
586
+ });
587
+
588
+ const decoded = await sdjwt.decode(presentation);
589
+ expect(decoded.jwt).toBeDefined();
590
+ expect(decoded.disclosures).toBeDefined();
591
+ expect(decoded.kbJwt).toBeDefined();
592
+ });
496
593
  });
@@ -1,8 +1,15 @@
1
1
  import { SDJWTException } from '@sd-jwt/utils';
2
2
  import { KBJwt } from '../kbjwt';
3
- import { KB_JWT_TYP, Signer, Verifier } from '@sd-jwt/types';
4
- import Crypto from 'node:crypto';
3
+ import {
4
+ JwtPayload,
5
+ KB_JWT_TYP,
6
+ KbVerifier,
7
+ Signer,
8
+ Verifier,
9
+ } from '@sd-jwt/types';
10
+ import Crypto, { KeyLike } from 'node:crypto';
5
11
  import { describe, expect, test } from 'vitest';
12
+ import { JWK, exportJWK, importJWK } from 'jose';
6
13
 
7
14
  describe('KB JWT', () => {
8
15
  test('create', async () => {
@@ -69,11 +76,27 @@ describe('KB JWT', () => {
69
76
  const sig = Crypto.sign(null, Buffer.from(data), privateKey);
70
77
  return Buffer.from(sig).toString('base64url');
71
78
  };
72
- const testVerifier: Verifier = async (data: string, sig: string) => {
79
+
80
+ const payload = {
81
+ cnf: {
82
+ jwk: await exportJWK(publicKey),
83
+ },
84
+ };
85
+
86
+ const testVerifier: KbVerifier = async (
87
+ data: string,
88
+ sig: string,
89
+ payload: JwtPayload,
90
+ ) => {
91
+ expect(payload).toStrictEqual(payload);
92
+ expect(payload.cnf?.jwk).toBeDefined();
93
+
94
+ const publicKey = payload.cnf?.jwk;
95
+
73
96
  return Crypto.verify(
74
97
  null,
75
98
  Buffer.from(data),
76
- publicKey,
99
+ (await importJWK(publicKey as JWK, 'EdDSA')) as KeyLike,
77
100
  Buffer.from(sig, 'base64url'),
78
101
  );
79
102
  };
@@ -91,7 +114,10 @@ describe('KB JWT', () => {
91
114
  });
92
115
  const encodedKbJwt = await kbJwt.sign(testSigner);
93
116
  const decoded = KBJwt.fromKBEncode(encodedKbJwt);
94
- const verified = await decoded.verify(testVerifier);
117
+ const verified = await decoded.verifyKB({
118
+ verifier: testVerifier,
119
+ payload,
120
+ });
95
121
  expect(verified).toStrictEqual({
96
122
  header: {
97
123
  typ: KB_JWT_TYP,
@@ -112,11 +138,26 @@ describe('KB JWT', () => {
112
138
  const sig = Crypto.sign(null, Buffer.from(data), privateKey);
113
139
  return Buffer.from(sig).toString('base64url');
114
140
  };
115
- const testVerifier: Verifier = async (data: string, sig: string) => {
141
+
142
+ const payload = {
143
+ cnf: {
144
+ jwk: await exportJWK(publicKey),
145
+ },
146
+ };
147
+ const testVerifier: KbVerifier = async (
148
+ data: string,
149
+ sig: string,
150
+ payload: JwtPayload,
151
+ ) => {
152
+ expect(payload).toStrictEqual(payload);
153
+ expect(payload.cnf?.jwk).toBeDefined();
154
+
155
+ const publicKey = payload.cnf?.jwk;
156
+
116
157
  return Crypto.verify(
117
158
  null,
118
159
  Buffer.from(data),
119
- publicKey,
160
+ (await importJWK(publicKey as JWK, 'EdDSA')) as KeyLike,
120
161
  Buffer.from(sig, 'base64url'),
121
162
  );
122
163
  };
@@ -136,26 +177,34 @@ describe('KB JWT', () => {
136
177
  const encodedKbJwt = await kbJwt.sign(testSigner);
137
178
  const decoded = KBJwt.fromKBEncode(encodedKbJwt);
138
179
  try {
139
- await decoded.verify(testVerifier);
180
+ await decoded.verifyKB({ verifier: testVerifier, payload });
140
181
  } catch (e: unknown) {
141
182
  const error = e as SDJWTException;
142
183
  expect(error.message).toBe('Invalid Key Binding Jwt');
143
184
  }
144
185
  });
145
186
 
146
- test('verify with custom Verifier', async () => {
187
+ test('verify failed with verifier return false', async () => {
147
188
  const { privateKey, publicKey } = Crypto.generateKeyPairSync('ed25519');
148
189
  const testSigner: Signer = async (data: string) => {
149
190
  const sig = Crypto.sign(null, Buffer.from(data), privateKey);
150
191
  return Buffer.from(sig).toString('base64url');
151
192
  };
152
- const testVerifier: Verifier = async (data: string, sig: string) => {
153
- return Crypto.verify(
154
- null,
155
- Buffer.from(data),
156
- publicKey,
157
- Buffer.from(sig, 'base64url'),
158
- );
193
+
194
+ const payload = {
195
+ cnf: {
196
+ jwk: await exportJWK(publicKey),
197
+ },
198
+ };
199
+ const testVerifier: KbVerifier = async (
200
+ data: string,
201
+ sig: string,
202
+ payload: JwtPayload,
203
+ ) => {
204
+ expect(payload).toStrictEqual(payload);
205
+ expect(payload.cnf?.jwk).toBeDefined();
206
+
207
+ return false;
159
208
  };
160
209
 
161
210
  const kbJwt = new KBJwt({
@@ -170,37 +219,37 @@ describe('KB JWT', () => {
170
219
  sd_hash: 'hash',
171
220
  },
172
221
  });
173
-
174
222
  const encodedKbJwt = await kbJwt.sign(testSigner);
175
223
  const decoded = KBJwt.fromKBEncode(encodedKbJwt);
176
- const verified = await decoded.verify(testVerifier);
177
- expect(verified).toStrictEqual({
178
- header: {
179
- typ: KB_JWT_TYP,
180
- alg: 'EdDSA',
181
- },
182
- payload: {
183
- iat: 1,
184
- aud: 'aud',
185
- nonce: 'nonce',
186
- sd_hash: 'hash',
187
- },
188
- });
224
+ try {
225
+ await decoded.verifyKB({ verifier: testVerifier, payload });
226
+ } catch (e: unknown) {
227
+ const error = e as SDJWTException;
228
+ expect(error.message).toBe('Verify Error: Invalid JWT Signature');
229
+ }
189
230
  });
190
231
 
191
- test('verify failed with custom Verifier', async () => {
232
+ test('verify failed with invalid jwt', async () => {
192
233
  const { privateKey, publicKey } = Crypto.generateKeyPairSync('ed25519');
193
234
  const testSigner: Signer = async (data: string) => {
194
235
  const sig = Crypto.sign(null, Buffer.from(data), privateKey);
195
236
  return Buffer.from(sig).toString('base64url');
196
237
  };
197
- const testVerifier: Verifier = async (data: string, sig: string) => {
198
- return Crypto.verify(
199
- null,
200
- Buffer.from(data),
201
- publicKey,
202
- Buffer.from(sig, 'base64url'),
203
- );
238
+
239
+ const payload = {
240
+ cnf: {
241
+ jwk: await exportJWK(publicKey),
242
+ },
243
+ };
244
+ const testVerifier: KbVerifier = async (
245
+ data: string,
246
+ sig: string,
247
+ payload: JwtPayload,
248
+ ) => {
249
+ expect(payload).toStrictEqual(payload);
250
+ expect(payload.cnf?.jwk).toBeDefined();
251
+
252
+ return false;
204
253
  };
205
254
 
206
255
  const kbJwt = new KBJwt({
@@ -212,17 +261,17 @@ describe('KB JWT', () => {
212
261
  iat: 1,
213
262
  aud: 'aud',
214
263
  nonce: 'nonce',
215
- sd_hash: '',
264
+ sd_hash: 'hash',
216
265
  },
217
266
  });
218
-
219
267
  const encodedKbJwt = await kbJwt.sign(testSigner);
220
268
  const decoded = KBJwt.fromKBEncode(encodedKbJwt);
269
+ decoded.signature = undefined;
221
270
  try {
222
- await decoded.verify(testVerifier);
271
+ await decoded.verifyKB({ verifier: testVerifier, payload });
223
272
  } catch (e: unknown) {
224
273
  const error = e as SDJWTException;
225
- expect(error.message).toBe('Invalid Key Binding Jwt');
274
+ expect(error.message).toBe('Verify Error: Invalid JWT');
226
275
  }
227
276
  });
228
277
 
@@ -232,11 +281,25 @@ describe('KB JWT', () => {
232
281
  const sig = Crypto.sign(null, Buffer.from(data), privateKey);
233
282
  return Buffer.from(sig).toString('base64url');
234
283
  };
235
- const testVerifier: Verifier = async (data: string, sig: string) => {
284
+ const payload = {
285
+ cnf: {
286
+ jwk: await exportJWK(publicKey),
287
+ },
288
+ };
289
+ const testVerifier: KbVerifier = async (
290
+ data: string,
291
+ sig: string,
292
+ payload: JwtPayload,
293
+ ) => {
294
+ expect(payload).toStrictEqual(payload);
295
+ expect(payload.cnf?.jwk).toBeDefined();
296
+
297
+ const publicKey = payload.cnf?.jwk;
298
+
236
299
  return Crypto.verify(
237
300
  null,
238
301
  Buffer.from(data),
239
- publicKey,
302
+ (await importJWK(publicKey as JWK, 'EdDSA')) as KeyLike,
240
303
  Buffer.from(sig, 'base64url'),
241
304
  );
242
305
  };
@@ -258,7 +321,10 @@ describe('KB JWT', () => {
258
321
 
259
322
  const encodedKbJwt = await kbJwt.sign(testSigner);
260
323
  const decoded = KBJwt.fromKBEncode(encodedKbJwt);
261
- const verified = await decoded.verify(testVerifier);
324
+ const verified = await decoded.verifyKB({
325
+ verifier: testVerifier,
326
+ payload,
327
+ });
262
328
  expect(verified).toStrictEqual({
263
329
  header: {
264
330
  typ: KB_JWT_TYP,
@@ -1,6 +1,11 @@
1
1
  import Crypto from 'node:crypto';
2
2
  import { SDJwtInstance, SdJwtPayload } from '../src';
3
- import { DisclosureFrame, Signer, Verifier } from '@sd-jwt/types';
3
+ import {
4
+ DisclosureFrame,
5
+ PresentationFrame,
6
+ Signer,
7
+ Verifier,
8
+ } from '@sd-jwt/types';
4
9
  import fs from 'fs';
5
10
  import path from 'path';
6
11
  import { describe, expect, test } from 'vitest';
@@ -105,7 +110,10 @@ describe('App', () => {
105
110
  'id',
106
111
  ]);
107
112
 
108
- const presentationFrame = ['firstname', 'id'];
113
+ const presentationFrame = {
114
+ firstname: true,
115
+ id: true,
116
+ };
109
117
  const presentedSDJwt = await sdjwt.present(encodedSdjwt, presentationFrame);
110
118
  expect(presentedSDJwt).toBeDefined();
111
119
 
@@ -215,7 +223,7 @@ async function JSONtest(filename: string) {
215
223
 
216
224
  const presentedSDJwt = await sdjwt.present(
217
225
  encodedSdjwt,
218
- test.presentationKeys,
226
+ test.presentationFrames,
219
227
  );
220
228
 
221
229
  expect(presentedSDJwt).toBeDefined();
@@ -236,7 +244,7 @@ async function JSONtest(filename: string) {
236
244
  type TestJson = {
237
245
  claims: SdJwtPayload;
238
246
  disclosureFrame: DisclosureFrame<SdJwtPayload>;
239
- presentationKeys: string[];
247
+ presentationFrames: PresentationFrame<SdJwtPayload>;
240
248
  presenatedClaims: object;
241
249
  requiredClaimKeys: string[];
242
250
  };
@@ -7,14 +7,16 @@
7
7
  "_sd": [0, 1, 2, 3, 4, 5]
8
8
  }
9
9
  },
10
- "presentationKeys": [
11
- "data_types.0",
12
- "data_types.1",
13
- "data_types.2",
14
- "data_types.3",
15
- "data_types.4",
16
- "data_types.5"
17
- ],
10
+ "presentationFrames": {
11
+ "data_types": {
12
+ "0": true,
13
+ "1": true,
14
+ "2": true,
15
+ "3": true,
16
+ "4": true,
17
+ "5": true
18
+ }
19
+ },
18
20
  "presenatedClaims": {
19
21
  "data_types": [null, 42, 3.14, "foo", ["Test"], { "foo": "bar" }]
20
22
  },
@@ -11,7 +11,7 @@
11
11
  "_sd": ["13", "18", "21"]
12
12
  }
13
13
  },
14
- "presentationKeys": ["is_over.18"],
14
+ "presentationFrames": { "is_over": { "18": true } },
15
15
  "presenatedClaims": {
16
16
  "is_over": {
17
17
  "18": false