Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
795ad38
feat: implement credential status management with StatusArray, Status…
dinkar-jain May 29, 2025
0e82b46
refactor: update StatusArray and StatusList constructors to use optio…
dinkar-jain Jun 4, 2025
bd500cf
refactor: update StatusArray and StatusList to use AllowedBitsPerEntr…
dinkar-jain Jun 4, 2025
8a552a4
refactor: update enum values in CwtStatusListClaims and CwtStandardCl…
dinkar-jain Jun 4, 2025
6f54b85
feat: added CWT status token verification
dinkar-jain Jun 8, 2025
113785c
refactor: rename constructor parameter in StatusArray and update vari…
dinkar-jain Jun 8, 2025
f95c6f9
refactor: simplify return statements in StatusList and CWTStatusToken…
dinkar-jain Jun 8, 2025
1ed21b0
feat: implement CoseType enum and update CWTStatusToken to use CoseTy…
dinkar-jain Jun 15, 2025
4c33d5d
fix: standardize error message formatting in CWTStatusToken verification
dinkar-jain Jun 15, 2025
fcb5c8f
feat: migrate zlib usage to pako for compression and decompression in…
dinkar-jain Jun 15, 2025
19a72b6
feat: add fetchStatusListUri method to retrieve status list from a gi…
dinkar-jain Jun 16, 2025
e4af828
feat: add abort-controller and node-fetch for improved fetch handling…
dinkar-jain Jun 16, 2025
21fdb40
refactor: rename CoseType to CoseStructureType and update related ref…
dinkar-jain Jun 17, 2025
142974b
refactor: update interface visibility and method names in CwtStatusTo…
dinkar-jain Jun 17, 2025
8328285
refactor: remove unused dependencies and update TypeScript lib config…
dinkar-jain Jun 17, 2025
b354d29
refactor: update StatusArray property naming and improve error messag…
dinkar-jain Jun 17, 2025
13caadc
refactor: update CoseStructureType usage in CwtStatusTokenOptions and…
dinkar-jain Jun 17, 2025
e7cb9af
refactor: add mdocContext to CwtStatusTokenOptions and CwtVerifyOptio…
dinkar-jain Jun 17, 2025
e6b52b7
refactor: update CwtStatusToken to use new claims enumeration and dat…
dinkar-jain Jun 17, 2025
6b2285f
refactor: introduce custom error classes for status list validation; …
dinkar-jain Jun 19, 2025
6e9ca7b
refactor: add StatusInfo model and integrate status handling in Mobil…
dinkar-jain Jun 20, 2025
87a85a5
refactor: update StatusInfo handling in IssuerSignedBuilder and Mobil…
dinkar-jain Jun 30, 2025
e239bb1
refactor: simplify StatusInfo structure by removing unnecessary claim…
dinkar-jain Jun 30, 2025
9ffcc8b
refactor: rename fetchStatusListUri to fetchStatusList for consistenc…
dinkar-jain Jul 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
"changeset-version": "pnpm changeset version && pnpm style:fix"
},
"dependencies": {
"buffer": "^6.0.3"
"buffer": "^6.0.3",
"pako": "^2.1.0"
},
"devDependencies": {
"@biomejs/biome": "^1.9.4",
Expand All @@ -40,6 +41,7 @@
"@panva/hkdf": "^1.2.1",
"@peculiar/x509": "^1.12.3",
"@types/node": "^20.14.11",
"@types/pako": "^2.0.3",
"jose": "^5.9.3",
"tsup": "^8.3.5",
"typescript": "^5.6.3",
Expand Down
16 changes: 16 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/cose/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './mac0'
export * from './sign1'
export * from './error'
export * from './key'
export * from './type'
24 changes: 24 additions & 0 deletions src/cose/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export enum CoseStructureType {
Sign = 'sign',
Sign1 = 'sign1',
Encrypt = 'encrypt',
Encrypt0 = 'encrypt0',
Mac = 'mac',
Mac0 = 'mac0',
}
export enum CoseStructureTag {
Sign = 98,
Sign1 = 18,
Encrypt = 96,
Encrypt0 = 16,
Mac = 97,
Mac0 = 17,
}
export const CoseTypeToTag: Record<CoseStructureType, CoseStructureTag> = {
[CoseStructureType.Sign]: CoseStructureTag.Sign,
[CoseStructureType.Sign1]: CoseStructureTag.Sign1,
[CoseStructureType.Encrypt]: CoseStructureTag.Encrypt,
[CoseStructureType.Encrypt0]: CoseStructureTag.Encrypt0,
[CoseStructureType.Mac]: CoseStructureTag.Mac,
[CoseStructureType.Mac0]: CoseStructureTag.Mac0,
}
9 changes: 9 additions & 0 deletions src/credential-status/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// biome-ignore format:
class StatusListError extends Error { constructor(message: string = new.target.name) { super(message) } }

export class InvalidStatusListFormatError extends StatusListError {}
export class InvalidStatusListBitsError extends StatusListError {
constructor(bits: number, allowedBits: readonly number[]) {
super(`Invalid bits per entry: ${bits}. Allowed values are ${allowedBits.join(', ')}.`)
}
}
3 changes: 3 additions & 0 deletions src/credential-status/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './status-array'
export * from './status-list'
export * from './status-token'
56 changes: 56 additions & 0 deletions src/credential-status/status-array.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import * as zlib from 'pako'

const arraySize = 1024
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we hardcode it to 1024?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the specs there is no limit actually

export const allowedBitsPerEntry = [1, 2, 4, 8] as const
export type AllowedBitsPerEntry = (typeof allowedBitsPerEntry)[number]

export class StatusArray {
private readonly _bitsPerEntry: AllowedBitsPerEntry
private readonly statusBitMask: number
private readonly data: Uint8Array

constructor(bitsPerEntry: AllowedBitsPerEntry, byteArr?: Uint8Array) {
if (!allowedBitsPerEntry.includes(bitsPerEntry)) {
throw new Error(`Only bits ${allowedBitsPerEntry.join(', ')} per entry are allowed.`)
}

this._bitsPerEntry = bitsPerEntry
this.statusBitMask = (1 << bitsPerEntry) - 1
this.data = byteArr ? byteArr : new Uint8Array(arraySize)
}

private computeByteAndOffset(index: number): { byteIndex: number; bitOffset: number } {
const byteIndex = Math.floor((index * this._bitsPerEntry) / 8)
const bitOffset = (index * this._bitsPerEntry) % 8

return { byteIndex, bitOffset }
}

get bitsPerEntry(): AllowedBitsPerEntry {
return this._bitsPerEntry
}

set(index: number, status: number): void {
if (status < 0 || status > this.statusBitMask) {
throw new Error(`Invalid status: ${status}. Must be between 0 and ${this.statusBitMask}.`)
}

const { byteIndex, bitOffset } = this.computeByteAndOffset(index)

// Clear current bits
this.data[byteIndex] &= ~(this.statusBitMask << bitOffset)

// Set new status bits
this.data[byteIndex] |= (status & this.statusBitMask) << bitOffset
}

get(index: number): number {
const { byteIndex, bitOffset } = this.computeByteAndOffset(index)

return (this.data[byteIndex] >> bitOffset) & this.statusBitMask
}

compress(): Uint8Array {
return zlib.deflate(this.data)
}
}
56 changes: 56 additions & 0 deletions src/credential-status/status-list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import * as zlib from 'pako'
import { cborDecode, cborEncode } from '../cbor'
import { InvalidStatusListBitsError, InvalidStatusListFormatError } from './error'
import { type AllowedBitsPerEntry, StatusArray, allowedBitsPerEntry } from './status-array'

export interface CborStatusListOptions {
statusArray: StatusArray
aggregationUri?: string
}

export interface CborStatusList {
bits: AllowedBitsPerEntry
lst: Uint8Array
aggregation_uri?: string
}

export class StatusList {
static buildCborStatusList(options: CborStatusListOptions): Uint8Array {
const compressed = options.statusArray.compress()

const statusList: CborStatusList = {
bits: options.statusArray.bitsPerEntry,
lst: compressed,
}

if (options.aggregationUri) {
statusList.aggregation_uri = options.aggregationUri
}
return cborEncode(statusList)
}

static verifyStatus(cborStatusList: Uint8Array, index: number, expectedStatus: number): boolean {
const decoded = cborDecode(cborStatusList)
if (!(decoded instanceof Map)) {
throw new Error('Decoded CBOR data is not a Map.')
}

const statusList: CborStatusList = {
bits: decoded.get('bits') as AllowedBitsPerEntry,
lst: decoded.get('lst') as Uint8Array,
aggregation_uri: decoded.get('aggregation_uri') as string | undefined,
}
const { bits, lst } = statusList

if (!statusList || !lst || !bits) {
throw new InvalidStatusListFormatError()
}
if (!allowedBitsPerEntry.includes(bits)) {
throw new InvalidStatusListBitsError(bits, allowedBitsPerEntry)
}

const statusArray = new StatusArray(bits, zlib.inflate(lst))
const actualStatus = statusArray.get(index)
return actualStatus === expectedStatus
}
}
Loading