vc-signer
v0.1.2
Published
Verifiable Credential signing and verification utilities (did:key, did:web) for Accord Project artifacts
Maintainers
Readme
vc-signer
Verifiable Credential signing and verification utilities for Accord Project artifacts.
A small, payload-agnostic library that takes a private key, derives or accepts an issuer DID, and produces a signed W3C Verifiable Credential. Consumers (cicero-core for templates, future engines for contract transactions) supply the credentialSubject shape; this package handles key loading, DID resolution, cryptosuite selection, proof construction, and verification.
Status
Pre-release. Will be promoted to the accordproject GitHub organization as @accordproject/vc-signer once stabilized.
Supported algorithms
| Algorithm | Proof type | Cryptosuite |
| --- | --- | --- |
| Ed25519 (default) | DataIntegrityProof | eddsa-jcs-2022 |
| ECDSA P-256 | DataIntegrityProof | ecdsa-jcs-2019 |
| ECDSA secp256k1 | JsonWebSignature2020 | (detached JWS, alg ES256K) |
All three canonicalize via RFC 8785 JCS. The Ed25519 and P-256 paths emit the W3C-standardized JCS cryptosuite variants; secp256k1 falls back to JsonWebSignature2020 because no DataIntegrityProof cryptosuite is standardized for it.
Supported DID methods
did:key— offline-resolvable; the public key is encoded in the DID itself.did:web— resolved over HTTPS from the issuer's.well-known/did.json(path-style DIDs supported).
Supported private key inputs
- Encrypted PKCS#8 PEM (
-----BEGIN ENCRYPTED PRIVATE KEY-----) + passphrase. - JWE-wrapped JWK + passphrase (PBES2 family of key-management algorithms).
Raw unencrypted JWK or PEM inputs are intentionally rejected.
API
import {
loadSigningKey,
deriveDidKey,
resolveIssuerKey,
signCredential,
verifyCredential,
} from 'vc-signer';
// 1. Load a private key (PEM or JWE-wrapped JWK).
const keyInfo = await loadSigningKey({
privateKeyPem: { pem, passphrase },
// - or -
// privateKeyJwe: { jwe, passphrase },
});
// keyInfo = { algo, keyObject, privateJwk, publicJwk }
// 2a. Optionally derive a did:key from the public side.
const { did, verificationMethodId } = deriveDidKey(keyInfo.publicJwk);
// 2b. Or use did:web with a key you publish at /.well-known/did.json.
const issuerDid = 'did:web:publisher.example.com';
const vmId = `${issuerDid}#template-signer-2025-05`;
// 3. Issue a Verifiable Credential.
const credential = await signCredential({
type: ['VerifiableCredential', 'TemplateAuthorshipCredential'],
subject: {
id: 'ap-template:[email protected]',
templateHash: 'f4e5...c0',
templateName: 'helloworld',
templateVersion: '0.7.1',
},
signer: {
keyInfo,
issuerDid, // omit (or pass 'did:key') to auto-derive
verificationMethodId: vmId, // required for did:web; auto for did:key
},
});
// 4. Verify.
await verifyCredential(credential, {
expectedSubject: { templateHash: 'f4e5...c0' },
// fetcher: customFetch, // injectable for did:web in tests
});verifyCredential returns { verified: true, issuer, verificationMethodId, subject } on success and throws on any failure — invalid signature, unresolved DID, mismatched expectedSubject, etc. — so callers cannot accidentally treat a thrown error as a successful verification.
Generating a publisher key (one-time setup)
# Ed25519 (recommended default)
openssl genpkey -algorithm ED25519 -out signer.pem -aes-256-cbc
# ECDSA P-256
openssl genpkey -algorithm EC \
-pkeyopt ec_paramgen_curve:P-256 \
-out signer.pem -aes-256-cbc
# ECDSA secp256k1
openssl genpkey -algorithm EC \
-pkeyopt ec_paramgen_curve:secp256k1 \
-out signer.pem -aes-256-cbcFor did:web publishing, derive a public JWK from the keypair (any standard tool), embed it as publicKeyJwk inside a did.json, and host that at https://<your-domain>/.well-known/did.json.
For did:key, no hosting is needed — the DID string itself encodes the public key.
Design notes
- JCS over RDFC — the Ed25519 / P-256 paths could use the digitalbazaar RDFC cryptosuites for stricter JSON-LD compatibility, but doing so pulls in a JSON-LD document loader, context bundles, and a dependency surface that doesn't make sense for our self-contained signer. JCS gives an equivalent W3C-standardized result with a flat dependency graph (only
canonicalize,jose,multiformats). - secp256k1 is the odd one — every modern VC stack treats it as a second-class citizen. We support it because Accord Project artifacts may end up in blockchain-adjacent flows, but it uses the older
JsonWebSignature2020proof type rather than aDataIntegrityProofcryptosuite. - Verifier throws on failure — the alternative ("return
{ verified: false }") makes it too easy to writeawait verifyCredential(...)and silently accept invalid VCs in callers that forget the boolean check.
License
Apache-2.0
