npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@trustvc/w3c-vc

v2.4.2

Published

This repository provides utilities for signing, verifying, and deriving Verifiable Credentials (VCs) using modern cryptographic signature schemes including ECDSA-SD-2023 and BBS-2023, with selective disclosure capabilities. These functions ensure the auth

Readme

TrustVC W3C VC

This repository provides utilities for signing, verifying, and deriving Verifiable Credentials (VCs) using modern cryptographic signature schemes including ECDSA-SD-2023 and BBS-2023, with selective disclosure capabilities. These functions ensure the authenticity and integrity of VCs within a W3C-compliant ecosystem.

Table of Contents

Installation

To install the package, use:

npm install @trustvc/w3c-vc

Features

  • Modern Cryptosuites: ECDSA-SD-2023 (default) and BBS-2023 with selective disclosure
  • W3C Compliance: Support for both W3C VC Data Model v1.1 and v2.0
  • DID Method Agnostic: Sign and verify with did:web issuers (hosted DID document) or did:key issuers (self-certifying, no hosting). See @trustvc/w3c-issuer for issuance.
  • Selective Disclosure: Derive credentials with selective field revelation
  • Verifiable Presentations: Create (with strict input validation + mandatory expiry), sign (optional holder binding via ecdsa-rdfc-2019, with or without challenge/domain, did:key or did:web holder) and verify presentations that may wrap credentials of mixed cryptosuites and versions
  • Legacy Support: Deprecated BbsBlsSignature2020 signature support (verification only)
  • Schema Validation: Checks if payload matches W3C VC schema
  • Version Detection: Helper functions to detect credential versions

Supported Cryptosuites

| Cryptosuite | Status | Signing | Verification | Derivation | Notes | |-------------|--------|---------|--------------|------------|-------| | ecdsa-sd-2023 | ✅ Active (Default) | ✅ | ✅ | ✅ | Modern, fast, selective disclosure | | bbs-2023 | ✅ Active | ✅ | ✅ | ✅ | Modern BBS with selective disclosure | | BbsBlsSignature2020 | ⚠️ Deprecated | ❌ | ✅ | ❌ | Legacy support only |

Usage

A note on issuer DIDs: the examples below use did:web issuers, but the same signCredential / verifyCredential / deriveCredential calls work unchanged for did:key issuers — just replace the keyPair's id / controller and the credential's issuer with the did:key:zXxx#zXxx / did:key:zXxx form. The default document loader from @trustvc/w3c-context resolves did:key URIs in-memory (no network call), so verification works out of the box. See @trustvc/w3c-issuer's did:key guide for the full flow.

1. Signing a Credential

The signCredential function allows you to sign a Verifiable Credential using modern cryptographic signature schemes. Default cryptosuite is ecdsa-sd-2023.

ECDSA-SD-2023 Signing (Default)

import { signCredential } from '@trustvc/w3c-vc';

/**
 * Parameters:
 * - credential (RawVerifiableCredential): The credential to be signed.
 * - keyPair (PrivateKeyPair): The key pair options for signing.
 * - cryptoSuite (CryptoSuiteName, optional): The cryptosuite to be used. Defaults to "ecdsa-sd-2023".
 * - options (object, optional): Optional parameters including documentLoader and mandatoryPointers.
 * 
 * Returns:
 * - A Promise that resolves to:
 *   - signedCredential.signed (SignedVerifiableCredential): The signed credential.
 *   - signedCredential.error (string): The error message in case of failure.
 */

const ecdsaKeyPair = {
  "id": "did:web:trustvc.github.io:did:1#multikey-1",
  "type": "Multikey",
  "controller": "did:web:trustvc.github.io:did:1",
  "secretKeyMultibase": "<secretKeyMultibase>",
  "publicKeyMultibase": "<publicKeyMultibase>"
};

const credential = {
  "@context": [
    "https://www.w3.org/ns/credentials/v2",
    "https://w3id.org/security/data-integrity/v2",
    "https://w3id.org/citizenship/v2"
  ],
  "credentialSubject": {
    "id": "did:example:b34ca6cd37bbf23",
    "type": ["Person"],
    "name": "TrustVC"
  },
  "validFrom": "2024-04-01T12:19:52Z",
  "validUntil": "2029-12-03T12:19:52Z",
  "issuer": "did:web:trustvc.github.io:did:1",
  "type": ["VerifiableCredential"]
};

// Sign with default ECDSA-SD-2023 cryptosuite
const signedCredential = await signCredential(credential, ecdsaKeyPair);

// Or explicitly specify cryptosuite with mandatory pointers
const signedCredentialWithOptions = await signCredential(credential, ecdsaKeyPair, 'ecdsa-sd-2023', {
  mandatoryPointers: ['/credentialSubject/name']
});

if (signedCredential.signed) {
  console.log('Signed Credential:', signedCredential.signed);
} else {
  console.error('Error:', signedCredential.error);
}

BBS-2023 Signing

import { signCredential } from '@trustvc/w3c-vc';

const bbsKeyPair = {
  "id": "did:web:trustvc.github.io:did:1#multikey-2",
  "type": "Multikey",
  "controller": "did:web:trustvc.github.io:did:1",
  "secretKeyMultibase": "<secretKeyMultibase>",
  "publicKeyMultibase": "<publicKeyMultibase>"
};

const credential = {
  "@context": [
    "https://www.w3.org/ns/credentials/v2",
    "https://w3id.org/security/data-integrity/v2",
    "https://w3id.org/citizenship/v2"
  ],
  "credentialSubject": {
    "id": "did:example:b34ca6cd37bbf23",
    "type": ["Person"],
    "name": "TrustVC"
  },
  "validFrom": "2024-04-01T12:19:52Z",
  "validUntil": "2029-12-03T12:19:52Z",
  "issuer": "did:web:trustvc.github.io:did:1",
  "type": ["VerifiableCredential"]
};

// Sign with BBS-2023 cryptosuite
const signedCredential = await signCredential(credential, bbsKeyPair, 'bbs-2023', {
  mandatoryPointers: ['/credentialSubject/name']
});

if (signedCredential.signed) {
  console.log('BBS-2023 Signed Credential:', signedCredential.signed);
} else {
  console.error('Error:', signedCredential.error);
}

Legacy BbsBlsSignature2020 Signing (Deprecated)

import { signCredential } from '@trustvc/w3c-vc';

// ⚠️ DEPRECATED: BbsBlsSignature2020 signing is no longer supported
const legacyResult = await signCredential(credential, keyPair, 'BbsBlsSignature2020');

// This will return an error:
// { error: "BbsBlsSignature2020 is no longer supported. Please use the latest cryptosuite versions instead." }

2. Verifying a Credential

The verifyCredential function allows you to verify signed Verifiable Credentials.

Modern Cryptosuite Verification (ECDSA-SD-2023 / BBS-2023)

Important: For modern cryptosuites (ECDSA-SD-2023, BBS-2023), base credentials must be derived before verification. You cannot directly verify the original signed credential.

import { verifyCredential, deriveCredential } from '@trustvc/w3c-vc';

/**
 * Parameters:
 * - credential (SignedVerifiableCredential): The credential to be verified.
 * - options (object, optional): Optional parameters including documentLoader.
 * 
 * Returns:
 * - A Promise that resolves to:
 *   - verificationResult.verified (boolean): Whether the verification was successful.
 *   - verificationResult.error (string): The error message if the verification failed.
 */

const signedCredential = {
  '@context': [
    'https://www.w3.org/ns/credentials/v2',
    'https://w3id.org/security/data-integrity/v2',
    "https://w3id.org/citizenship/v2"
  ],
  credentialSubject: {
    id: 'did:example:b34ca6cd37bbf23',
    type: ['Person'],
    name: 'TrustVC'
  },
  validFrom: '2024-04-01T12:19:52Z',
  validUntil: '2029-12-03T12:19:52Z',
  issuer: 'did:web:trustvc.github.io:did:1',
  type: ['VerifiableCredential'],
  proof: {
    type: 'DataIntegrityProof',
    cryptosuite: 'ecdsa-sd-2023',
    created: '2024-10-02T09:04:07Z',
    proofPurpose: 'assertionMethod',
    proofValue: 'z...',
    verificationMethod: 'did:web:trustvc.github.io:did:1#multikey-1'
  }
};

// ❌ This will fail - base credentials cannot be verified directly
const baseVerification = await verifyCredential(signedCredential);
// Returns: { verified: false, error: "ecdsa-sd-2023 base credentials must be derived before verification. Use deriveCredential() first." }

// ✅ Correct approach: Derive first, then verify
const derivedResult = await deriveCredential(signedCredential, ['/credentialSubject/name']);

if (derivedResult.derived) {
  const verificationResult = await verifyCredential(derivedResult.derived);
  
  if (verificationResult.verified) {
    console.log('Credential verified successfully.');
  } else {
    console.error('Verification failed:', verificationResult.error);
  }
}

Legacy BbsBlsSignature2020 Verification

import { verifyCredential } from '@trustvc/w3c-vc';

// Legacy BbsBlsSignature2020 credentials can be verified directly (both base and derived credentials)
const bbsCredential = {
  '@context': [
    'https://www.w3.org/2018/credentials/v1',
    'https://w3id.org/security/bbs/v1',
    'https://w3id.org/citizenship/v1'
  ],
  credentialSubject: {
    id: 'did:example:b34ca6cd37bbf23',
    type: ['Person'],
    name: 'TrustVC'
  },
  issuanceDate: '2024-04-01T12:19:52Z',
  expirationDate: '2029-12-03T12:19:52Z',
  issuer: 'did:web:trustvc.github.io:did:1',
  type: [ 'VerifiableCredential' ],
  proof: {
    type: 'BbsBlsSignature2020',
    created: '2024-10-02T09:04:07Z',
    proofPurpose: 'assertionMethod',
    proofValue: 'A...',
    verificationMethod: 'did:web:trustvc.github.io:did:1#keys-1'
  }
};

const verificationResult = await verifyCredential(bbsCredential);

if (verificationResult.verified) {
  console.log('Legacy BbsBlsSignature2020 credential verified successfully.');
} else {
  console.error('Verification failed:', verificationResult.error);
}

3. Deriving a Credential (Selective Disclosure)

The deriveCredential function allows you to create selective disclosure from signed credentials.

ECDSA-SD-2023 Selective Disclosure

import { deriveCredential } from '@trustvc/w3c-vc';

/**
 * Parameters:
 * - credential (SignedVerifiableCredential): The verifiable credential to be selectively disclosed.
 * - revealedAttributes (string[]): Array of JSON pointers specifying which fields to reveal.
 * - options (object, optional): Optional parameters including documentLoader.
 * 
 * Returns:
 * - A Promise that resolves to:
 *   - derived (SignedVerifiableCredential): The selectively disclosed credential.
 *   - error (string): The error message in case of failure.
 */

const signedCredential = {
  '@context': [
    'https://www.w3.org/ns/credentials/v2',
    'https://w3id.org/security/data-integrity/v2',
    "https://w3id.org/citizenship/v2"
  ],
  credentialSubject: {
    id: 'did:example:b34ca6cd37bbf23',
    type: ['Person'],
    name: 'TrustVC'
  },
  validFrom: '2024-04-01T12:19:52Z',
  validUntil: '2029-12-03T12:19:52Z',
  issuer: 'did:web:trustvc.github.io:did:1',
  type: ['VerifiableCredential'],
  proof: {
    type: 'DataIntegrityProof',
    cryptosuite: 'ecdsa-sd-2023',
    // ... proof details
  }
};

// Reveal only specific fields (mandatory pointers are automatically included)
const selectivePointers = ['/credentialSubject/name'];

const derivedResult = await deriveCredential(signedCredential, selectivePointers);

if (derivedResult.derived) {
  console.log('Derived ECDSA-SD-2023 Credential:', derivedResult.derived);
} else {
  console.error('Error:', derivedResult.error);
}

// ❌ Multiple derivation rounds are not supported
const secondDerivation = await deriveCredential(derivedResult.derived, selectivePointers);
// Returns: { error: "ecdsa-sd-2023 derived credentials cannot be further derived. Multiple rounds of derivation are not supported by this cryptosuite." }

BBS-2023 Selective Disclosure

import { deriveCredential } from '@trustvc/w3c-vc';

const bbsSignedCredential = {
  '@context': [
    'https://www.w3.org/ns/credentials/v2',
    'https://w3id.org/security/data-integrity/v2',
    "https://w3id.org/citizenship/v2"
  ],
  credentialSubject: {
    id: 'did:example:b34ca6cd37bbf23',
    type: ['Person'],
    name: 'TrustVC'
  },
  validFrom: '2024-04-01T12:19:52Z',
  validUntil: '2029-12-03T12:19:52Z',
  issuer: 'did:web:trustvc.github.io:did:1',
  type: ['VerifiableCredential'],
  proof: {
    type: 'DataIntegrityProof',
    cryptosuite: 'bbs-2023',
    // ... proof details
  }
};

// Reveal only specific fields (mandatory pointers are automatically included)
const selectivePointers = ['/credentialSubject/name'];

const derivedResult = await deriveCredential(bbsSignedCredential, selectivePointers);

if (derivedResult.derived) {
  console.log('Derived BBS-2023 Credential:', derivedResult.derived);
} else {
  console.error('Error:', derivedResult.error);
}

Legacy BbsBlsSignature2020 Derivation (Deprecated)

import { deriveCredential } from '@trustvc/w3c-vc';

// ⚠️ DEPRECATED: BbsBlsSignature2020 derivation is no longer supported
const legacyDerivation = await deriveCredential(credential, revealedAttributes);

// This will return an error:
// { error: "BbsBlsSignature2020 is no longer supported for derivation. Please use the latest cryptosuite versions instead." }

4. Schema Validation

Validate if a document meets W3C VC schema requirements:

import { 
  isRawDocument, 
  isSignedDocument,
  isRawDocumentV1_1,
  isRawDocumentV2_0,
  isSignedDocumentV1_1,
  isSignedDocumentV2_0,
  isDerived
} from '@trustvc/w3c-vc';

/**
 * Available validation functions:
 * - isRawDocument(document): Checks if document is a valid raw credential
 * - isSignedDocument(document): Checks if document is a valid signed credential
 * - isRawDocumentV1_1(document): Checks if document is a valid v1.1 raw credential
 * - isRawDocumentV2_0(document): Checks if document is a valid v2.0 raw credential
 * - isSignedDocumentV1_1(document): Checks if document is a valid v1.1 signed credential
 * - isSignedDocumentV2_0(document): Checks if document is a valid v2.0 signed credential
 * - isDerived(document): Checks if document is a derived (selective disclosure) credential
 */

const document = {
  "@context": [
    "https://www.w3.org/ns/credentials/v2",
    "https://w3id.org/security/data-integrity/v2",
    "https://w3id.org/citizenship/v2"
  ],
  "type": ["VerifiableCredential"],
  "issuer": "did:web:example.com",
  "validFrom": "2024-01-01T00:00:00Z",
  "credentialSubject": {
    "id": "did:example:123",
    "type": ["Person"],
    "name": "John Doe"
  }
};

// Basic validation
console.log('Is raw document:', isRawDocument(document)); // true
console.log('Is signed document:', isSignedDocument(document)); // false (no proof)

// Version-specific validation
console.log('Is v1.1 raw document:', isRawDocumentV1_1(document)); // false
console.log('Is v2.0 raw document:', isRawDocumentV2_0(document)); // true

// Check if credential is derived (selective disclosure)
const signedDocument = { ...document, proof: { /* proof object */ } };
console.log('Is derived credential:', await isDerived(signedDocument));

5. Verifiable Presentations

A Verifiable Presentation (VP) is an envelope that a holder wraps around one or more Verifiable Credentials to present them to a verifier. It has two independent layers:

  1. The embedded credentials (verifiableCredential) — each keeps its own proof. A single VP may freely mix cryptosuites (ecdsa-sd-2023, bbs-2023, BbsBlsSignature2020) and VC data-model versions (v1.1 / v2.0). Each is verified independently.
  2. An optional holder-binding proof over the whole envelope, with a challenge (and optional domain) supplied by the verifier to prevent replay. This proves the holder controls the key and made this presentation for this verifier.

Which cryptosuite signs the presentation?

The holder proof must sign the entire envelope, so it uses a plain (non-selective-disclosure) suite: ecdsa-rdfc-2019. This reuses the same ECDSA (P-256) Multikey used for ecdsa-sd-2023 credentials — the holder's signing key is independent of the suites used by the credentials inside. The holder DID may be a did:key (self-certifying, nothing to host) or a did:web — both resolve out of the box.

The selective-disclosure suites (ecdsa-sd-2023, bbs-2023) cannot sign a presentation: verifiableCredential is a JSON-LD @graph container that JSON-pointer selective disclosure cannot cover, so the proof would not bind the credentials being presented. The deprecated BbsBlsSignature2020 is likewise not used. signPresentation returns a descriptive error if you request any of them. This only affects the outer proof — the credentials inside the VP may still use any suite.

A BBS-only holder (no ECDSA key) cannot produce a modern holder proof (there is no non-deprecated plain BBS suite) — either generate an ECDSA key or present an unsigned VP (see below).

| Layer | Cryptosuite | |-------|-------------| | VP holder proof (outer) | ecdsa-rdfc-2019 (ECDSA P-256 Multikey, did:key or did:web) | | Embedded credentials | any (ecdsa-sd-2023, bbs-2023, BbsBlsSignature2020), any version |

Transferable records cannot be presented via a VP

Credentials with a TransferableRecords credentialStatus are controlled on-chain via their token registry — possession is proven by token ownership, not by a presentation. createPresentation (and signPresentation) therefore throw / error if any embedded credential carries a TransferableRecords status. Present those through their token ownership instead.

Strict validation at creation + mandatory expiry

createPresentation is async and validates every input credential before producing a VP. If any credential is not signed, expired, future-dated, revoked, or a transferable record, it throws with a clear reason (and index) and no VP is produced. The VP is also stamped with a mandatory, configurable expiry (validFrom = now, validUntil = now + lifetime; default 5 minutes).

The presentation envelope defaults to VC Data Model v2.0 regardless of the embedded credentials' versions (each credential keeps its own @context); pass version: 'v1' only if a verifier specifically requires a v1.1 presentation.

const presentation = await createPresentation(signedCredentials, {
  holder: 'did:web:holder.example',
  expiresInSeconds: 300,        // VP lifetime (default 300s); or pass an explicit validUntil
  // version: 'v2',             // envelope version (default 'v2'); wraps v1.1 or v2.0 credentials
  // fullDisclosure: true,      // auto-derive any base SD credential (reveals ALL fields)
});
// throws e.g. "credential at index 0 is not valid: Invalid signature."
//            "credential at index 1 has expired (2024-01-01T…)."
//            "credential at index 0 has been revoked (credentialStatus)."

Three signing routes

| Route | Call | Proof | Notes | |-------|------|-------|-------| | 1. Unsigned | createPresentation(...)verifyPresentation(vp) | none | verifies credential authenticity only | | 2. Signed, no challenge | signPresentation(vp, key) | assertionMethod | proves holder signed it; not anti-replay | | 3. Signed, with challenge | signPresentation(vp, key, { challenge, domain }) | authentication | challenge/domain enforced on verify (anti-replay) |

import { createPresentation, signPresentation, verifyPresentation } from '@trustvc/w3c-vc';

// `signedCredentials` are already signed (and, for SD suites, derived) — mixed suites/versions ok.
const vp = await createPresentation(signedCredentials, { holder: 'did:web:holder.example' });

// Route 3 — signed with a verifier-issued challenge (strongest; needs a server to issue/track it)
const challenge = 'a-random-verifier-supplied-nonce';
const { signed, error } = await signPresentation(vp, holderEcdsaKey, {
  challenge,
  domain: 'verifier.example.com', // optional audience binding
});
if (error) throw new Error(error);

const result = await verifyPresentation(signed, {
  challenge,                      // REQUIRED for an authentication proof; never read from the proof
  domain: 'verifier.example.com',
  requireProof: true,             // reject an unsigned VP
  checkHolderBinding: true,       // signer DID == holder == every credentialSubject.id
  maxLifetimeSeconds: 300,        // reject a VP whose lifetime exceeds this (defeats a huge validUntil)
});

console.log(result.verified);          // true only if the holder proof + all credentials verify + not expired
console.log(result.presentationResult); // outcome of the holder proof (undefined if unsigned)
console.log(result.credentialResults);  // per-credential outcomes (with credentialIndex)

challenge/domain are optional (per the W3C Data Integrity spec). Provide a challenge (Route 3) for single-use anti-replay — this needs a server to issue a fresh nonce and track/consume it. Omit it (Route 2) for a signed-but-replayable proof. verifyPresentation requires you to pass the challenge it issued — the value in the proof is never trusted (that would allow replay).

Stateless alternative for frontends. If you have no backend to track challenges, rely on the VP expiry instead: a short-lived signed VP + maxLifetimeSeconds on verify gives time-boxed replay mitigation with no server state.

Unsigned presentations

If you only need to bundle credentials (no cryptographic holder binding), omit the signing step. verifyPresentation still verifies every embedded credential (and the VP expiry):

const presentation = await createPresentation(signedCredentials);
const result = await verifyPresentation(presentation);
// result.verified reflects whether all embedded credentials verify and the VP is not expired;
// result.presentationResult is undefined (no holder proof present).

Note: an unsigned VP does not prove who is presenting it — anyone holding a copy of the credentials could send it. Sign it (Route 2/3) when you need to prove the holder controls the subject's key.

Presentation helper functions

import { isRawPresentation, isSignedPresentation } from '@trustvc/w3c-vc';

isRawPresentation(presentation); // true for a structurally valid unsigned VP
isSignedPresentation(signed); // true for a structurally valid VP carrying a proof

Migration from Legacy BbsBlsSignature2020

If you're migrating from legacy BbsBlsSignature2020 signatures:

  1. Update Key Pairs: Use Multikey format instead of Bls12381G2Key2020
  2. Change Cryptosuite: Use 'ecdsa-sd-2023' or 'bbs-2023' instead of 'BbsBlsSignature2020'
  3. Update Contexts: Use W3C VC Data Model v2.0 contexts
  4. Modify Derivation: Use JSON pointer arrays instead of ContextDocument format
  5. Update Verification Flow: Derive credentials before verification for modern cryptosuites
// Legacy BbsBlsSignature2020 (deprecated)
const legacyKeyPair = {
  type: "Bls12381G2Key2020",
  privateKeyBase58: "...",
  publicKeyBase58: "..."
};

// Modern approach
const modernKeyPair = {
  type: "Multikey",
  secretKeyMultibase: "...",
  publicKeyMultibase: "..."
};

Best Practices

  1. Use Modern Cryptosuites: Prefer 'ecdsa-sd-2023' (default) or 'bbs-2023' over legacy BbsBlsSignature2020
  2. Implement Proper Error Handling: Always check for errors in function results
  3. Derive Before Verify: For modern cryptosuites, always derive credentials before verification
  4. Use Mandatory Pointers: Specify fields that should always be revealed in selective disclosure
  5. Version Detection: Use helper functions to detect credential versions when needed
  6. Single Derivation: Remember that modern cryptosuites support only single-round derivation

License

This project is licensed under the Apache 2.0 License.