From 40a8973f3bfef4447be93bec5974bfe9a9cd179e Mon Sep 17 00:00:00 2001 From: moiz-sgtradex Date: Mon, 6 Oct 2025 15:29:21 +0530 Subject: [PATCH 1/5] feat: update sign and derive for bbs2023 --- README.md | 27 +++-- package-lock.json | 73 ++++++++----- package.json | 10 +- src/__tests__/core/documentBuilder.test.ts | 116 +++------------------ src/__tests__/fixtures/fixtures.ts | 66 ++++++++++++ src/__tests__/w3c/derive.test.ts | 34 +++++- src/__tests__/w3c/sign.test.ts | 61 ++++++++++- src/core/documentBuilder.ts | 3 +- src/w3c/derive.ts | 11 +- src/w3c/sign.ts | 4 +- 10 files changed, 247 insertions(+), 158 deletions(-) diff --git a/README.md b/README.md index 9f47e41..bb59bf1 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,7 @@ const signedWrappedDocument = await signOA(wrappedDocument, { #### b) TrustVC W3C Signing (signW3C) -The `signW3C` function signs W3C Verifiable Credentials using the provided cryptographic suite and key pair. By default, it uses the **ecdsa-sd-2023** crypto suite unless otherwise specified. +The `signW3C` function signs W3C Verifiable Credentials using the provided cryptographic suite and key pair. By default, it uses the **ecdsa-sd-2023** crypto suite unless otherwise specified. It also supports **bbs-2023** for modern BBS signatures. ```ts import { signW3C, VerificationType } from '@trustvc/trustvc'; @@ -195,7 +195,7 @@ const signingResult = await signW3C(rawDocument, { secretKeyMultibase: '' }); -// You can also specify mandatory pointers for selective disclosure with ecdsa-sd-2023 +// You can also specify mandatory pointers for selective disclosure with ecdsa-sd-2023 / bbs-2023 const signingResultWithPointers = await signW3C( rawDocument, { @@ -212,7 +212,22 @@ const signingResultWithPointers = await signW3C( } ); -// Alternatively, specify a different crypto suite. Ensure the context is updated to include the crypto suite. +// Using BBS-2023 cryptosuite +const signingResultWithBbs2023 = await signW3C( + rawDocument, + { + '@context': 'https://w3id.org/security/multikey/v1', + id: 'did:web:trustvc.github.io:did:1#multikey-2', + type: VerificationType.Multikey, + controller: 'did:web:trustvc.github.io:did:1', + publicKeyMultibase: 'zUC75kRac7BdtjawFUxowfgD6mzqnRHFxAfMDaBynebdYgakviQkPS1KNJEw7uGWqj91H3hSE4pTERb3EZKLgKXjpqHWrN8dyE8SKyPBE3k7kUGjBNAqJoNGgUzqUW3DSaWrcNr', + secretKeyMultibase: '', + }, + 'bbs-2023' +); + +// ⚠️ DEPRECATED: BbsBlsSignature2020 is no longer supported +// Use 'ecdsa-sd-20203 or bbs-2023' cryptosuite instead as shown above const signingResultWithBbs = await signW3C( rawDocument, { @@ -222,7 +237,7 @@ const signingResultWithBbs = await signW3C( publicKeyBase58: 'oRfEeWFresvhRtXCkihZbxyoi2JER7gHTJ5psXhHsdCoU1MttRMi3Yp9b9fpjmKh7bMgfWKLESiK2YovRd8KGzJsGuamoAXfqDDVhckxuc9nmsJ84skCSTijKeU4pfAcxeJ', privateKeyBase58: '', }, - 'BbsBlsSignature2020' + 'BbsBlsSignature2020' // This will return an error ); ``` @@ -231,7 +246,7 @@ const signingResultWithBbs = await signW3C( ### 3. **Deriving (Selective Disclosure)** -> When using ECDSA-SD-2023 crypto suite, we can derive a new credential with selective disclosure. This means you can choose which parts of the credential to reveal while keeping others hidden. +> When using ECDSA-SD-2023 or BBS-2023 crypto suites, we can derive a new credential with selective disclosure. This means you can choose which parts of the credential to reveal while keeping others hidden. ```ts import { deriveW3C } from '@trustvc/trustvc'; @@ -282,7 +297,7 @@ const derivationResult = await deriveW3C(signedDocument, { ### 4. **Verifying** -> TrustVC simplifies the verification process with a single function that supports both W3C Verifiable Credentials (VCs) and OpenAttestation Verifiable Documents (VDs). Whether you're working with W3C standards or OpenAttestation standards, TrustVC handles the verification seamlessly. For ECDSA-signed documents, which normally require derivation before verification, TrustVC automatically handles this process internally - if a document is not derived, the `verifyDocument` function will automatically derive and verify the document in a single step. +> TrustVC simplifies the verification process with a single function that supports both W3C Verifiable Credentials (VCs) and OpenAttestation Verifiable Documents (VDs). Whether you're working with W3C standards or OpenAttestation standards, TrustVC handles the verification seamlessly. For ECDSA-SD-2023 and BBS-2023 signed documents, which normally require derivation before verification, TrustVC automatically handles this process internally - if a document is not derived, the `verifyDocument` function will automatically derive and verify the document in a single step. ```ts import { verifyDocument } from '@trustvc/trustvc'; diff --git a/package-lock.json b/package-lock.json index cd899f7..2d0e068 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,11 +16,11 @@ "@tradetrust-tt/tradetrust": "^6.10.2", "@tradetrust-tt/tradetrust-utils": "^2.4.2", "@tradetrust-tt/tt-verify": "^9.6.0", - "@trustvc/w3c": "^1.3.0-alpha.11", - "@trustvc/w3c-context": "^1.3.0-alpha.10", - "@trustvc/w3c-credential-status": "^1.3.0-alpha.10", - "@trustvc/w3c-issuer": "^1.3.0-alpha.8", - "@trustvc/w3c-vc": "^1.3.0-alpha.11", + "@trustvc/w3c": "^1.3.0-alpha.14", + "@trustvc/w3c-context": "^1.3.0-alpha.12", + "@trustvc/w3c-credential-status": "^1.3.0-alpha.12", + "@trustvc/w3c-issuer": "^1.3.0-alpha.10", + "@trustvc/w3c-vc": "^1.3.0-alpha.14", "ethers": "^5.8.0", "ethersV6": "npm:ethers@^6.14.4", "js-sha3": "^0.9.3", @@ -1139,6 +1139,21 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@digitalbazaar/bbs-2023-cryptosuite": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@digitalbazaar/bbs-2023-cryptosuite/-/bbs-2023-cryptosuite-2.0.1.tgz", + "integrity": "sha512-Uw7aDSuCehLUsiSsTi2ob1hQ8AgVq+jV3OgvPsOzy/AmIh2yG9kg2tNCv6PngWPyOm3VCzolwh+5MzWySASiww==", + "license": "BSD-3-Clause", + "dependencies": { + "@digitalbazaar/bls12-381-multikey": "^2.0.0", + "@digitalbazaar/di-sd-primitives": "^3.0.4", + "base64url-universal": "^2.0.0", + "cborg": "^4.0.5" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@digitalbazaar/bbs-signatures": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@digitalbazaar/bbs-signatures/-/bbs-signatures-3.0.0.tgz", @@ -6115,24 +6130,24 @@ } }, "node_modules/@trustvc/w3c": { - "version": "1.3.0-alpha.11", - "resolved": "https://registry.npmjs.org/@trustvc/w3c/-/w3c-1.3.0-alpha.11.tgz", - "integrity": "sha512-7/mUqO4l31Jqp13Zck56BHDl9wSoFhfOmzD+m/DoSKMh9SlFu0tiNGf6Ip4MUqG2ltgDv5pEE7rO0WtSAXCzLw==", + "version": "1.3.0-alpha.14", + "resolved": "https://registry.npmjs.org/@trustvc/w3c/-/w3c-1.3.0-alpha.14.tgz", + "integrity": "sha512-Mxqr4tytHF1j6KuPpON469CaGNYxGtgxQjYOfjncdbx19U+7yTDFIpSxuGUu8F7257C2cOZ/qbH/25kPxeMbjg==", "license": "Apache-2.0", "dependencies": { - "@trustvc/w3c-context": "^1.3.0-alpha.10", - "@trustvc/w3c-credential-status": "^1.3.0-alpha.10", - "@trustvc/w3c-issuer": "^1.3.0-alpha.8", - "@trustvc/w3c-vc": "^1.3.0-alpha.11" + "@trustvc/w3c-context": "^1.3.0-alpha.12", + "@trustvc/w3c-credential-status": "^1.3.0-alpha.12", + "@trustvc/w3c-issuer": "^1.3.0-alpha.10", + "@trustvc/w3c-vc": "^1.3.0-alpha.14" }, "engines": { "node": ">=18.x" } }, "node_modules/@trustvc/w3c-context": { - "version": "1.3.0-alpha.10", - "resolved": "https://registry.npmjs.org/@trustvc/w3c-context/-/w3c-context-1.3.0-alpha.10.tgz", - "integrity": "sha512-Fdc3XSpTok3mQXrV7Zf3Z1+miWBJHEhfIaAFNIEv8eV8xT60SExn8JgRQn+yRV2r1k4oE80lOXNwixd5w9eScA==", + "version": "1.3.0-alpha.12", + "resolved": "https://registry.npmjs.org/@trustvc/w3c-context/-/w3c-context-1.3.0-alpha.12.tgz", + "integrity": "sha512-NO6j3EJtzfHos3vJPDsvAryGGK+g6+qK/Ur5MiEjPX4ox7C/JBGI9snEQBHj8fo5u+eig/aT/eOKYzeTAHAVDQ==", "license": "Apache-2.0", "dependencies": { "did-resolver": "^4.1.0", @@ -6218,13 +6233,13 @@ } }, "node_modules/@trustvc/w3c-credential-status": { - "version": "1.3.0-alpha.10", - "resolved": "https://registry.npmjs.org/@trustvc/w3c-credential-status/-/w3c-credential-status-1.3.0-alpha.10.tgz", - "integrity": "sha512-9aIDJXbzzL0IvxbKaAqtIa3LF4mBNweaHtFjAL1wRfh95aQMZ7VPTauWfSHLLtEqKTEFdTjCv6zGOqV+aCRMeg==", + "version": "1.3.0-alpha.12", + "resolved": "https://registry.npmjs.org/@trustvc/w3c-credential-status/-/w3c-credential-status-1.3.0-alpha.12.tgz", + "integrity": "sha512-nTBshHbqoLMlz0AxFJKIJh4JAqoUf1lrnVQArdFf0sbc6TT3aCKEVCc45zcsQ04eFLxQOcLXWAKr/oCD51Ivtg==", "license": "Apache-2.0", "dependencies": { - "@trustvc/w3c-context": "^1.3.0-alpha.10", - "@trustvc/w3c-issuer": "^1.3.0-alpha.8", + "@trustvc/w3c-context": "^1.3.0-alpha.12", + "@trustvc/w3c-issuer": "^1.3.0-alpha.10", "base64url-universal": "^2.0.0", "pako": "^2.1.0" }, @@ -6233,9 +6248,9 @@ } }, "node_modules/@trustvc/w3c-issuer": { - "version": "1.3.0-alpha.8", - "resolved": "https://registry.npmjs.org/@trustvc/w3c-issuer/-/w3c-issuer-1.3.0-alpha.8.tgz", - "integrity": "sha512-qx8/xvae09irrYUbaXcwGQevu0igkBxYtPXesmJoJLq6YL/CK+m6b+rkz3ZU2ISbvy1BQuaKYyiqXo+BarRIxA==", + "version": "1.3.0-alpha.10", + "resolved": "https://registry.npmjs.org/@trustvc/w3c-issuer/-/w3c-issuer-1.3.0-alpha.10.tgz", + "integrity": "sha512-ZkRyYf0h1hwgLBkXuC0dDgLx8DzWGTbHRP0nTWEu+I57T/51nr7GAcUgtxefe0pfaE83+18BrwbhBtb0xGjU4w==", "license": "Apache-2.0", "dependencies": { "@digitalbazaar/bls12-381-multikey": "^2.1.0", @@ -6257,17 +6272,19 @@ "license": "Apache-2.0" }, "node_modules/@trustvc/w3c-vc": { - "version": "1.3.0-alpha.11", - "resolved": "https://registry.npmjs.org/@trustvc/w3c-vc/-/w3c-vc-1.3.0-alpha.11.tgz", - "integrity": "sha512-y1Xcb3zwOdAGS0p3SEtjQe5b/tqDM8rHmgEcKYb0Af/ARh32GobWK6CC5Gtb/DWoesbHA0wgbLtI/vVW7WURbA==", + "version": "1.3.0-alpha.14", + "resolved": "https://registry.npmjs.org/@trustvc/w3c-vc/-/w3c-vc-1.3.0-alpha.14.tgz", + "integrity": "sha512-fA8a65IEBmqPi1IsGEOsBe6gXquhZmeU8ab+IM1Y7lub3FXwOm3ObkZdq3G7VjyxF7XKRyLU1yK6qxHO81CJHw==", "license": "Apache-2.0", "dependencies": { + "@digitalbazaar/bbs-2023-cryptosuite": "^2.0.1", + "@digitalbazaar/bls12-381-multikey": "^2.1.0", "@digitalbazaar/data-integrity": "^2.5.0", "@digitalbazaar/ecdsa-multikey": "^1.8.0", "@digitalbazaar/ecdsa-sd-2023-cryptosuite": "^3.4.1", "@mattrglobal/jsonld-signatures-bbs": "^1.2.0", - "@trustvc/w3c-credential-status": "^1.3.0-alpha.10", - "@trustvc/w3c-issuer": "^1.3.0-alpha.8", + "@trustvc/w3c-credential-status": "^1.3.0-alpha.12", + "@trustvc/w3c-issuer": "^1.3.0-alpha.10", "base64url-universal": "^2.0.0", "cbor": "^9.0.2", "did-resolver": "^4.1.0", diff --git a/package.json b/package.json index b60e6b2..8f574ad 100644 --- a/package.json +++ b/package.json @@ -122,11 +122,11 @@ "@tradetrust-tt/tradetrust": "^6.10.2", "@tradetrust-tt/tradetrust-utils": "^2.4.2", "@tradetrust-tt/tt-verify": "^9.6.0", - "@trustvc/w3c": "^1.3.0-alpha.11", - "@trustvc/w3c-context": "^1.3.0-alpha.10", - "@trustvc/w3c-credential-status": "^1.3.0-alpha.10", - "@trustvc/w3c-issuer": "^1.3.0-alpha.8", - "@trustvc/w3c-vc": "^1.3.0-alpha.11", + "@trustvc/w3c": "^1.3.0-alpha.14", + "@trustvc/w3c-context": "^1.3.0-alpha.12", + "@trustvc/w3c-credential-status": "^1.3.0-alpha.12", + "@trustvc/w3c-issuer": "^1.3.0-alpha.10", + "@trustvc/w3c-vc": "^1.3.0-alpha.14", "ethers": "^5.8.0", "ethersV6": "npm:ethers@^6.14.4", "js-sha3": "^0.9.3", diff --git a/src/__tests__/core/documentBuilder.test.ts b/src/__tests__/core/documentBuilder.test.ts index 05ff022..c7db363 100644 --- a/src/__tests__/core/documentBuilder.test.ts +++ b/src/__tests__/core/documentBuilder.test.ts @@ -1,7 +1,6 @@ import { describe, it, expect, beforeEach } from 'vitest'; import { DocumentBuilder } from '../../core/documentBuilder'; import { PrivateKeyPair, VerificationType } from '@trustvc/w3c-issuer'; -import { ContextDocument } from '@trustvc/w3c-context'; const BBS2020testPrivateKey: PrivateKeyPair = { id: 'did:web:trustvc.github.io:did:1#keys-1', @@ -25,7 +24,9 @@ describe('DocumentBuilder data model 2.0 using ECDSA', () => { let documentBuilder: DocumentBuilder; beforeEach(() => { - documentBuilder = new DocumentBuilder({}).credentialSubject({}); + documentBuilder = new DocumentBuilder({ + '@context': 'https://trustvc.io/context/bill-of-lading.json', + }).credentialSubject({ type: ['BillOfLading'] }); }); describe('Initialization', () => { @@ -35,6 +36,7 @@ describe('DocumentBuilder data model 2.0 using ECDSA', () => { '@context': expect.arrayContaining([ 'https://www.w3.org/ns/credentials/v2', 'https://w3id.org/security/data-integrity/v2', + 'https://trustvc.io/context/bill-of-lading.json', ]), type: expect.arrayContaining(['VerifiableCredential']), }); @@ -45,9 +47,12 @@ describe('DocumentBuilder data model 2.0 using ECDSA', () => { it('should return the current state of the document as a JSON string', () => { const expectedJson = JSON.stringify( { - '@context': ['https://www.w3.org/ns/credentials/v2'], + '@context': [ + 'https://www.w3.org/ns/credentials/v2', + 'https://trustvc.io/context/bill-of-lading.json', + ], type: ['VerifiableCredential'], - credentialSubject: {}, + credentialSubject: { type: ['BillOfLading'] }, }, null, 2, @@ -275,102 +280,11 @@ describe('DocumentBuilder Data model 2.0 using BBS2020', () => { documentBuilder = new DocumentBuilder({}).credentialSubject({}); }); - describe('Initialization', () => { - it('should initialize with default context and type', async () => { - const signedDocument = await documentBuilder.sign( - BBS2020testPrivateKey, - 'BbsBlsSignature2020', - ); - expect(signedDocument).toMatchObject({ - '@context': expect.arrayContaining([ - 'https://www.w3.org/ns/credentials/v2', - 'https://w3id.org/security/bbs/v1', - ]), - type: expect.arrayContaining(['VerifiableCredential']), - }); - }); - }); - - describe('Validation Errors', () => { - it('should throw an error when required fields are missing', async () => { - await expect( - new DocumentBuilder({}).sign(BBS2020testPrivateKey, 'BbsBlsSignature2020'), - ).rejects.toThrow( - 'Validation Error: Missing required field "credentialSubject" in the credential.', - ); - }); - - it('should throw an error when document is already signed', async () => { - await documentBuilder.sign(BBS2020testPrivateKey, 'BbsBlsSignature2020'); - expect(() => - documentBuilder.credentialStatus({ - chain: 'amoy', - chainId: 80002, - tokenRegistry: '0x71D28767662cB233F887aD2Bb65d048d760bA694', - rpcProviderUrl: 'https://rpc-amoy.polygon.technology', - }), - ).toThrow('Configuration Error: Document is already signed.'); - }); - }); - - describe('Signing and Verification', () => { - it('should sign and verify the document successfully for transferableRecords', async () => { - documentBuilder.credentialStatus({ - chain: 'amoy', - chainId: 80002, - tokenRegistry: '0x71D28767662cB233F887aD2Bb65d048d760bA694', - rpcProviderUrl: 'https://rpc-amoy.polygon.technology', - }); - const signedDocument = await documentBuilder.sign( - BBS2020testPrivateKey, - 'BbsBlsSignature2020', - ); - expect(signedDocument).toBeDefined(); - const verificationResult = await documentBuilder.verify(); - expect(verificationResult).toBe(true); - }); - - it('should sign and verify the document successfully for verifiableDocument', async () => { - documentBuilder.credentialStatus({ - url: 'https://trustvc.github.io/did/credentials/statuslist/1', - index: 10, // Not revoked - }); - const signedDocument = await documentBuilder.sign( - BBS2020testPrivateKey, - 'BbsBlsSignature2020', - ); - expect(signedDocument).toBeDefined(); - const verificationResult = await documentBuilder.verify(); - expect(verificationResult).toBe(true); - }); - - it('should not verify the document if it is not signed yet', async () => { - await expect(documentBuilder.verify()).rejects.toThrow( - 'Verification Error: Document is not signed yet.', - ); - }); - - it('should be able to derive document and verify derived document', async () => { - const contextDocument: ContextDocument = { - '@context': [ - 'https://www.w3.org/ns/credentials/v2', - 'https://w3id.org/security/bbs/v1', - 'https://trustvc.io/context/transferable-records-context.json', - ], - type: ['VerifiableCredential'], - }; - - documentBuilder.credentialStatus({ - chain: 'amoy', - chainId: 80002, - tokenRegistry: '0x71D28767662cB233F887aD2Bb65d048d760bA694', - rpcProviderUrl: 'https://rpc-amoy.polygon.technology', - }); - await documentBuilder.sign(BBS2020testPrivateKey, 'BbsBlsSignature2020'); - const derivedDocument = await documentBuilder.derive(contextDocument); - expect(derivedDocument).toBeDefined(); - const verificationResult = await documentBuilder.verify(); - expect(verificationResult).toBe(true); - }); + it('should throw error when trying to sign with BbsBlsSignature2020', async () => { + await expect( + documentBuilder.sign(BBS2020testPrivateKey, 'BbsBlsSignature2020'), + ).rejects.toThrow( + 'BbsBlsSignature2020 is no longer supported. Please use the latest cryptosuite versions instead', + ); }); }); diff --git a/src/__tests__/fixtures/fixtures.ts b/src/__tests__/fixtures/fixtures.ts index e8df128..f4b7abd 100644 --- a/src/__tests__/fixtures/fixtures.ts +++ b/src/__tests__/fixtures/fixtures.ts @@ -1612,6 +1612,72 @@ export const ECDSA_W3C_VERIFIABLE_DOCUMENT_V1_1 = freezeObject({ }, }); +export const BBS2023_W3C_VERIFIABLE_DOCUMENT_V2_0 = freezeObject({ + '@context': [ + 'https://www.w3.org/ns/credentials/v2', + 'https://w3id.org/security/data-integrity/v2', + 'https://trustvc.io/context/transferable-records-context.json', + 'https://trustvc.io/context/attachments-context.json', + 'https://trustvc.io/context/qrcode-context.json', + 'https://trustvc.io/context/bill-of-lading.json', + 'https://trustvc.io/context/render-method-context-v2.json', + ], + qrCode: { type: 'TrustVCQRCode', uri: 'https://localhost:3000/qrcode' }, + credentialStatus: { + type: 'TransferableRecords', + tokenNetwork: { chain: 'MATIC', chainId: '80001' }, + tokenRegistry: '0xE0a94770B8e969B5D9179d6dA8730B01e19279e2', + tokenId: 'c9f954f5cd10aa6eb4d02a9047ed45d70205e0206425cdd7238d45b3a2529491', + }, + credentialSubject: { + billOfLadingName: 'TrustVC Bill of Lading', + scac: 'SGPU', + blNumber: 'SGCNM21566325', + vessel: 'vessel', + voyageNo: 'voyageNo', + portOfLoading: 'Singapore', + portOfDischarge: 'Paris', + carrierName: 'A.P. Moller', + placeOfReceipt: 'Beijing', + placeOfDelivery: 'Singapore', + packages: [ + { packagesDescription: 'package 1', packagesWeight: '10', packagesMeasurement: '20' }, + { packagesDescription: 'package 2', packagesWeight: '10', packagesMeasurement: '20' }, + ], + shipperName: 'Shipper Name', + shipperAddressStreet: '101 ORCHARD ROAD', + shipperAddressCountry: 'SINGAPORE', + consigneeName: 'Consignee name', + notifyPartyName: 'Notify Party Name', + links: 'https://localhost:3000/url', + attachments: [ + { data: 'BASE64_ENCODED_FILE', filename: 'sample1.pdf', mimeType: 'application/pdf' }, + { data: 'BASE64_ENCODED_FILE', filename: 'sample2.pdf', mimeType: 'application/pdf' }, + ], + type: ['BillOfLading'], + }, + renderMethod: [ + { + id: 'https://localhost:3000/renderer', + type: 'EMBEDDED_RENDERER', + templateName: 'BILL_OF_LADING', + }, + ], + validUntil: '2029-12-03T12:19:52Z', + issuer: 'did:web:trustvc.github.io:did:1', + type: ['VerifiableCredential'], + validFrom: '2024-04-01T12:19:52Z', + id: 'urn:uuid:01999f97-53b3-7337-b852-24fc22e6dab7', + proof: { + type: 'DataIntegrityProof', + verificationMethod: 'did:web:trustvc.github.io:did:1#multikey-2', + cryptosuite: 'bbs-2023', + proofPurpose: 'assertionMethod', + proofValue: + 'u2V0ChVhQkxEIPZQRcCYa_DVdzn94xbcFe1OjSOH2CbwFV3O2fmyzppXKGt1iZg87FnNFKUKpL9591HkwCmKKuQwRkdnexNHPfsTju0WYkOjhuRQNVNNYQD9WZttHrlLy5Yt4KX5JlbD4AqxyPhcyoKk-Wo6FkAtp78zG_THkeDFZZiI-UUYKjRf-169_cRJcvK5pKH1lTFFYYI39K3Wi-5P1hP7ChGf2V9wmQ-egtRazAMNEt_X4g4iitVcZ-PE0kQ-Xsh-ea5CEmQ3V8Yco8ZXCQvfZyu-lahTHpzH70s6N2ZB70Mn52xzHM7_BM1n1ywQ0k4E27OiBN1ggXL67s8-x7caeAOr0kGYLCkwYNN4cyVPSfp7csMhs0DWDZy9pc3N1ZXJqL3ZhbGlkRnJvbXEvY3JlZGVudGlhbFN0YXR1cw', + }, +}); + // Freeze fixture to prevent accidental changes during tests function freezeObject(obj: T): T { return deepFreeze(obj) as T; diff --git a/src/__tests__/w3c/derive.test.ts b/src/__tests__/w3c/derive.test.ts index 2020672..c58d422 100644 --- a/src/__tests__/w3c/derive.test.ts +++ b/src/__tests__/w3c/derive.test.ts @@ -1,9 +1,13 @@ import { describe, expect, it } from 'vitest'; -import { ECDSA_W3C_VERIFIABLE_DOCUMENT_V2_0 } from '../fixtures/fixtures'; +import { + ECDSA_W3C_VERIFIABLE_DOCUMENT_V2_0, + BBS2023_W3C_VERIFIABLE_DOCUMENT_V2_0, +} from '../fixtures/fixtures'; import { deriveW3C } from 'src/w3c'; import { SignedVerifiableCredential } from '@trustvc/w3c-vc'; describe('W3C derive', () => { + // credentialStatus is defined since the document has been signed with credentailStatus as mandatory parameter it('should derive a W3C v2.0 document using ECDSA-SD-2023 without custom selective pointers', async () => { const result = await deriveW3C( ECDSA_W3C_VERIFIABLE_DOCUMENT_V2_0 as SignedVerifiableCredential, @@ -17,7 +21,7 @@ describe('W3C derive', () => { expect(result.derived.qrCode).toBeUndefined(); }); - it('should derive a W3C v2.0 document using ECDSA-SD-2023 without custom selective pointers', async () => { + it('should derive a W3C v2.0 document using ECDSA-SD-2023 with custom selective pointers', async () => { const result = await deriveW3C( ECDSA_W3C_VERIFIABLE_DOCUMENT_V2_0 as SignedVerifiableCredential, ['/renderMethod', '/qrCode'], @@ -29,4 +33,30 @@ describe('W3C derive', () => { expect(result.derived.renderMethod).toBeDefined(); expect(result.derived.qrCode).toBeDefined(); }); + + it('should derive a W3C v2.0 document using BBS-2023 without custom selective pointers', async () => { + const result = await deriveW3C( + BBS2023_W3C_VERIFIABLE_DOCUMENT_V2_0 as SignedVerifiableCredential, + [], + ); + expect(result.derived).toBeDefined(); + expect(result.derived.proof).toBeDefined(); + expect(result.derived['@context']).toBeDefined(); + expect(result.derived.credentialStatus).toBeDefined(); + expect(result.derived.renderMethod).toBeUndefined(); + expect(result.derived.qrCode).toBeUndefined(); + }); + + it('should derive a W3C v2.0 document using BBS-2023 with custom selective pointers', async () => { + const result = await deriveW3C( + BBS2023_W3C_VERIFIABLE_DOCUMENT_V2_0 as SignedVerifiableCredential, + ['/renderMethod', '/qrCode'], + ); + expect(result.derived).toBeDefined(); + expect(result.derived.proof).toBeDefined(); + expect(result.derived['@context']).toBeDefined(); + expect(result.derived.credentialStatus).toBeDefined(); + expect(result.derived.renderMethod).toBeDefined(); + expect(result.derived.qrCode).toBeDefined(); + }); }); diff --git a/src/__tests__/w3c/sign.test.ts b/src/__tests__/w3c/sign.test.ts index 5260f17..b38c27a 100644 --- a/src/__tests__/w3c/sign.test.ts +++ b/src/__tests__/w3c/sign.test.ts @@ -1,5 +1,9 @@ import { describe, expect, it } from 'vitest'; -import { ECDSA_W3C_VERIFIABLE_DOCUMENT_V2_0, W3C_VERIFIABLE_DOCUMENT } from '../fixtures/fixtures'; +import { + ECDSA_W3C_VERIFIABLE_DOCUMENT_V2_0, + BBS2023_W3C_VERIFIABLE_DOCUMENT_V2_0, + W3C_VERIFIABLE_DOCUMENT, +} from '../fixtures/fixtures'; import { signW3C } from '../..'; import { VerificationType } from '@trustvc/w3c-issuer'; import type { CryptoSuiteName } from '@trustvc/w3c-vc'; @@ -27,9 +31,10 @@ describe('W3C sign', () => { }, 'BbsBlsSignature2020', ); - expect(signingResult.signed).toBeDefined(); - expect(signingResult.signed.proof).toBeDefined(); - expect(signingResult.signed.proof.type).toBe('BbsBlsSignature2020'); + expect(signingResult).toEqual({ + error: + 'BbsBlsSignature2020 is no longer supported. Please use the latest cryptosuite versions instead.', + }); }); const ecdsaSdTestCases: TestCase[] = [ @@ -47,6 +52,21 @@ describe('W3C sign', () => { }, ]; + const bbs2023TestCases: TestCase[] = [ + { + name: 'Should sign a W3C v2.0 document using BBS-2023', + proofType: 'bbs-2023', + options: undefined, + }, + { + name: 'Should sign a W3C v2.0 document using BBS-2023 with mandatory pointers', + proofType: 'bbs-2023', + options: { + mandatoryPointers: ['/credentialStatus'], + }, + }, + ]; + ecdsaSdTestCases.forEach(({ name, proofType, options }) => { it(name, async () => { const { @@ -78,4 +98,37 @@ describe('W3C sign', () => { expect(signingResult.signed.proof.type).toBe('DataIntegrityProof'); }); }); + + bbs2023TestCases.forEach(({ name, proofType, options }) => { + it(name, async () => { + const { + //eslint-disable-next-line @typescript-eslint/no-unused-vars + proof, + //eslint-disable-next-line @typescript-eslint/no-unused-vars + id, + //eslint-disable-next-line @typescript-eslint/no-unused-vars + credentialStatus: { tokenId, ...restCredentialStatus }, + ...documentWithoutProof + } = BBS2023_W3C_VERIFIABLE_DOCUMENT_V2_0; + + const signingResult = await signW3C( + { ...documentWithoutProof, credentialStatus: restCredentialStatus }, + { + '@context': 'https://w3id.org/security/multikey/v1', + id: 'did:web:trustvc.github.io:did:1#multikey-2', + type: VerificationType.Multikey, + controller: 'did:web:trustvc.github.io:did:1', + publicKeyMultibase: + 'zUC7HnpncVAkTjtL6B8prX6bQM2WA5sJ7rXFeCqyrvPnrzoFBjYsVUTNwzhhPUazja73tWwPeEBWCUgq5qBSrtrXiYhVvBCgZPTCiWANj7TSiZJ6SnyC3pkt94GiuChhAvmRRbt', + secretKeyMultibase: 'z488ur1KSFDd3Y1L6pXcPrZRjE18PNBhgzwJvMeoSxKPNysj', + }, + proofType, + options, + ); + + expect(signingResult.signed).toBeDefined(); + expect(signingResult.signed.proof).toBeDefined(); + expect(signingResult.signed.proof.type).toBe('DataIntegrityProof'); + }); + }); }); diff --git a/src/core/documentBuilder.ts b/src/core/documentBuilder.ts index 170393d..19fe65d 100644 --- a/src/core/documentBuilder.ts +++ b/src/core/documentBuilder.ts @@ -2,7 +2,6 @@ import { CryptoSuite, PrivateKeyPair } from '@trustvc/w3c-issuer'; import { deriveW3C, signW3C, verifyW3CSignature } from '../w3c'; import { assertCredentialStatus, assertTransferableRecords } from '@trustvc/w3c-credential-status'; import { - ContextDocument, CredentialStatus, CryptoSuiteName, SignedVerifiableCredential, @@ -210,7 +209,7 @@ export class DocumentBuilder { return signedVC.signed; } - async derive(revealedAttributes: ContextDocument | string[]) { + async derive(revealedAttributes: string[]) { if (!this.isSigned) throw new Error('Configuration Error: Document is not signed yet.'); if (this.isDerived) throw new Error('Configuration Error: Document is already derived.'); diff --git a/src/w3c/derive.ts b/src/w3c/derive.ts index 0b7db10..3927425 100644 --- a/src/w3c/derive.ts +++ b/src/w3c/derive.ts @@ -1,20 +1,15 @@ -import { - ContextDocument, - deriveCredential, - DerivedResult, - SignedVerifiableCredential, -} from '@trustvc/w3c-vc'; +import { deriveCredential, DerivedResult, SignedVerifiableCredential } from '@trustvc/w3c-vc'; /** * Derives a credential with selective disclosure based on revealed attributes. * @param {object} credential - The verifiable credential to be selectively disclosed. - * @param {object|string[]} revealedAttributes - For BBS+: The attributes from the credential that should be revealed. For ECDSA-SD-2023: Array of selective pointers. + * @param {string[]} revealedAttributes - Array of selective pointers. * @returns {Promise} A DerivedResult containing the derived proof or an error message. */ export const deriveW3C = async ( credential: SignedVerifiableCredential, - revealedAttributes: ContextDocument | string[], + revealedAttributes: string[], ): Promise => { return deriveCredential(credential, revealedAttributes); }; diff --git a/src/w3c/sign.ts b/src/w3c/sign.ts index e353bab..e83cdeb 100644 --- a/src/w3c/sign.ts +++ b/src/w3c/sign.ts @@ -6,8 +6,8 @@ import { RawVerifiableCredential, SigningResult, PrivateKeyPair } from './types' * @param {RawVerifiableCredential} credential - The verifiable credential object that needs to be signed. * @param {PrivateKeyPair} keyPair - The private and public key pair used for signing the credential. * @param {CryptoSuiteName} [cryptoSuite='ecdsa-sd-2023'] - The cryptographic suite to be used for signing (default is 'ecdsa-sd-2023'). - * @param {object} [options] - Optional parameters including mandatoryPointers for ECDSA-SD-2023. - * @param {string[]} [options.mandatoryPointers] - Optional mandatory pointers for ECDSA-SD-2023. + * @param {object} [options] - Optional parameters including mandatoryPointers for both ECDSA-SD-2023 / BBS-2023. + * @param {string[]} [options.mandatoryPointers] - Optional mandatory pointers for both ECDSA-SD-2023 / BBS-2023. * @returns {Promise} A promise that resolves to the result of the signing operation, which includes the signed credential. */ export const signW3C = async ( From aa571453d907a158e93dca857421c6f7d5f9b337 Mon Sep 17 00:00:00 2001 From: moiz-sgtradex Date: Mon, 6 Oct 2025 15:31:42 +0530 Subject: [PATCH 2/5] feat: comment added --- src/core/documentBuilder.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/documentBuilder.ts b/src/core/documentBuilder.ts index 19fe65d..8b34f1d 100644 --- a/src/core/documentBuilder.ts +++ b/src/core/documentBuilder.ts @@ -184,6 +184,7 @@ export class DocumentBuilder { // Verify the document's credential status based on the selected status type. if (this.selectedStatusType === 'verifiableDocument') { + // TODO: For document builder, let user use only BitStringStatusList with v2.0 assertCredentialStatus(this.document.credentialStatus); const verificationResult = await verifyCredentialStatus(this.document.credentialStatus); if (verificationResult.error) From 363fc3db46acefb914b4529d49d9309d7bb6b540 Mon Sep 17 00:00:00 2001 From: moiz-sgtradex Date: Wed, 8 Oct 2025 09:14:53 +0530 Subject: [PATCH 3/5] feat: readme update --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bb59bf1..0ab4c43 100644 --- a/README.md +++ b/README.md @@ -227,7 +227,7 @@ const signingResultWithBbs2023 = await signW3C( ); // ⚠️ DEPRECATED: BbsBlsSignature2020 is no longer supported -// Use 'ecdsa-sd-20203 or bbs-2023' cryptosuite instead as shown above +// Use 'ecdsa-sd-2023 or bbs-2023' cryptosuite instead as shown above const signingResultWithBbs = await signW3C( rawDocument, { From f48d516a6e5d93af52b04bd624beabf843867370 Mon Sep 17 00:00:00 2001 From: moiz-sgtradex Date: Wed, 8 Oct 2025 09:15:59 +0530 Subject: [PATCH 4/5] feat: readme update --- src/__tests__/w3c/derive.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__tests__/w3c/derive.test.ts b/src/__tests__/w3c/derive.test.ts index c58d422..b653dde 100644 --- a/src/__tests__/w3c/derive.test.ts +++ b/src/__tests__/w3c/derive.test.ts @@ -7,7 +7,7 @@ import { deriveW3C } from 'src/w3c'; import { SignedVerifiableCredential } from '@trustvc/w3c-vc'; describe('W3C derive', () => { - // credentialStatus is defined since the document has been signed with credentailStatus as mandatory parameter + // credentialStatus is defined since the document has been signed with credentialStatus as mandatory parameter it('should derive a W3C v2.0 document using ECDSA-SD-2023 without custom selective pointers', async () => { const result = await deriveW3C( ECDSA_W3C_VERIFIABLE_DOCUMENT_V2_0 as SignedVerifiableCredential, From d236cc1e45c922068693fdd49ea4f540116209ea Mon Sep 17 00:00:00 2001 From: moiz-sgtradex Date: Wed, 8 Oct 2025 09:28:18 +0530 Subject: [PATCH 5/5] feat: derive test case update --- src/__tests__/w3c/derive.test.ts | 100 ++++++++++++++++--------------- 1 file changed, 52 insertions(+), 48 deletions(-) diff --git a/src/__tests__/w3c/derive.test.ts b/src/__tests__/w3c/derive.test.ts index b653dde..a725d74 100644 --- a/src/__tests__/w3c/derive.test.ts +++ b/src/__tests__/w3c/derive.test.ts @@ -7,56 +7,60 @@ import { deriveW3C } from 'src/w3c'; import { SignedVerifiableCredential } from '@trustvc/w3c-vc'; describe('W3C derive', () => { - // credentialStatus is defined since the document has been signed with credentialStatus as mandatory parameter - it('should derive a W3C v2.0 document using ECDSA-SD-2023 without custom selective pointers', async () => { - const result = await deriveW3C( - ECDSA_W3C_VERIFIABLE_DOCUMENT_V2_0 as SignedVerifiableCredential, - [], - ); - expect(result.derived).toBeDefined(); - expect(result.derived.proof).toBeDefined(); - expect(result.derived['@context']).toBeDefined(); - expect(result.derived.credentialStatus).toBeDefined(); - expect(result.derived.renderMethod).toBeUndefined(); - expect(result.derived.qrCode).toBeUndefined(); - }); + // Test case configuration + const testCases = [ + { + name: 'ECDSA-SD-2023', + document: ECDSA_W3C_VERIFIABLE_DOCUMENT_V2_0, + }, + { + name: 'BBS-2023', + document: BBS2023_W3C_VERIFIABLE_DOCUMENT_V2_0, + }, + ]; - it('should derive a W3C v2.0 document using ECDSA-SD-2023 with custom selective pointers', async () => { - const result = await deriveW3C( - ECDSA_W3C_VERIFIABLE_DOCUMENT_V2_0 as SignedVerifiableCredential, - ['/renderMethod', '/qrCode'], - ); - expect(result.derived).toBeDefined(); - expect(result.derived.proof).toBeDefined(); - expect(result.derived['@context']).toBeDefined(); - expect(result.derived.credentialStatus).toBeDefined(); - expect(result.derived.renderMethod).toBeDefined(); - expect(result.derived.qrCode).toBeDefined(); - }); + const scenarioTests = [ + { + scenario: 'without custom selective pointers', + selectivePointers: [], + expectations: { + renderMethodDefined: false, + qrCodeDefined: false, + }, + }, + { + scenario: 'with custom selective pointers', + selectivePointers: ['/renderMethod', '/qrCode'], + expectations: { + renderMethodDefined: true, + qrCodeDefined: true, + }, + }, + ]; - it('should derive a W3C v2.0 document using BBS-2023 without custom selective pointers', async () => { - const result = await deriveW3C( - BBS2023_W3C_VERIFIABLE_DOCUMENT_V2_0 as SignedVerifiableCredential, - [], - ); - expect(result.derived).toBeDefined(); - expect(result.derived.proof).toBeDefined(); - expect(result.derived['@context']).toBeDefined(); - expect(result.derived.credentialStatus).toBeDefined(); - expect(result.derived.renderMethod).toBeUndefined(); - expect(result.derived.qrCode).toBeUndefined(); - }); + // Note: CredentialStatus is defined since the document has been signed with credentialStatus as mandatory parameter + testCases.forEach(({ name, document }) => { + scenarioTests.forEach(({ scenario, selectivePointers, expectations }) => { + it(`should derive a W3C v2.0 document using ${name} ${scenario}`, async () => { + const result = await deriveW3C(document as SignedVerifiableCredential, selectivePointers); + + expect(result.derived).toBeDefined(); + expect(result.derived.proof).toBeDefined(); + expect(result.derived['@context']).toBeDefined(); + expect(result.derived.credentialStatus).toBeDefined(); + + if (expectations.renderMethodDefined) { + expect(result.derived.renderMethod).toBeDefined(); + } else { + expect(result.derived.renderMethod).toBeUndefined(); + } - it('should derive a W3C v2.0 document using BBS-2023 with custom selective pointers', async () => { - const result = await deriveW3C( - BBS2023_W3C_VERIFIABLE_DOCUMENT_V2_0 as SignedVerifiableCredential, - ['/renderMethod', '/qrCode'], - ); - expect(result.derived).toBeDefined(); - expect(result.derived.proof).toBeDefined(); - expect(result.derived['@context']).toBeDefined(); - expect(result.derived.credentialStatus).toBeDefined(); - expect(result.derived.renderMethod).toBeDefined(); - expect(result.derived.qrCode).toBeDefined(); + if (expectations.qrCodeDefined) { + expect(result.derived.qrCode).toBeDefined(); + } else { + expect(result.derived.qrCode).toBeUndefined(); + } + }); + }); }); });