1+ // @ts -nocheck
2+ import { createJwsSigner , Ed25519Keypair } from '@docknetwork/crypto-utils' ;
3+ import { digest , generateSalt } from '@sd-jwt/crypto-nodejs' ;
4+ import { SDJwtVcInstance } from '@sd-jwt/sd-jwt-vc' ;
15import {
26 isSDJWTCredential ,
37 sdJwtToW3C ,
@@ -11,6 +15,41 @@ import {
1115// Test SD-JWT credential provided by the user
1216const TEST_SD_JWT = 'eyJ0eXAiOiJ2YytzZC1qd3QiLCJraWQiOiJkaWQ6Y2hlcWQ6dGVzdG5ldDpjMDg5MGYxYy1jN2JiLTRlYTYtYmU3YS04YzMxNDA0NzQzYjcja2V5cy0xIiwiYWxnIjoiRWREU0EifQ.eyJpYXQiOjE3NTk0MTQzOTQsImlzcyI6ImRpZDpjaGVxZDp0ZXN0bmV0OmMwODkwZjFjLWM3YmItNGVhNi1iZTdhLThjMzE0MDQ3NDNiNyNrZXlzLTEiLCJ2Y3QiOiJJbnRlcm5hbFRlc3RpbmciLCJfc2QiOlsiM3JVUGt1Mk5XckFFeTV3ZE9uVms5TkJBa0haMWE4RDB6Y2liMFNQdmthWSIsIkhjNHAxMnZTTWNMQ0piNVRYVlFrdFFMR2xjM0J3SDNWN2ltakV5ZDdvdzAiLCJNZWNNZEd6NjAxY3kwTTdvanRtSjR1LUI5LTJxSXAya1RvbFpDUm1GZ1pFIiwiZmo4WHdBb0lERmRsWmpEa0NTVzVpeXBPYUZBcVplTWZDRncwTWd3cHhOQSJdLCJfc2RfYWxnIjoic2hhLTI1NiJ9.FcYvNrldceL5BTNmoIaS4Mub8a5NcbiseUeSmmvUpOW8SUom-bchV5AEefrH1VMECbdc2whhk2sW4_jmZo7_Dw~WyI1YTRkZWY1MTAzYjRiYjc0IiwiaWQiLCJkaWQ6a2V5Ono2TWt1OVI4emRBOExENmhjRlhrbjQ3akxuZmNLWk5HbXdhVHJEbmFDQmtTYjhVbiJd~WyJiZDUyMjQ5ZjAyNjk3NWRkIiwiZGF0ZSIsIjIwMjUtMTAtMDkiXQ~WyJlMjcxNzExNzVkODdlMWE1IiwibmFtZSIsIm1heWNvbiJd~WyIwZDFiOTBlNTlhNmMyNDNiIiwibnVtYmVyIiwxMjNd~' ;
1317
18+ const ISSUER_DID = 'did:ex:issuer#keys-1' ;
19+
20+ async function issueTestSdJwt ( keypair , claims , disclosureFrame = { _sd : [ 'name' , 'date' , 'number' , 'id' ] } ) {
21+ const signer = createJwsSigner ( keypair ) ;
22+ const sdjwt = new SDJwtVcInstance ( {
23+ signer : async data => {
24+ const bytes = typeof data === 'string' ? Buffer . from ( data ) : data ;
25+ const signature = await signer . sign ( { data : bytes } ) ;
26+ return Buffer . from ( signature ) . toString ( 'base64url' ) ;
27+ } ,
28+ signAlg : 'EdDSA' ,
29+ hasher : digest ,
30+ hashAlg : 'sha-256' ,
31+ saltGenerator : generateSalt ,
32+ } ) ;
33+
34+ return sdjwt . issue ( claims , disclosureFrame , {
35+ header : {
36+ typ : 'vc+sd-jwt' ,
37+ alg : 'EdDSA' ,
38+ kid : claims . iss ,
39+ } ,
40+ } ) ;
41+ }
42+
43+ function createIssuerKeypair ( ) {
44+ return Ed25519Keypair . fromSeed (
45+ Uint8Array . from ( { length : Ed25519Keypair . SeedSize } , ( _ , index ) => index + 1 ) ,
46+ ) ;
47+ }
48+
49+ function createKeyResolver ( keypair ) {
50+ return async ( ) => keypair . publicKey ( ) ;
51+ }
52+
1453// Regular JWT (not SD-JWT) for testing
1554const REGULAR_JWT = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' ;
1655
@@ -402,32 +441,69 @@ describe('SD-JWT Service', () => {
402441 } ) ;
403442
404443 describe ( 'verifySDJWT' , ( ) => {
405- it ( 'should verify valid SD-JWT successfully' , async ( ) => {
406- const result = await verifySDJWT ( TEST_SD_JWT ) ;
444+ it ( 'should fail when no issuer key resolution options are provided' , async ( ) => {
445+ const keypair = createIssuerKeypair ( ) ;
446+ const credential = await issueTestSdJwt (
447+ keypair ,
448+ {
449+ iss : ISSUER_DID ,
450+ iat : 1700000000 ,
451+ vct : 'InternalTesting' ,
452+ name : 'maycon' ,
453+ } ,
454+ { _sd : [ 'name' ] } ,
455+ ) ;
407456
408- expect ( result ) . toHaveProperty ( 'verified' ) ;
409- expect ( result . verified ) . toBe ( true ) ;
410- expect ( result . error ) . toBeUndefined ( ) ;
457+ // Production callers must pass documentLoader/resolver/keyResolver.
458+ const result = await verifySDJWT ( credential ) ;
459+
460+ expect ( result . verified ) . toBe ( false ) ;
461+ expect ( result . error ) . toBeDefined ( ) ;
411462 } ) ;
412463
413- it ( 'should fail verification for expired SD-JWT' , async ( ) => {
414- // Create an expired SD-JWT (exp in the past)
415- const expiredJWT = 'eyJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJkaWQ6ZXhhbXBsZTppc3N1ZXIiLCJ2Y3QiOiJUZXN0Q3JlZGVudGlhbCIsImV4cCI6MTYwOTQ1OTIwMCwiaWF0IjoxNjA5NDU5MjAwfQ.signature~' ;
464+ it ( 'should cryptographically verify a valid SD-JWT with keyResolver' , async ( ) => {
465+ const keypair = createIssuerKeypair ( ) ;
466+ const credential = await issueTestSdJwt ( keypair , {
467+ iss : ISSUER_DID ,
468+ iat : 1700000000 ,
469+ exp : 1800000000 ,
470+ vct : 'InternalTesting' ,
471+ id : 'did:example:subject' ,
472+ name : 'maycon' ,
473+ date : '2025-10-09' ,
474+ number : 123 ,
475+ } ) ;
416476
417- const result = await verifySDJWT ( expiredJWT ) ;
477+ const result = await verifySDJWT ( credential , [ 'name' ] , {
478+ keyResolver : createKeyResolver ( keypair ) ,
479+ } ) ;
418480
419- expect ( result . verified ) . toBe ( false ) ;
420- expect ( result . error ) . toContain ( 'expired' ) ;
481+ expect ( result . verified ) . toBe ( true ) ;
482+ expect ( result . error ) . toBeUndefined ( ) ;
483+ expect ( result . credentialResults [ 0 ] ) . toMatchObject ( {
484+ issuer : 'did:ex:issuer' ,
485+ type : [ 'InternalTesting' ] ,
486+ credentialSubject : {
487+ name : 'maycon' ,
488+ } ,
489+ } ) ;
421490 } ) ;
422491
423- it ( 'should fail verification for not-yet-valid SD-JWT' , async ( ) => {
424- // Create an SD-JWT with nbf in the future
425- const futureJWT = 'eyJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJkaWQ6ZXhhbXBsZTppc3N1ZXIiLCJ2Y3QiOiJUZXN0Q3JlZGVudGlhbCIsIm5iZiI6OTk5OTk5OTk5OSwiaWF0IjoxNjA5NDU5MjAwfQ.signature~' ;
492+ it ( 'should fail verification when issuer key cannot be resolved' , async ( ) => {
493+ const keypair = createIssuerKeypair ( ) ;
494+ const credential = await issueTestSdJwt ( keypair , {
495+ iss : ISSUER_DID ,
496+ iat : 1700000000 ,
497+ vct : 'InternalTesting' ,
498+ name : 'maycon' ,
499+ } , { _sd : [ 'name' ] } ) ;
426500
427- const result = await verifySDJWT ( futureJWT ) ;
501+ const result = await verifySDJWT ( credential , undefined , {
502+ keyResolver : async ( ) => null ,
503+ } ) ;
428504
429505 expect ( result . verified ) . toBe ( false ) ;
430- expect ( result . error ) . toContain ( 'not yet valid' ) ;
506+ expect ( result . error ) . toBeDefined ( ) ;
431507 } ) ;
432508
433509 it ( 'should fail verification for invalid SD-JWT format' , async ( ) => {
@@ -437,11 +513,24 @@ describe('SD-JWT Service', () => {
437513 expect ( result . error ) . toBeDefined ( ) ;
438514 } ) ;
439515
440- it ( 'should verify SD-JWT without expiration date' , async ( ) => {
441- // The TEST_SD_JWT doesn't have an expiration date, so it should verify
442- const result = await verifySDJWT ( TEST_SD_JWT ) ;
516+ it ( 'should fail verification with wrong issuer key' , async ( ) => {
517+ const issuerKeypair = createIssuerKeypair ( ) ;
518+ const wrongKeypair = Ed25519Keypair . fromSeed (
519+ Uint8Array . from ( { length : Ed25519Keypair . SeedSize } , ( _ , index ) => index + 9 ) ,
520+ ) ;
521+ const credential = await issueTestSdJwt ( issuerKeypair , {
522+ iss : ISSUER_DID ,
523+ iat : 1700000000 ,
524+ vct : 'InternalTesting' ,
525+ name : 'maycon' ,
526+ } , { _sd : [ 'name' ] } ) ;
527+
528+ const result = await verifySDJWT ( credential , undefined , {
529+ keyResolver : createKeyResolver ( wrongKeypair ) ,
530+ } ) ;
443531
444- expect ( result . verified ) . toBe ( true ) ;
532+ expect ( result . verified ) . toBe ( false ) ;
533+ expect ( result . error ) . toBeDefined ( ) ;
445534 } ) ;
446535
447536 it ( 'should return error message on verification failure' , async ( ) => {
@@ -602,25 +691,44 @@ describe('SD-JWT Service', () => {
602691 } ) ;
603692
604693 it ( 'should preserve presentation format for verification flow' , async ( ) => {
694+ const keypair = createIssuerKeypair ( ) ;
695+ const credential = await issueTestSdJwt ( keypair , {
696+ iss : ISSUER_DID ,
697+ iat : 1700000000 ,
698+ vct : 'InternalTesting' ,
699+ name : 'maycon' ,
700+ date : '2025-10-09' ,
701+ } , { _sd : [ 'name' , 'date' ] } ) ;
702+
605703 const presentation = await createSDJWTPresentation ( {
606704 attributesToReveal : [ 'name' , 'date' ] ,
607- credential : TEST_SD_JWT ,
705+ credential,
608706 } ) ;
609707
610- // The presentation should be verifiable
611- const verificationResult = await verifySDJWT ( presentation ) ;
708+ const verificationResult = await verifySDJWT ( presentation , undefined , {
709+ keyResolver : createKeyResolver ( keypair ) ,
710+ } ) ;
612711 expect ( verificationResult . verified ) . toBe ( true ) ;
613712 } ) ;
614713 } ) ;
615714
616715 describe ( 'Presentation Integration Tests' , ( ) => {
617716 it ( 'should complete full flow: store credential -> create presentation -> verify' , async ( ) => {
717+ const keypair = createIssuerKeypair ( ) ;
718+ const issued = await issueTestSdJwt ( keypair , {
719+ iss : ISSUER_DID ,
720+ iat : 1700000000 ,
721+ vct : 'InternalTesting' ,
722+ name : 'maycon' ,
723+ date : '2025-10-09' ,
724+ } , { _sd : [ 'name' , 'date' ] } ) ;
725+
618726 // Step 1: Convert SD-JWT to W3C format (as done in addCredential)
619- const w3cCredential = await decodeSDJWTToW3C ( TEST_SD_JWT ) as any ;
727+ const w3cCredential = await decodeSDJWTToW3C ( issued ) as any ;
620728
621729 // Verify metadata is stored
622730 expect ( w3cCredential . _sd_jwt ) . toBeDefined ( ) ;
623- expect ( w3cCredential . _sd_jwt . encoded ) . toBe ( TEST_SD_JWT ) ;
731+ expect ( w3cCredential . _sd_jwt . encoded ) . toBe ( issued ) ;
624732
625733 // Step 2: Create presentation from stored credential (as done in verification-controller)
626734 const presentation = await createSDJWTPresentation ( {
@@ -630,8 +738,10 @@ describe('SD-JWT Service', () => {
630738
631739 expect ( typeof presentation ) . toBe ( 'string' ) ;
632740
633- // Step 3: Verify presentation
634- const verificationResult = await verifySDJWT ( presentation ) ;
741+ // Step 3: Verify presentation cryptographically
742+ const verificationResult = await verifySDJWT ( presentation , undefined , {
743+ keyResolver : createKeyResolver ( keypair ) ,
744+ } ) ;
635745 expect ( verificationResult . verified ) . toBe ( true ) ;
636746
637747 // Step 4: Decode presentation to check disclosed claims
@@ -643,16 +753,26 @@ describe('SD-JWT Service', () => {
643753 } ) ;
644754
645755 it ( 'should support multiple presentation creations from same credential' , async ( ) => {
756+ const keypair = createIssuerKeypair ( ) ;
757+ const credential = await issueTestSdJwt ( keypair , {
758+ iss : ISSUER_DID ,
759+ iat : 1700000000 ,
760+ vct : 'InternalTesting' ,
761+ name : 'maycon' ,
762+ date : '2025-10-09' ,
763+ number : 123 ,
764+ } , { _sd : [ 'name' , 'date' , 'number' ] } ) ;
765+
646766 // Create first presentation with some attributes
647767 const presentation1 = await createSDJWTPresentation ( {
648768 attributesToReveal : [ 'name' ] ,
649- credential : TEST_SD_JWT ,
769+ credential,
650770 } ) ;
651771
652772 // Create second presentation with different attributes
653773 const presentation2 = await createSDJWTPresentation ( {
654774 attributesToReveal : [ 'date' , 'number' ] ,
655- credential : TEST_SD_JWT ,
775+ credential,
656776 } ) ;
657777
658778 // Both should be valid
@@ -662,9 +782,10 @@ describe('SD-JWT Service', () => {
662782 // They should be different (different disclosures)
663783 expect ( presentation1 ) . not . toBe ( presentation2 ) ;
664784
665- // Both should verify
666- const result1 = await verifySDJWT ( presentation1 ) ;
667- const result2 = await verifySDJWT ( presentation2 ) ;
785+ // Both should verify cryptographically
786+ const keyResolver = createKeyResolver ( keypair ) ;
787+ const result1 = await verifySDJWT ( presentation1 , undefined , { keyResolver} ) ;
788+ const result2 = await verifySDJWT ( presentation2 , undefined , { keyResolver} ) ;
668789
669790 expect ( result1 . verified ) . toBe ( true ) ;
670791 expect ( result2 . verified ) . toBe ( true ) ;
0 commit comments