@sd-jwt/core
Version:
sd-jwt draft 7 implementation in typescript
946 lines (826 loc) • 27.7 kB
text/typescript
import { getSDAlgAndPayload } from './decode';
import { FlattenJSON } from './flattenJSON';
import { GeneralJSON } from './generalJSON';
import { Jwt, type VerifierOptions } from './jwt';
import { KBJwt } from './kbjwt';
import { pack, SDJwt } from './sdjwt';
import {
type DisclosureFrame,
type Hasher,
IANA_HASH_ALGORITHMS,
KB_JWT_TYP,
type KBOptions,
type PresentationFrame,
type SafeVerifyResult,
SD_DECOY,
SD_DIGEST,
type SDJWTCompact,
type SDJWTConfig,
type Signer,
type VerificationError,
type VerificationErrorCode,
} from './types';
import {
base64urlEncode,
ensureError,
SDJWTException,
uint8ArrayToBase64Url,
} from './utils';
import { decodeBase64urlJsonStrict } from './utils/strict-json';
export * from './decode';
export * from './decoy';
export * from './flattenJSON';
export * from './generalJSON';
export * from './jwt';
export * from './kbjwt';
export * from './present';
export * from './sdjwt';
// Re-export all types, utils, decode, and present functionality
export * from './types';
export * from './utils';
export type SdJwtPayload = Record<string, unknown>;
export class SDJwtInstance<ExtendedPayload extends SdJwtPayload, T = unknown> {
//header type
protected type?: string;
public static readonly DEFAULT_hashAlg = 'sha-256';
protected userConfig: SDJWTConfig<T> = {};
constructor(userConfig?: SDJWTConfig<T>) {
if (userConfig) {
if (
userConfig.hashAlg &&
!IANA_HASH_ALGORITHMS.includes(userConfig.hashAlg)
) {
throw new SDJWTException(
`Invalid hash algorithm: ${userConfig.hashAlg}`,
);
}
this.userConfig = userConfig;
}
}
private async createKBJwt(
options: KBOptions,
sdHash: string,
): Promise<KBJwt> {
if (!this.userConfig.kbSigner) {
throw new SDJWTException('Key Binding Signer not found');
}
if (!this.userConfig.kbSignAlg) {
throw new SDJWTException('Key Binding sign algorithm not specified');
}
const { payload } = options;
const kbJwt = new KBJwt({
header: {
typ: KB_JWT_TYP,
alg: this.userConfig.kbSignAlg,
},
payload: { ...payload, sd_hash: sdHash },
});
await kbJwt.sign(this.userConfig.kbSigner);
return kbJwt;
}
private async SignJwt(jwt: Jwt) {
if (!this.userConfig.signer) {
throw new SDJWTException('Signer not found');
}
await jwt.sign(this.userConfig.signer);
return jwt;
}
private async VerifyJwt(jwt: Jwt, options?: T & VerifierOptions) {
if (!this.userConfig.verifier) {
throw new SDJWTException('Verifier not found');
}
return jwt.verify<T>(this.userConfig.verifier, options);
}
public async issue<Payload extends ExtendedPayload>(
payload: Payload,
disclosureFrame?: DisclosureFrame<Payload>,
options?: {
header?: object; // This is for customizing the header of the jwt
},
): Promise<SDJWTCompact> {
if (!this.userConfig.hasher) {
throw new SDJWTException('Hasher not found');
}
if (!this.userConfig.saltGenerator) {
throw new SDJWTException('SaltGenerator not found');
}
if (!this.userConfig.signAlg) {
throw new SDJWTException('sign alogrithm not specified');
}
this.validateReservedFields<Payload>(payload);
this.validateDisclosureFrame<Payload>(disclosureFrame);
const hasher = this.userConfig.hasher;
const hashAlg = this.userConfig.hashAlg ?? SDJwtInstance.DEFAULT_hashAlg;
const { packedClaims, disclosures } = await pack(
payload,
disclosureFrame,
{ hasher, alg: hashAlg },
this.userConfig.saltGenerator,
);
const alg = this.userConfig.signAlg;
const OptionHeader = options?.header ?? {};
const CustomHeader = this.userConfig.omitTyp
? OptionHeader
: { typ: this.type, ...OptionHeader };
const header = { ...CustomHeader, alg };
const jwt = new Jwt({
header,
payload: {
...packedClaims,
_sd_alg: disclosureFrame ? hashAlg : undefined,
},
});
await this.SignJwt(jwt);
const sdJwt = new SDJwt({
jwt,
disclosures,
});
return sdJwt.encodeSDJwt();
}
/**
* Validates if the payload contains any reserved claim names. If so it will throw an error.
* @param payload
* @returns
*/
protected validateReservedFields<T extends ExtendedPayload>(payload: T) {
const reservedFields = new Set([SD_DIGEST, '_sd_alg', SD_DECOY]);
const visit = (node: unknown) => {
if (!node || typeof node !== 'object') {
return;
}
for (const [key, value] of Object.entries(
node as Record<string, unknown>,
)) {
if (reservedFields.has(key)) {
throw new SDJWTException(
`Reserved field name "${key}" is not allowed`,
);
}
visit(value);
}
};
visit(payload);
}
protected validateDisclosureFrame<T extends ExtendedPayload>(
_disclosureFrame?: DisclosureFrame<T>,
) {
return;
}
public async present<T extends Record<string, unknown>>(
encodedSDJwt: string,
presentationFrame?: PresentationFrame<T>,
options?: {
kb?: KBOptions;
},
): Promise<SDJWTCompact> {
if (!this.userConfig.hasher) {
throw new SDJWTException('Hasher not found');
}
const hasher = this.userConfig.hasher;
const sdjwt = await SDJwt.fromEncode(encodedSDJwt, hasher);
if (!sdjwt.jwt?.payload) throw new SDJWTException('Payload not found');
const presentSdJwtWithoutKb = await sdjwt.present(
presentationFrame,
hasher,
);
if (!options?.kb) {
return presentSdJwtWithoutKb;
}
const sdHashStr = await this.calculateSDHash(
presentSdJwtWithoutKb,
sdjwt,
hasher,
);
sdjwt.kbJwt = await this.createKBJwt(options.kb, sdHashStr);
return sdjwt.present(presentationFrame, hasher);
}
// This function is for verifying the SD JWT
// If requiredClaimKeys is provided, it will check if the required claim keys are presentation in the SD JWT
// If requireKeyBindings is true, it will check if the key binding JWT is presentation and verify it
public async verify(encodedSDJwt: string, options?: T & VerifierOptions) {
if (!this.userConfig.hasher) {
throw new SDJWTException('Hasher not found');
}
const hasher = this.userConfig.hasher;
const sdjwt = await SDJwt.fromEncode(encodedSDJwt, hasher);
if (!sdjwt.jwt || !sdjwt.jwt.payload) {
throw new SDJWTException('Invalid SD JWT');
}
const { payload, header } = await this.validate(encodedSDJwt, options);
if (options?.requiredClaimKeys) {
const keys = await sdjwt.keys(hasher);
const missingKeys = options.requiredClaimKeys.filter(
(k) => !keys.includes(k),
);
if (missingKeys.length > 0) {
throw new SDJWTException(
`Missing required claim keys: ${missingKeys.join(', ')}`,
);
}
}
if (!options?.keyBindingNonce) {
return { payload, header };
}
if (!sdjwt.kbJwt) {
throw new SDJWTException('Key Binding JWT not exist');
}
if (!this.userConfig.kbVerifier) {
throw new SDJWTException('Key Binding Verifier not found');
}
const kb = await sdjwt.kbJwt.verifyKB({
verifier: this.userConfig.kbVerifier,
payload,
nonce: options.keyBindingNonce,
options,
});
if (!kb) {
throw new Error('signature is not valid');
}
const sdHashfromKb = kb.payload.sd_hash;
const sdjwtWithoutKb = new SDJwt({
jwt: sdjwt.jwt,
disclosures: sdjwt.disclosures,
});
const presentSdJwtWithoutKb = sdjwtWithoutKb.encodeSDJwt();
const sdHashStr = await this.calculateSDHash(
presentSdJwtWithoutKb,
sdjwt,
hasher,
);
if (sdHashStr !== sdHashfromKb) {
throw new SDJWTException('Invalid sd_hash in Key Binding JWT');
}
return { payload, header, kb };
}
/**
* Safe verification that collects all errors instead of failing fast.
* Returns a result object with either the verified data or an array of all errors.
*
* @param encodedSDJwt - The encoded SD-JWT to verify
* @param options - Verification options
* @returns A SafeVerifyResult containing either success data or collected errors
*/
public async safeVerify(
encodedSDJwt: string,
options?: T & VerifierOptions,
): Promise<
SafeVerifyResult<{
payload: ExtendedPayload;
header: Record<string, unknown> | undefined;
kb?: {
payload: Record<string, unknown>;
header: Record<string, unknown>;
};
}>
> {
const errors: VerificationError[] = [];
// Helper to add errors
const addError = (
code: VerificationErrorCode,
message: string,
details?: unknown,
) => {
errors.push({ code, message, details });
};
// Helper to convert exception to error code
const exceptionToCode = (error: Error): VerificationErrorCode => {
const message = error.message.toLowerCase();
if (message.includes('hasher not found')) return 'HASHER_NOT_FOUND';
if (message.includes('verifier not found')) return 'VERIFIER_NOT_FOUND';
if (message.includes('invalid sd jwt') || message.includes('invalid jwt'))
return 'INVALID_SD_JWT';
if (message.includes('not yet valid')) return 'JWT_NOT_YET_VALID';
if (message.includes('expired')) return 'JWT_EXPIRED';
if (message.includes('signature')) return 'INVALID_JWT_SIGNATURE';
if (message.includes('missing required claim'))
return 'MISSING_REQUIRED_CLAIMS';
if (message.includes('key binding jwt not exist'))
return 'KEY_BINDING_JWT_MISSING';
if (message.includes('key binding verifier not found'))
return 'KEY_BINDING_VERIFIER_NOT_FOUND';
if (message.includes('sd_hash')) return 'KEY_BINDING_SD_HASH_INVALID';
return 'UNKNOWN_ERROR';
};
// Check basic configuration first
if (!this.userConfig.hasher) {
addError('HASHER_NOT_FOUND', 'Hasher not found');
}
if (!this.userConfig.verifier) {
addError('VERIFIER_NOT_FOUND', 'Verifier not found');
}
// If basic config is missing, return early
if (errors.length > 0) {
return { success: false, errors };
}
if (!this.userConfig.hasher) {
throw new SDJWTException('Hasher not found');
}
// hasher and verifier are guaranteed to be defined here
const hasher = this.userConfig.hasher;
// Try to decode and validate the SD-JWT
let sdjwt: SDJwt | undefined;
let payload: ExtendedPayload | undefined;
let header: Record<string, unknown> | undefined;
try {
sdjwt = await SDJwt.fromEncode(encodedSDJwt, hasher);
if (!sdjwt.jwt || !sdjwt.jwt.payload) {
addError('INVALID_SD_JWT', 'Invalid SD JWT: missing JWT or payload');
}
} catch (e) {
const error = ensureError(e);
addError(
'INVALID_SD_JWT',
`Failed to decode SD-JWT: ${error.message}`,
error,
);
}
// Validate signature and claims
if (sdjwt?.jwt) {
try {
const result = await this.VerifyJwt(sdjwt.jwt, options);
header = result.header;
const claims = await sdjwt.getClaims(hasher);
payload = claims as ExtendedPayload;
} catch (e) {
const error = ensureError(e);
const code = exceptionToCode(error);
addError(code, error.message, error);
}
}
// Check required claim keys
if (sdjwt && options?.requiredClaimKeys) {
try {
const keys = await sdjwt.keys(hasher);
const missingKeys = options.requiredClaimKeys.filter(
(k) => !keys.includes(k),
);
if (missingKeys.length > 0) {
addError(
'MISSING_REQUIRED_CLAIMS',
`Missing required claim keys: ${missingKeys.join(', ')}`,
{ missingKeys },
);
}
} catch (e) {
const error = ensureError(e);
addError(
'UNKNOWN_ERROR',
`Failed to check required claims: ${error.message}`,
error,
);
}
}
// Verify key binding if requested
let kb:
| { payload: Record<string, unknown>; header: Record<string, unknown> }
| undefined;
if (options?.keyBindingNonce && sdjwt) {
if (!sdjwt.kbJwt) {
addError('KEY_BINDING_JWT_MISSING', 'Key Binding JWT not exist');
} else if (!this.userConfig.kbVerifier) {
addError(
'KEY_BINDING_VERIFIER_NOT_FOUND',
'Key Binding Verifier not found',
);
} else if (payload) {
try {
const kbResult = await sdjwt.kbJwt.verifyKB({
verifier: this.userConfig.kbVerifier,
payload,
nonce: options.keyBindingNonce,
options,
});
if (!kbResult) {
addError(
'KEY_BINDING_SIGNATURE_INVALID',
'Key binding signature is not valid',
);
} else {
kb = kbResult;
// Verify sd_hash
const sdjwtWithoutKb = new SDJwt({
jwt: sdjwt.jwt,
disclosures: sdjwt.disclosures,
});
const presentSdJwtWithoutKb = sdjwtWithoutKb.encodeSDJwt();
const sdHashStr = await this.calculateSDHash(
presentSdJwtWithoutKb,
sdjwt,
hasher,
);
if (sdHashStr !== kbResult.payload.sd_hash) {
addError(
'KEY_BINDING_SD_HASH_INVALID',
'Invalid sd_hash in Key Binding JWT',
{
expected: sdHashStr,
received: kbResult.payload.sd_hash,
},
);
}
}
} catch (e) {
const error = ensureError(e);
addError(
'KEY_BINDING_SIGNATURE_INVALID',
`Key binding verification failed: ${error.message}`,
error,
);
}
}
}
// Return result
if (errors.length > 0) {
return { success: false, errors };
}
return {
success: true,
data: {
payload: payload as ExtendedPayload,
header,
kb,
},
};
}
private async calculateSDHash(
presentSdJwtWithoutKb: string,
sdjwt: SDJwt,
hasher: Hasher,
) {
if (!sdjwt.jwt || !sdjwt.jwt.payload) {
throw new SDJWTException('Invalid SD JWT');
}
const { _sd_alg } = getSDAlgAndPayload(sdjwt.jwt.payload);
const sdHash = await hasher(presentSdJwtWithoutKb, _sd_alg);
const sdHashStr = uint8ArrayToBase64Url(sdHash);
return sdHashStr;
}
/**
* This function is for validating the SD JWT
* Checking signature, if provided the iat and exp when provided and return its the claims
* @param encodedSDJwt
* @param options
* @returns
*/
public async validate(encodedSDJwt: string, options?: T & VerifierOptions) {
if (!this.userConfig.hasher) {
throw new SDJWTException('Hasher not found');
}
const hasher = this.userConfig.hasher;
const sdjwt = await SDJwt.fromEncode(encodedSDJwt, hasher);
if (!sdjwt.jwt) {
throw new SDJWTException('Invalid SD JWT');
}
const verifiedPayloads = await this.VerifyJwt(sdjwt.jwt, options);
const claims = await sdjwt.getClaims<ExtendedPayload>(hasher);
return { payload: claims, header: verifiedPayloads.header };
}
public config(newConfig: SDJWTConfig) {
this.userConfig = { ...this.userConfig, ...newConfig };
}
public encode(sdJwt: SDJwt): SDJWTCompact {
return sdJwt.encodeSDJwt();
}
public decode(endcodedSDJwt: SDJWTCompact) {
if (!this.userConfig.hasher) {
throw new SDJWTException('Hasher not found');
}
return SDJwt.fromEncode(endcodedSDJwt, this.userConfig.hasher);
}
public async keys(endcodedSDJwt: SDJWTCompact) {
if (!this.userConfig.hasher) {
throw new SDJWTException('Hasher not found');
}
const sdjwt = await SDJwt.fromEncode(endcodedSDJwt, this.userConfig.hasher);
return sdjwt.keys(this.userConfig.hasher);
}
public async presentableKeys(endcodedSDJwt: SDJWTCompact) {
if (!this.userConfig.hasher) {
throw new SDJWTException('Hasher not found');
}
const sdjwt = await SDJwt.fromEncode(endcodedSDJwt, this.userConfig.hasher);
return sdjwt.presentableKeys(this.userConfig.hasher);
}
public async getClaims(endcodedSDJwt: SDJWTCompact) {
if (!this.userConfig.hasher) {
throw new SDJWTException('Hasher not found');
}
const sdjwt = await SDJwt.fromEncode(endcodedSDJwt, this.userConfig.hasher);
return sdjwt.getClaims(this.userConfig.hasher);
}
public toFlattenJSON(endcodedSDJwt: SDJWTCompact) {
return FlattenJSON.fromEncode(endcodedSDJwt);
}
public toGeneralJSON(endcodedSDJwt: SDJWTCompact) {
return GeneralJSON.fromEncode(endcodedSDJwt);
}
}
export class SDJwtGeneralJSONInstance<ExtendedPayload extends SdJwtPayload> {
//header type
protected type?: string;
public static readonly DEFAULT_hashAlg = 'sha-256';
protected userConfig: SDJWTConfig = {};
constructor(userConfig?: SDJWTConfig) {
if (userConfig) {
if (
userConfig.hashAlg &&
!IANA_HASH_ALGORITHMS.includes(userConfig.hashAlg)
) {
throw new SDJWTException(
`Invalid hash algorithm: ${userConfig.hashAlg}`,
);
}
this.userConfig = userConfig;
}
}
private async createKBJwt(
options: KBOptions,
sdHash: string,
): Promise<KBJwt> {
if (!this.userConfig.kbSigner) {
throw new SDJWTException('Key Binding Signer not found');
}
if (!this.userConfig.kbSignAlg) {
throw new SDJWTException('Key Binding sign algorithm not specified');
}
const { payload } = options;
const kbJwt = new KBJwt({
header: {
typ: KB_JWT_TYP,
alg: this.userConfig.kbSignAlg,
},
payload: { ...payload, sd_hash: sdHash },
});
await kbJwt.sign(this.userConfig.kbSigner);
return kbJwt;
}
private encodeObj(obj: Record<string, unknown>): string {
return base64urlEncode(JSON.stringify(obj));
}
public async issue<Payload extends ExtendedPayload>(
payload: Payload,
disclosureFrame: DisclosureFrame<Payload> | undefined,
options: {
sigs: Array<{
signer: Signer;
alg: string;
kid: string;
header?: Record<string, unknown>;
}>; // multiple signers for the credential
},
): Promise<GeneralJSON> {
if (!this.userConfig.hasher) {
throw new SDJWTException('Hasher not found');
}
if (!this.userConfig.saltGenerator) {
throw new SDJWTException('SaltGenerator not found');
}
this.validateReservedFields<Payload>(payload);
this.validateDisclosureFrame<Payload>(disclosureFrame);
const hasher = this.userConfig.hasher;
const hashAlg = this.userConfig.hashAlg ?? SDJwtInstance.DEFAULT_hashAlg;
const { packedClaims, disclosures } = await pack(
payload,
disclosureFrame,
{ hasher, alg: hashAlg },
this.userConfig.saltGenerator,
);
const encodedSDJwtPayload = this.encodeObj({
...packedClaims,
_sd_alg: disclosureFrame ? hashAlg : undefined,
});
const encodedDisclosures = disclosures.map((disclosure) =>
disclosure.encode(),
);
const signatures = await Promise.all(
options.sigs.map(async (s) => {
const { signer, alg, kid, header } = s;
const protectedHeader = { typ: this.type, alg, kid, ...header };
const encodedProtectedHeader = this.encodeObj(protectedHeader);
const signature = await signer(
`${encodedProtectedHeader}.${encodedSDJwtPayload}`,
);
return {
protected: encodedProtectedHeader,
signature,
};
}),
);
const generalJson = new GeneralJSON({
payload: encodedSDJwtPayload,
disclosures: encodedDisclosures,
signatures,
});
return generalJson;
}
/**
* Validates if the payload contains any reserved claim names. If so it will throw an error.
* @param payload
* @returns
*/
protected validateReservedFields<T extends ExtendedPayload>(payload: T) {
const reservedFields = new Set([SD_DIGEST, '_sd_alg', SD_DECOY]);
const visit = (node: unknown) => {
if (!node || typeof node !== 'object') {
return;
}
for (const [key, value] of Object.entries(
node as Record<string, unknown>,
)) {
if (reservedFields.has(key)) {
throw new SDJWTException(
`Reserved field name "${key}" is not allowed`,
);
}
visit(value);
}
};
visit(payload);
}
protected validateDisclosureFrame<T extends ExtendedPayload>(
_disclosureFrame?: DisclosureFrame<T>,
) {
return;
}
public async present<T extends Record<string, unknown>>(
generalJSON: GeneralJSON,
presentationFrame?: PresentationFrame<T>,
options?: {
kb?: KBOptions;
},
): Promise<GeneralJSON> {
if (!this.userConfig.hasher) {
throw new SDJWTException('Hasher not found');
}
const hasher = this.userConfig.hasher;
const encodedSDJwt = generalJSON.toEncoded(0);
const sdjwt = await SDJwt.fromEncode(encodedSDJwt, hasher);
if (!sdjwt.jwt?.payload) throw new SDJWTException('Payload not found');
const disclosures = await sdjwt.getPresentDisclosures(
presentationFrame,
hasher,
);
const encodedDisclosures = disclosures.map((d) => d.encode());
const presentedGeneralJSON = new GeneralJSON({
payload: generalJSON.payload,
disclosures: encodedDisclosures,
signatures: generalJSON.signatures,
});
if (!options?.kb) {
return presentedGeneralJSON;
}
const presentSdJwtWithoutKb = await sdjwt.present(
presentationFrame,
hasher,
);
const sdHashStr = await this.calculateSDHash(
presentSdJwtWithoutKb,
sdjwt,
hasher,
);
const kbJwt = await this.createKBJwt(options.kb, sdHashStr);
const encodedKbJwt = kbJwt.encodeJwt();
presentedGeneralJSON.kb_jwt = encodedKbJwt;
return presentedGeneralJSON;
}
// This function is for verifying the SD JWT
// If requiredClaimKeys is provided, it will check if the required claim keys are presentation in the SD JWT
// If requireKeyBindings is true, it will check if the key binding JWT is presentation and verify it
public async verify(generalJSON: GeneralJSON, options?: VerifierOptions) {
if (!this.userConfig.hasher) {
throw new SDJWTException('Hasher not found');
}
const hasher = this.userConfig.hasher;
const { payload, headers } = await this.validate(generalJSON);
const encodedSDJwt = generalJSON.toEncoded(0);
const sdjwt = await SDJwt.fromEncode(encodedSDJwt, hasher);
if (!sdjwt.jwt || !sdjwt.jwt.payload) {
throw new SDJWTException('Invalid SD JWT');
}
if (options?.requiredClaimKeys) {
const keys = await sdjwt.keys(hasher);
const missingKeys = options?.requiredClaimKeys.filter(
(k) => !keys.includes(k),
);
if (missingKeys.length > 0) {
throw new SDJWTException(
`Missing required claim keys: ${missingKeys.join(', ')}`,
);
}
}
if (!options?.keyBindingNonce) {
return { payload, headers };
}
if (!sdjwt.kbJwt) {
throw new SDJWTException('Key Binding JWT not exist');
}
if (!this.userConfig.kbVerifier) {
throw new SDJWTException('Key Binding Verifier not found');
}
const kb = await sdjwt.kbJwt.verifyKB({
verifier: this.userConfig.kbVerifier,
payload,
nonce: options.keyBindingNonce,
options,
});
if (!kb) {
throw new Error('signature is not valid');
}
const sdHashfromKb = kb.payload.sd_hash;
const sdjwtWithoutKb = new SDJwt({
jwt: sdjwt.jwt,
disclosures: sdjwt.disclosures,
});
const presentSdJwtWithoutKb = sdjwtWithoutKb.encodeSDJwt();
const sdHashStr = await this.calculateSDHash(
presentSdJwtWithoutKb,
sdjwt,
hasher,
);
if (sdHashStr !== sdHashfromKb) {
throw new SDJWTException('Invalid sd_hash in Key Binding JWT');
}
return { payload, headers, kb };
}
private async calculateSDHash(
presentSdJwtWithoutKb: string,
sdjwt: SDJwt,
hasher: Hasher,
) {
if (!sdjwt.jwt || !sdjwt.jwt.payload) {
throw new SDJWTException('Invalid SD JWT');
}
const { _sd_alg } = getSDAlgAndPayload(sdjwt.jwt.payload);
const sdHash = await hasher(presentSdJwtWithoutKb, _sd_alg);
const sdHashStr = uint8ArrayToBase64Url(sdHash);
return sdHashStr;
}
// This function is for validating the SD JWT
// Just checking signature and return its the claims
public async validate(generalJSON: GeneralJSON) {
if (!this.userConfig.hasher) {
throw new SDJWTException('Hasher not found');
}
if (!this.userConfig.verifier) {
throw new SDJWTException('Verifier not found');
}
const hasher = this.userConfig.hasher;
const verifier = this.userConfig.verifier;
const { payload, signatures } = generalJSON;
const results = await Promise.all(
signatures.map(async (s) => {
const { protected: encodedHeader, signature } = s;
const verified = await verifier(
`${encodedHeader}.${payload}`,
signature,
);
const header = decodeBase64urlJsonStrict(encodedHeader, 'Invalid JWT');
return { verified, header };
}),
);
const verified = results.every((r) => r.verified);
if (!verified) {
throw new SDJWTException('Signature is not valid');
}
const encodedSDJwt = generalJSON.toEncoded(0);
const sdjwt = await SDJwt.fromEncode(encodedSDJwt, hasher);
if (!sdjwt.jwt) {
throw new SDJWTException('Invalid SD JWT');
}
const claims = await sdjwt.getClaims<ExtendedPayload>(hasher);
return { payload: claims, headers: results.map((r) => r.header) };
}
public config(newConfig: SDJWTConfig) {
this.userConfig = { ...this.userConfig, ...newConfig };
}
public encode(sdJwt: GeneralJSON, index: number): SDJWTCompact {
return sdJwt.toEncoded(index);
}
public decode(endcodedSDJwt: SDJWTCompact) {
return GeneralJSON.fromEncode(endcodedSDJwt);
}
public async keys(generalSdjwt: GeneralJSON) {
if (!this.userConfig.hasher) {
throw new SDJWTException('Hasher not found');
}
const endcodedSDJwt = generalSdjwt.toEncoded(0);
const sdjwt = await SDJwt.fromEncode(endcodedSDJwt, this.userConfig.hasher);
return sdjwt.keys(this.userConfig.hasher);
}
public async presentableKeys(generalSdjwt: GeneralJSON) {
if (!this.userConfig.hasher) {
throw new SDJWTException('Hasher not found');
}
const endcodedSDJwt = generalSdjwt.toEncoded(0);
const sdjwt = await SDJwt.fromEncode(endcodedSDJwt, this.userConfig.hasher);
return sdjwt.presentableKeys(this.userConfig.hasher);
}
public async getClaims(generalSdjwt: GeneralJSON) {
if (!this.userConfig.hasher) {
throw new SDJWTException('Hasher not found');
}
const endcodedSDJwt = generalSdjwt.toEncoded(0);
const sdjwt = await SDJwt.fromEncode(endcodedSDJwt, this.userConfig.hasher);
return sdjwt.getClaims(this.userConfig.hasher);
}
}