UNPKG

@sd-jwt/core

Version:

sd-jwt draft 7 implementation in typescript

946 lines (826 loc) 27.7 kB
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); } }