@sphereon/ssi-sdk.vc-status-list 0.32.1-feature.MWALL.715.49 → 0.32.1-feature.SDK.56.oauth.status.list.34

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.
Files changed (51) hide show
  1. package/dist/functions.d.ts +13 -13
  2. package/dist/functions.d.ts.map +1 -1
  3. package/dist/functions.js +40 -109
  4. package/dist/functions.js.map +1 -1
  5. package/dist/impl/IStatusList.d.ts +22 -0
  6. package/dist/impl/IStatusList.d.ts.map +1 -0
  7. package/dist/impl/IStatusList.js +3 -0
  8. package/dist/impl/IStatusList.js.map +1 -0
  9. package/dist/impl/OAuthStatusList.d.ts +20 -0
  10. package/dist/impl/OAuthStatusList.d.ts.map +1 -0
  11. package/dist/impl/OAuthStatusList.js +147 -0
  12. package/dist/impl/OAuthStatusList.js.map +1 -0
  13. package/dist/impl/StatusList2021.d.ts +14 -0
  14. package/dist/impl/StatusList2021.d.ts.map +1 -0
  15. package/dist/impl/StatusList2021.js +153 -0
  16. package/dist/impl/StatusList2021.js.map +1 -0
  17. package/dist/impl/StatusListFactory.d.ts +12 -0
  18. package/dist/impl/StatusListFactory.d.ts.map +1 -0
  19. package/dist/impl/StatusListFactory.js +36 -0
  20. package/dist/impl/StatusListFactory.js.map +1 -0
  21. package/dist/impl/encoding/cbor.d.ts +6 -0
  22. package/dist/impl/encoding/cbor.d.ts.map +1 -0
  23. package/dist/impl/encoding/cbor.js +128 -0
  24. package/dist/impl/encoding/cbor.js.map +1 -0
  25. package/dist/impl/encoding/common.d.ts +12 -0
  26. package/dist/impl/encoding/common.d.ts.map +1 -0
  27. package/dist/impl/encoding/common.js +26 -0
  28. package/dist/impl/encoding/common.js.map +1 -0
  29. package/dist/impl/encoding/jwt.d.ts +7 -0
  30. package/dist/impl/encoding/jwt.d.ts.map +1 -0
  31. package/dist/impl/encoding/jwt.js +56 -0
  32. package/dist/impl/encoding/jwt.js.map +1 -0
  33. package/dist/types/index.d.ts +107 -29
  34. package/dist/types/index.d.ts.map +1 -1
  35. package/dist/types/index.js +12 -0
  36. package/dist/types/index.js.map +1 -1
  37. package/dist/utils.d.ts +17 -0
  38. package/dist/utils.d.ts.map +1 -0
  39. package/dist/utils.js +87 -0
  40. package/dist/utils.js.map +1 -0
  41. package/package.json +13 -5
  42. package/src/functions.ts +59 -158
  43. package/src/impl/IStatusList.ts +36 -0
  44. package/src/impl/OAuthStatusList.ts +165 -0
  45. package/src/impl/StatusList2021.ts +195 -0
  46. package/src/impl/StatusListFactory.ts +39 -0
  47. package/src/impl/encoding/cbor.ts +152 -0
  48. package/src/impl/encoding/common.ts +25 -0
  49. package/src/impl/encoding/jwt.ts +54 -0
  50. package/src/types/index.ts +123 -41
  51. package/src/utils.ts +91 -0
@@ -0,0 +1,195 @@
1
+ import { IAgentContext, ICredentialPlugin, ProofFormat as VmoProofFormat } from '@veramo/core'
2
+ import { IIdentifierResolution } from '@sphereon/ssi-sdk-ext.identifier-resolution'
3
+ import { CredentialMapper, DocumentFormat, IIssuer, ProofFormat, StatusListType, StatusListVerifiableCredential } from '@sphereon/ssi-types'
4
+
5
+ import { StatusList } from '@sphereon/vc-status-list'
6
+ import { IStatusList } from './IStatusList'
7
+ import {
8
+ CheckStatusIndexArgs,
9
+ CreateStatusListArgs,
10
+ Status2021,
11
+ StatusListResult,
12
+ UpdateStatusListFromEncodedListArgs,
13
+ UpdateStatusListIndexArgs,
14
+ } from '../types'
15
+ import { assertValidProofType, getAssertedProperty, getAssertedValue, getAssertedValues } from '../utils'
16
+
17
+ export const DEFAULT_LIST_LENGTH = 250000
18
+ export const DEFAULT_PROOF_FORMAT = 'lds' as VmoProofFormat
19
+
20
+ export class StatusList2021Implementation implements IStatusList {
21
+ async createNewStatusList(
22
+ args: CreateStatusListArgs,
23
+ context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
24
+ ): Promise<StatusListResult> {
25
+ const length = args?.length ?? DEFAULT_LIST_LENGTH
26
+ const proofFormat: ProofFormat = args?.proofFormat ?? DEFAULT_PROOF_FORMAT
27
+ assertValidProofType(StatusListType.StatusList2021, proofFormat)
28
+ const vmoProofFormat: VmoProofFormat = proofFormat as VmoProofFormat
29
+
30
+ const { issuer, id } = args
31
+ const correlationId = getAssertedValue('correlationId', args.correlationId)
32
+
33
+ const list = new StatusList({ length })
34
+ const encodedList = await list.encode()
35
+ const statusPurpose = 'revocation'
36
+
37
+ const statusListCredential = await this.createVerifiableCredential(
38
+ {
39
+ ...args,
40
+ encodedList,
41
+ proofFormat: vmoProofFormat,
42
+ },
43
+ context,
44
+ )
45
+
46
+ return {
47
+ encodedList,
48
+ statusListCredential: statusListCredential,
49
+ statusList2021: {
50
+ statusPurpose,
51
+ indexingDirection: 'rightToLeft',
52
+ },
53
+ length,
54
+ type: StatusListType.StatusList2021,
55
+ proofFormat,
56
+ id,
57
+ correlationId,
58
+ issuer,
59
+ }
60
+ }
61
+
62
+ async updateStatusListIndex(
63
+ args: UpdateStatusListIndexArgs,
64
+ context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
65
+ ): Promise<StatusListResult> {
66
+ const credential = args.statusListCredential
67
+ const uniform = CredentialMapper.toUniformCredential(credential)
68
+ const { issuer, credentialSubject } = uniform
69
+ const id = getAssertedValue('id', uniform.id)
70
+ const origEncodedList = getAssertedProperty('encodedList', credentialSubject)
71
+
72
+ const index = typeof args.statusListIndex === 'number' ? args.statusListIndex : parseInt(args.statusListIndex)
73
+ const statusList = await StatusList.decode({ encodedList: origEncodedList })
74
+ statusList.setStatus(index, args.value != 0)
75
+ const encodedList = await statusList.encode()
76
+
77
+ const updatedCredential = await this.createVerifiableCredential(
78
+ {
79
+ ...args,
80
+ id,
81
+ issuer,
82
+ encodedList,
83
+ proofFormat: CredentialMapper.detectDocumentType(credential) === DocumentFormat.JWT ? 'jwt' : 'lds',
84
+ },
85
+ context,
86
+ )
87
+
88
+ return {
89
+ statusListCredential: updatedCredential,
90
+ encodedList,
91
+ statusList2021: {
92
+ ...('statusPurpose' in credentialSubject ? { statusPurpose: credentialSubject.statusPurpose } : {}),
93
+ indexingDirection: 'rightToLeft',
94
+ },
95
+ length: statusList.length - 1,
96
+ type: StatusListType.StatusList2021,
97
+ proofFormat: CredentialMapper.detectDocumentType(credential) === DocumentFormat.JWT ? 'jwt' : 'lds',
98
+ id,
99
+ issuer,
100
+ }
101
+ }
102
+
103
+ async updateStatusListFromEncodedList(
104
+ args: UpdateStatusListFromEncodedListArgs,
105
+ context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
106
+ ): Promise<StatusListResult> {
107
+ if (!args.statusList2021) {
108
+ throw new Error('statusList2021 options required for type StatusList2021')
109
+ }
110
+ const proofFormat: ProofFormat = args?.proofFormat ?? DEFAULT_PROOF_FORMAT
111
+ assertValidProofType(StatusListType.StatusList2021, proofFormat)
112
+ const vmoProofFormat: VmoProofFormat = proofFormat as VmoProofFormat
113
+
114
+ const { issuer, id } = getAssertedValues(args)
115
+ const statusList = await StatusList.decode({ encodedList: args.encodedList })
116
+ const index = typeof args.statusListIndex === 'number' ? args.statusListIndex : parseInt(args.statusListIndex)
117
+ statusList.setStatus(index, args.value)
118
+
119
+ const newEncodedList = await statusList.encode()
120
+ const credential = await this.createVerifiableCredential(
121
+ {
122
+ id,
123
+ issuer,
124
+ encodedList: newEncodedList,
125
+ proofFormat: vmoProofFormat,
126
+ keyRef: args.keyRef,
127
+ },
128
+ context,
129
+ )
130
+
131
+ return {
132
+ type: StatusListType.StatusList2021,
133
+ statusListCredential: credential,
134
+ encodedList: newEncodedList,
135
+ statusList2021: {
136
+ statusPurpose: args.statusList2021.statusPurpose,
137
+ indexingDirection: 'rightToLeft',
138
+ },
139
+ length: statusList.length,
140
+ proofFormat: args.proofFormat ?? 'lds',
141
+ id: id,
142
+ issuer: issuer,
143
+ }
144
+ }
145
+
146
+ async checkStatusIndex(args: CheckStatusIndexArgs): Promise<number | Status2021> {
147
+ const uniform = CredentialMapper.toUniformCredential(args.statusListCredential)
148
+ const { credentialSubject } = uniform
149
+ const encodedList = getAssertedProperty('encodedList', credentialSubject)
150
+
151
+ const statusList = await StatusList.decode({ encodedList })
152
+ const status = statusList.getStatus(typeof args.statusListIndex === 'number' ? args.statusListIndex : parseInt(args.statusListIndex))
153
+ return status ? Status2021.Invalid : Status2021.Valid
154
+ }
155
+
156
+ private async createVerifiableCredential(
157
+ args: {
158
+ id: string
159
+ issuer: string | IIssuer
160
+ encodedList: string
161
+ proofFormat: VmoProofFormat
162
+ keyRef?: string
163
+ },
164
+ context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
165
+ ): Promise<StatusListVerifiableCredential> {
166
+ const identifier = await context.agent.identifierManagedGet({
167
+ identifier: typeof args.issuer === 'string' ? args.issuer : args.issuer.id,
168
+ vmRelationship: 'assertionMethod',
169
+ offlineWhenNoDIDRegistered: true,
170
+ })
171
+
172
+ const credential = {
173
+ '@context': ['https://www.w3.org/2018/credentials/v1', 'https://w3id.org/vc/status-list/2021/v1'],
174
+ id: args.id,
175
+ issuer: args.issuer,
176
+ type: ['VerifiableCredential', 'StatusList2021Credential'],
177
+ credentialSubject: {
178
+ id: args.id,
179
+ type: 'StatusList2021',
180
+ statusPurpose: 'revocation',
181
+ encodedList: args.encodedList,
182
+ },
183
+ }
184
+
185
+ const verifiableCredential = await context.agent.createVerifiableCredential({
186
+ credential,
187
+ keyRef: args.keyRef ?? identifier.kmsKeyRef,
188
+ proofFormat: args.proofFormat,
189
+ fetchRemoteContexts: true,
190
+ })
191
+
192
+ return CredentialMapper.toWrappedVerifiableCredential(verifiableCredential as StatusListVerifiableCredential)
193
+ .original as StatusListVerifiableCredential
194
+ }
195
+ }
@@ -0,0 +1,39 @@
1
+ import { IStatusList } from './IStatusList'
2
+ import { StatusList2021Implementation } from './StatusList2021'
3
+ import { OAuthStatusListImplementation } from './OAuthStatusList'
4
+ import { StatusListType } from '@sphereon/ssi-types'
5
+
6
+ export class StatusListFactory {
7
+ private static instance: StatusListFactory
8
+ private implementations: Map<StatusListType, IStatusList>
9
+
10
+ private constructor() {
11
+ this.implementations = new Map()
12
+ this.implementations.set(StatusListType.StatusList2021, new StatusList2021Implementation())
13
+ this.implementations.set(StatusListType.OAuthStatusList, new OAuthStatusListImplementation())
14
+ }
15
+
16
+ public static getInstance(): StatusListFactory {
17
+ if (!StatusListFactory.instance) {
18
+ StatusListFactory.instance = new StatusListFactory()
19
+ }
20
+ return StatusListFactory.instance
21
+ }
22
+
23
+ public getImplementation(type: StatusListType): IStatusList {
24
+ const implementation = this.implementations.get(type)
25
+ if (!implementation) {
26
+ throw new Error(`No implementation found for status list type: ${type}`)
27
+ }
28
+ return implementation
29
+ }
30
+
31
+ // Optional: Method to register custom implementations if needed
32
+ public registerImplementation(type: StatusListType, implementation: IStatusList): void {
33
+ this.implementations.set(type, implementation)
34
+ }
35
+ }
36
+
37
+ export function getStatusListImplementation(type: StatusListType): IStatusList {
38
+ return StatusListFactory.getInstance().getImplementation(type)
39
+ }
@@ -0,0 +1,152 @@
1
+ import { StatusList } from '@sd-jwt/jwt-status-list'
2
+ import { deflate, inflate } from 'pako'
3
+ import { com, kotlin } from '@sphereon/kmp-cbor'
4
+ import base64url from 'base64url'
5
+ import { IRequiredContext, SignedStatusListData } from '../../types'
6
+ import { DecodedStatusListPayload, resolveIdentifier } from './common'
7
+ import { BitsPerStatus } from '@sd-jwt/jwt-status-list/dist'
8
+
9
+ const cbor = com.sphereon.cbor
10
+ const kmp = com.sphereon.kmp
11
+ const decompressRawStatusList = (StatusList as any).decodeStatusList.bind(StatusList)
12
+
13
+ const CWT_CLAIMS = {
14
+ SUBJECT: 2,
15
+ ISSUER: 1,
16
+ ISSUED_AT: 6,
17
+ EXPIRATION: 4,
18
+ TIME_TO_LIVE: 65534,
19
+ STATUS_LIST: 65533,
20
+ } as const
21
+
22
+ export const createSignedCbor = async (
23
+ context: IRequiredContext,
24
+ statusList: StatusList,
25
+ issuerString: string,
26
+ id: string,
27
+ keyRef?: string,
28
+ ): Promise<SignedStatusListData> => {
29
+ const identifier = await resolveIdentifier(context, issuerString, keyRef)
30
+
31
+ const encodeStatusList = statusList.encodeStatusList()
32
+ const compressedList = deflate(encodeStatusList, { level: 9 })
33
+ const compressedBytes = new Int8Array(compressedList)
34
+
35
+ const statusListMap = new cbor.CborMap(
36
+ kotlin.collections.KtMutableMap.fromJsMap(
37
+ new Map<com.sphereon.cbor.CborString, com.sphereon.cbor.CborItem<any>>([
38
+ [new cbor.CborString('bits'), new cbor.CborUInt(kmp.LongKMP.fromNumber(statusList.getBitsPerStatus()))],
39
+ [new cbor.CborString('lst'), new cbor.CborByteString(compressedBytes)],
40
+ ]),
41
+ ),
42
+ )
43
+
44
+ const exp = Math.floor(new Date().getTime() / 1000)
45
+ const ttl = 65535 // FIXME figure out what value should be / come from and what the difference is with exp
46
+ const claimsEntries: Array<[com.sphereon.cbor.CborUInt, com.sphereon.cbor.CborItem<any>]> = [
47
+ [new cbor.CborUInt(kmp.LongKMP.fromNumber(CWT_CLAIMS.SUBJECT)), new cbor.CborString(id)], // "sub"
48
+ [new cbor.CborUInt(kmp.LongKMP.fromNumber(CWT_CLAIMS.ISSUER)), new cbor.CborString(issuerString)], // "iss"
49
+ [
50
+ new cbor.CborUInt(kmp.LongKMP.fromNumber(CWT_CLAIMS.ISSUED_AT)),
51
+ new cbor.CborUInt(kmp.LongKMP.fromNumber(Math.floor(Date.now() / 1000))), // "iat"
52
+ ],
53
+ ]
54
+
55
+ if (exp) {
56
+ claimsEntries.push([
57
+ new cbor.CborUInt(kmp.LongKMP.fromNumber(CWT_CLAIMS.EXPIRATION)),
58
+ new cbor.CborUInt(kmp.LongKMP.fromNumber(exp)), // "exp"
59
+ ])
60
+ }
61
+
62
+ if (ttl) {
63
+ claimsEntries.push([
64
+ new cbor.CborUInt(kmp.LongKMP.fromNumber(CWT_CLAIMS.TIME_TO_LIVE)),
65
+ new cbor.CborUInt(kmp.LongKMP.fromNumber(ttl)), // "time to live"
66
+ ])
67
+ }
68
+
69
+ claimsEntries.push([new cbor.CborUInt(kmp.LongKMP.fromNumber(CWT_CLAIMS.STATUS_LIST)), statusListMap])
70
+
71
+ const claimsMap = new cbor.CborMap(kotlin.collections.KtMutableMap.fromJsMap(new Map(claimsEntries)))
72
+
73
+ const protectedHeader = new cbor.CborMap(
74
+ kotlin.collections.KtMutableMap.fromJsMap(
75
+ new Map([[new cbor.CborUInt(kmp.LongKMP.fromNumber(16)), new cbor.CborString('statuslist+cwt')]]), // "type"
76
+ ),
77
+ )
78
+ const protectedHeaderEncoded = cbor.Cbor.encode(protectedHeader)
79
+ const claimsEncoded = cbor.Cbor.encode(claimsMap)
80
+
81
+ const signedCWT = await context.agent.keyManagerSign({
82
+ keyRef: identifier.kmsKeyRef,
83
+ data: claimsEncoded,
84
+ encoding: undefined,
85
+ })
86
+
87
+ const protectedHeaderEncodedInt8 = new Int8Array(protectedHeaderEncoded)
88
+ const claimsEncodedInt8 = new Int8Array(claimsEncoded)
89
+ const signatureInt8 = new Int8Array(signedCWT.signature)
90
+
91
+ const cwtArray = new cbor.CborArray(
92
+ kotlin.collections.KtMutableList.fromJsArray([
93
+ new cbor.CborByteString(protectedHeaderEncodedInt8),
94
+ new cbor.CborByteString(claimsEncodedInt8),
95
+ new cbor.CborByteString(signatureInt8),
96
+ ]),
97
+ )
98
+
99
+ const cwtEncoded = cbor.Cbor.encode(cwtArray)
100
+ const cwtBuffer = Buffer.from(cwtEncoded)
101
+ return {
102
+ statusListCredential: base64url.encode(cwtBuffer),
103
+ encodedList: base64url.encode(compressedList as Buffer), // JS in @sd-jwt/jwt-status-list drops it in like this, so keep the same method
104
+ }
105
+ }
106
+
107
+ const getCborValueFromMap = <T>(map: Map<com.sphereon.cbor.CborItem<any>, com.sphereon.cbor.CborItem<any>>, key: number): T | never => {
108
+ const value = map.get(new com.sphereon.cbor.CborUInt(kmp.LongKMP.fromNumber(key)))
109
+ if (!value) {
110
+ throw new Error(`Required claim ${key} not found`)
111
+ }
112
+ return value.value as T
113
+ }
114
+
115
+ export const decodeStatusListCWT = (cwt: string): DecodedStatusListPayload => {
116
+ const encodedCbor = base64url.toBuffer(cwt)
117
+ const encodedCborArray = new Int8Array(encodedCbor)
118
+ const decodedCbor = com.sphereon.cbor.Cbor.decode(encodedCborArray)
119
+
120
+ if (!(decodedCbor instanceof com.sphereon.cbor.CborArray)) {
121
+ throw new Error('Invalid CWT format: Expected a CBOR array')
122
+ }
123
+
124
+ const [, payload] = decodedCbor.value.asJsArrayView()
125
+ if (!(payload instanceof com.sphereon.cbor.CborByteString)) {
126
+ throw new Error('Invalid payload format: Expected a CBOR ByteString')
127
+ }
128
+
129
+ const claims = com.sphereon.cbor.Cbor.decode(payload.value)
130
+ if (!(claims instanceof com.sphereon.cbor.CborMap)) {
131
+ throw new Error('Invalid claims format: Expected a CBOR map')
132
+ }
133
+
134
+ const claimsMap = claims.value.asJsMapView()
135
+
136
+ const statusListMap = claimsMap.get(new com.sphereon.cbor.CborUInt(kmp.LongKMP.fromNumber(65533))).value.asJsMapView()
137
+
138
+ const bits = Number(statusListMap.get(new com.sphereon.cbor.CborString('bits')).value) as BitsPerStatus
139
+ const decoded = new Uint8Array(statusListMap.get(new com.sphereon.cbor.CborString('lst')).value)
140
+ const uint8Array = inflate(decoded)
141
+ const rawStatusList = decompressRawStatusList(uint8Array, bits)
142
+ const statusList = new StatusList(rawStatusList, bits)
143
+
144
+ return {
145
+ issuer: getCborValueFromMap<string>(claimsMap, CWT_CLAIMS.ISSUER),
146
+ id: getCborValueFromMap<string>(claimsMap, CWT_CLAIMS.SUBJECT),
147
+ statusList,
148
+ exp: getCborValueFromMap<number>(claimsMap, CWT_CLAIMS.EXPIRATION),
149
+ ttl: getCborValueFromMap<number>(claimsMap, CWT_CLAIMS.TIME_TO_LIVE),
150
+ iat: Number(getCborValueFromMap<number>(claimsMap, CWT_CLAIMS.ISSUED_AT)),
151
+ }
152
+ }
@@ -0,0 +1,25 @@
1
+ import { IRequiredContext } from '../../types'
2
+ import { StatusList } from '@sd-jwt/jwt-status-list'
3
+
4
+ export interface DecodedStatusListPayload {
5
+ issuer: string
6
+ id: string
7
+ statusList: StatusList
8
+ exp?: number
9
+ ttl?: number
10
+ iat: number
11
+ }
12
+
13
+ export const resolveIdentifier = async (context: IRequiredContext, issuer: string, keyRef?: string) => {
14
+ if (keyRef) {
15
+ return await context.agent.identifierManagedGetByKid({
16
+ identifier: keyRef,
17
+ })
18
+ }
19
+
20
+ return await context.agent.identifierManagedGet({
21
+ identifier: issuer,
22
+ vmRelationship: 'assertionMethod',
23
+ offlineWhenNoDIDRegistered: true,
24
+ })
25
+ }
@@ -0,0 +1,54 @@
1
+ import { CompactJWT } from '@sphereon/ssi-types'
2
+ import { createHeaderAndPayload, StatusList, StatusListJWTPayload } from '@sd-jwt/jwt-status-list'
3
+ import base64url from 'base64url'
4
+ import { JWTPayload } from 'did-jwt'
5
+ import { STATUS_LIST_JWT_HEADER } from '../OAuthStatusList'
6
+ import { IRequiredContext, SignedStatusListData } from '../../types'
7
+ import { DecodedStatusListPayload, resolveIdentifier } from './common'
8
+
9
+ export const createSignedJwt = async (
10
+ context: IRequiredContext,
11
+ statusList: StatusList,
12
+ issuerString: string,
13
+ id: string,
14
+ keyRef?: string,
15
+ ): Promise<SignedStatusListData> => {
16
+ const identifier = await resolveIdentifier(context, issuerString, keyRef)
17
+ const payload: JWTPayload = {
18
+ iss: issuerString,
19
+ sub: id,
20
+ iat: Math.floor(Date.now() / 1000),
21
+ }
22
+
23
+ const values = createHeaderAndPayload(statusList, payload, STATUS_LIST_JWT_HEADER)
24
+ const signedJwt = await context.agent.jwtCreateJwsCompactSignature({
25
+ issuer: { ...identifier, noIssPayloadUpdate: false },
26
+ protectedHeader: values.header,
27
+ payload: values.payload,
28
+ })
29
+
30
+ return {
31
+ statusListCredential: signedJwt.jwt,
32
+ encodedList: (values.payload as StatusListJWTPayload).status_list.lst,
33
+ }
34
+ }
35
+
36
+ export const decodeStatusListJWT = (jwt: CompactJWT): DecodedStatusListPayload => {
37
+ const [, payloadBase64] = jwt.split('.')
38
+ const payload = JSON.parse(base64url.decode(payloadBase64))
39
+
40
+ if (!payload.iss || !payload.sub || !payload.status_list) {
41
+ throw new Error('Missing required fields in JWT payload')
42
+ }
43
+
44
+ const statusList = StatusList.decompressStatusList(payload.status_list.lst, payload.status_list.bits)
45
+
46
+ return {
47
+ issuer: payload.iss,
48
+ id: payload.sub,
49
+ statusList,
50
+ exp: payload.exp,
51
+ ttl: payload.ttl,
52
+ iat: payload.iat,
53
+ }
54
+ }