Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## About

A wrapper lib build for TrustVC to work with W3C [Verifiable Credentials](https://www.w3.org/TR/vc-data-model/) (VCs) Data Model v1.1. Provides packages to facilitate the creation of [Decentralized Identifiers](https://www.w3.org/TR/did-core/) (DIDs) v1, specifically [`did:web`](https://w3c-ccg.github.io/did-method-web/), and [Verifiable Credentials Status](https://www.w3.org/TR/2023/WD-vc-status-list-20230427/) Status List v2021.
A wrapper library built for TrustVC to work with W3C [Verifiable Credentials](https://www.w3.org/TR/vc-data-model/) (VCs) Data Model v1.1 and [v2.0](https://www.w3.org/TR/vc-data-model-2.0/). Provides packages to facilitate the creation of [Decentralized Identifiers](https://www.w3.org/TR/did-core/) (DIDs) v1, supporting both [`did:web`](https://w3c-ccg.github.io/did-method-web/) (host a DID document) and [`did:key`](https://w3c-ccg.github.io/did-key-spec/) (self-certifying, no hosting required), and Verifiable Credentials Status — both [Status List 2021](https://www.w3.org/TR/2023/WD-vc-status-list-20230427/) and the latest [Bitstring Status List](https://www.w3.org/TR/vc-bitstring-status-list/).

## Packages

Expand All @@ -21,7 +21,9 @@ For more details on each packages, refer to the individual README doc.
1. **Pre Requisite**

1. Generate a signature specific key pair. [link](https://github.com/TrustVC/w3c/tree/main/packages/w3c-issuer#1-create-private-key)
2. Generate and host a DID web identity. [link](https://github.com/TrustVC/w3c/tree/main/packages/w3c-issuer#2-generate-did-key-pair-and-did-document)
2. Choose a DID method:
- `did:web` — generate a DID document and host it on a domain you control. [did:web setup guide](https://github.com/TrustVC/w3c/tree/main/packages/w3c-issuer#2-generate-did-key-pair-and-did-document)
- `did:key` — generate a self-certifying DID key pair (no hosting required). [did:key setup guide](https://github.com/TrustVC/w3c/tree/main/packages/w3c-issuer#3-generate-a-didkey-key-pair)

2. **Sign and Verify VC**

Expand Down
26 changes: 13 additions & 13 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

56 changes: 55 additions & 1 deletion packages/w3c-issuer/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# TrustVC W3C Issuer

A library to facilitate the creation of [Decentralized Identifiers](https://www.w3.org/TR/did-core/) DIDs v1, specifically [`did:web`](https://w3c-ccg.github.io/did-method-web/), for the signing of [Verifiable Credentials](https://www.w3.org/TR/vc-data-model/) v1.1.
A library to facilitate the creation of [Decentralized Identifiers](https://www.w3.org/TR/did-core/) DIDs v1, supporting both [`did:web`](https://w3c-ccg.github.io/did-method-web/) (host a DID document) and [`did:key`](https://w3c-ccg.github.io/did-key-spec/) (self-certifying, no hosting required), for the signing of [Verifiable Credentials](https://www.w3.org/TR/vc-data-model/) v1.1 and [v2.0](https://www.w3.org/TR/vc-data-model-2.0/).

## Installation
To install the package, use:
Expand All @@ -12,6 +12,7 @@ npm install @trustvc/w3c-issuer
## Features
- Create private key pairs for specific signature suites used for signing Verifiable Credentials: [ECDSA-SD-2023](https://w3c.github.io/vc-di-ecdsa/#ecdsa-sd-2023), [BBS-2023](https://w3c.github.io/vc-di-bbs/#bbs-2023), and [legacy suites](https://w3c-ccg.github.io/ld-cryptosuite-registry/).
- Generate DID private key pairs and DID documents.
- Issue self-certifying `did:key` DIDs (P-256 for ECDSA-SD-2023, BLS12-381 G2 for BBS-2023) — no hosting required.

<br>

Expand Down Expand Up @@ -142,3 +143,56 @@ console.log("didKeyPairs:", didKeyPairs)
}
```
</details>

### 3. Generate a `did:key` Key Pair

`generateDidKeyPair` issues a self-certifying [`did:key`](https://w3c-ccg.github.io/did-key-spec/) DID together with the matching Multikey private key pair. Unlike `did:web`, there is no DID document to host — the public key is encoded directly into the DID, so the DID document is reconstructed deterministically by any verifier from the identifier alone.

> __DID Private Key Pair__ needs to be kept securely. Required for signing Verifiable Credentials. \
> __Issuer identity__ is not bound to any domain. If you need to bind a `did:key` to a real-world entity, use an out-of-band mechanism (trust registry, delegation credential, etc.). See the [`did:key` guide](https://github.com/TrustVC/w3c/tree/main/packages/w3c-issuer/src/did-key#trust-model) for the trust-model discussion.

```ts
import { CryptoSuite, generateDidKeyPair, parseDidKey } from '@trustvc/w3c-issuer';

/**
* Parameters:
* - cryptosuite (CryptoSuite): Either CryptoSuite.EcdsaSd2023 (P-256) or CryptoSuite.Bbs2023 (BLS12-381 G2)
* - options.seedBase58? (string): 32 byte base58 encoded seed for deterministic BBS-2023 generation (optional, ignored for ECDSA-SD-2023)
*
* Returns:
* - A Promise that resolves to:
* - did (string): The resulting did:key DID
* - didKeyPairs (PrivateKeyPair): Multikey private key pair scoped to the new DID
*/

const { did, didKeyPairs } = await generateDidKeyPair(CryptoSuite.EcdsaSd2023);
console.log('did:', did);
console.log('didKeyPairs:', didKeyPairs);
```

<details>
<summary>generateDidKeyPair Result</summary>

```js
did: 'did:key:zDnaemDNwi4G5eTzGfRooFFu5Kns3be6yfyVNtiaMhWkZbwtc'
didKeyPairs: {
'@context': 'https://w3id.org/security/multikey/v1',
id: 'did:key:zDnaemDNwi4G5eTzGfRooFFu5Kns3be6yfyVNtiaMhWkZbwtc#zDnaemDNwi4G5eTzGfRooFFu5Kns3be6yfyVNtiaMhWkZbwtc',
type: 'Multikey',
controller: 'did:key:zDnaemDNwi4G5eTzGfRooFFu5Kns3be6yfyVNtiaMhWkZbwtc',
publicKeyMultibase: 'zDnaemDNwi4G5eTzGfRooFFu5Kns3be6yfyVNtiaMhWkZbwtc',
secretKeyMultibase: '<secretKeyMultibase>'
}
```
</details>

`parseDidKey` decodes a `did:key` identifier back into its key type and raw public key bytes — useful if you receive a `did:key` from elsewhere and need to inspect it:

```ts
const info = parseDidKey('did:key:zDnaemDNwi4G5eTzGfRooFFu5Kns3be6yfyVNtiaMhWkZbwtc');
// info.keyType === 'P-256'
// info.publicKey is a Uint8Array of the compressed 33-byte P-256 public key
// info.verificationMethodId === 'did:key:zDna...#zDna...'
```

**Supported key types:** `P-256` (for `ecdsa-sd-2023`) and `BLS12-381 G2` (for `bbs-2023`). Other `did:key` types defined in the spec (Ed25519, secp256k1, P-384, etc.) are rejected at parse time because no matching cryptosuite is wired up in `@trustvc/w3c-vc`.
2 changes: 1 addition & 1 deletion packages/w3c-issuer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,4 @@
"registry": "https://registry.npmjs.org/"
},
"private": false
}
}
164 changes: 164 additions & 0 deletions packages/w3c-issuer/src/did-key/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# Using `did:key`

This guide explains how to issue and use a Decentralized Identifier (DID) with the [`did:key`](https://w3c-ccg.github.io/did-key-spec/) method. Unlike `did:web`, `did:key` is **self-certifying** — the public key is encoded directly into the DID itself, so no hosting infrastructure is required.

## Table of Contents

- 1. [How it differs from `did:web`](#how-it-differs-from-didweb)
- 2. [Trust model](#trust-model)
- 3. [Supported key types](#supported-key-types)
- 4. [Step-by-Step Usage](#step-by-step-usage)
- 1. [Generate a `did:key` Key Pair](#1-generate-a-didkey-key-pair)
- 2. [Sign a Credential](#2-sign-a-credential)
- 3. [Verify a Credential](#3-verify-a-credential)
- 5. [Anatomy of a `did:key` DID](#anatomy-of-a-didkey-did)
- 6. [Specifications](#specifications)

## How it differs from `did:web`

| | `did:web` | `did:key` |
|---|---|---|
| Hosting required | Yes (a `.well-known/did.json` or similar) | **No** |
| Verifier needs network | Yes (to fetch the DID document) | **No** (DID document is synthesised in memory) |
| Human-readable identifier | Yes (domain name in DID) | No (the DID is the encoded public key) |
| Multiple keys per DID | Yes | No (exactly one key per DID) |
| Key rotation | Update hosted document, DID stays the same | DID changes — a new key is a new DID |
| Revocation by removal | Yes (take the doc down, remove the VM) | Not possible — anyone with the key can keep signing |
| Trust anchor | The domain | **Must be supplied out-of-band** (see below) |

Example identifiers:

```text
did:web:trustvc.github.io:did:1 ← did:web (resolves via HTTPS)
did:key:zDnaemDNwi4G5eTzGfRooFFu5Kns3be6yfyVNtiaMhWkZbwtc ← did:key (resolves locally)
```

## Trust model

Cryptographically, a `did:key`-signed credential proves *the holder of this private key signed this credential*. That part is identical to `did:web` — same crypto, same guarantees.

What `did:key` does **not** give you on its own is *who* in the real world holds that private key. With `did:web`, the domain answers that question (you trust whoever controls `trustvc.github.io`). With `did:key`, the DID is just a number and you need to bind it to a real-world entity through one of:

1. **Trust registry / allow-list** — maintain a list of approved `did:key` DIDs out-of-band. The verifier checks the credential's issuer against the registry.
2. **Chained credentials (delegation)** — a known authority (often a `did:web` issuer) issues a credential **to** a `did:key` saying "this key is authorised to act on behalf of X". The verifier walks the chain.
3. **Sidechannel exchange** — exchange the `did:key` through a trusted channel (business card, contract, signed email, etc.).
4. **Self-contained credentials** — for use cases where the credential proves a property of itself (a hash, a receipt) and issuer identity is not relevant.

If you are using `did:key` in production for regulated contexts (Bills of Lading, KYC, etc.), do **not** rely on the cryptographic identity check alone — layer one of the patterns above in your application code.

## Supported key types

This library supports two `did:key` key types, chosen to match the cryptosuites available in `@trustvc/w3c-vc`:

| Multibase prefix | Key type | Multicodec | Cryptosuite |
|---|---|---|---|
| `zDna...` | P-256 (NIST secp256r1, compressed) | `0x1200` | `ecdsa-sd-2023` |
| `zUC7...` | BLS12-381 G2 | `0xeb` | `bbs-2023` |

Other `did:key` types defined in the method spec (Ed25519 `z6Mk...`, secp256k1 `zQ3s...`, P-384, P-521, RSA, etc.) are rejected at parse time with a clear error, because no matching cryptosuite is wired up in `@trustvc/w3c-vc`.

## Step-by-Step Usage

### 1. Generate a `did:key` Key Pair

```ts
import { CryptoSuite, generateDidKeyPair } from '@trustvc/w3c-issuer';

const { did, didKeyPairs } = await generateDidKeyPair(CryptoSuite.EcdsaSd2023);
// did is: 'did:key:zDna...'
// didKeyPairs is a Multikey private key pair (id/controller/publicKeyMultibase/secretKeyMultibase)
```

Keep `didKeyPairs.secretKeyMultibase` private — it is what allows you to sign as this DID. The other fields (`did`, `publicKeyMultibase`) are safe to publish.

For BBS-2023 you may pass an optional `seedBase58` if you want deterministic generation:

```ts
const { did, didKeyPairs } = await generateDidKeyPair(CryptoSuite.Bbs2023, {
seedBase58: 'FVj12jBiBUqYFaEUkTuwAD73p9Hx5NzCJBge74nTguQN',
});
```

ECDSA-SD-2023 does not support seeded generation.

### 2. Sign a Credential

The signing call is identical to `did:web` — pass the key pair to `signCredential` from `@trustvc/w3c-vc`. The resulting proof's `verificationMethod` references the canonical `did:key:zXxx#zXxx` form:

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

const credential = {
'@context': ['https://www.w3.org/ns/credentials/v2', 'https://w3id.org/security/data-integrity/v2'],
type: ['VerifiableCredential'],
issuer: did, // the did:key from above
validFrom: '2024-04-01T12:19:52Z',
credentialSubject: { name: 'TrustVC Demo' },
};

const { signed } = await signCredential(credential, didKeyPairs, 'ecdsa-sd-2023');
```

### 3. Verify a Credential

`verifyCredential` works without any extra configuration — the default document loader from `@trustvc/w3c-context` automatically dispatches `did:key:*` resolution through this package's `queryDidDocument`, which synthesises the DID document in memory. No network call is made.

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

// ECDSA-SD-2023 and BBS-2023 are selective-disclosure schemes: derive before verifying.
const { derived } = await deriveCredential(signed, ['/credentialSubject/name']);
const result = await verifyCredential(derived);
// result.verified === true
```

## Anatomy of a `did:key` DID

A `did:key` DID is constructed as:

```text
did:key: + z + base58btc( <multicodec varint> || <public key bytes> )
```

Worked example for the P-256 key `zDnaemDNwi4G5eTzGfRooFFu5Kns3be6yfyVNtiaMhWkZbwtc`:

```text
did:key:zDnaemDNwi4G5eTzGfRooFFu5Kns3be6yfyVNtiaMhWkZbwtc
└── z = base58btc multibase prefix
── decode base58btc → [0x80, 0x24, ...33 bytes pubkey]
└──┬───┘ └──────┬────────┘
varint(0x1200) raw compressed P-256 public key
= "p256-pub"
```

The synthesised DID document has exactly one verification method, whose `id` is `<did>#<multibase>` (the fragment **must** equal the multibase identifier per the did:key spec):

```json
{
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/multikey/v1"
],
"id": "did:key:zDnaemDNwi4G5eTzGfRooFFu5Kns3be6yfyVNtiaMhWkZbwtc",
"verificationMethod": [{
"id": "did:key:zDnaemDNwi4G5eTzGfRooFFu5Kns3be6yfyVNtiaMhWkZbwtc#zDnaemDNwi4G5eTzGfRooFFu5Kns3be6yfyVNtiaMhWkZbwtc",
"type": "Multikey",
"controller": "did:key:zDnaemDNwi4G5eTzGfRooFFu5Kns3be6yfyVNtiaMhWkZbwtc",
"publicKeyMultibase": "zDnaemDNwi4G5eTzGfRooFFu5Kns3be6yfyVNtiaMhWkZbwtc"
}],
"authentication": ["did:key:zDnae...#zDnae..."],
"assertionMethod": ["did:key:zDnae...#zDnae..."],
"capabilityInvocation": ["did:key:zDnae...#zDnae..."],
"capabilityDelegation": ["did:key:zDnae...#zDnae..."]
}
```

## Specifications

- [DID Core 1.0](https://www.w3.org/TR/did-core/) — DID and verification method data model.
- [The `did:key` Method v0.9](https://w3c-ccg.github.io/did-key-spec/) — encoding rules and canonical fragment form.
- [VC Data Integrity 1.0](https://www.w3.org/TR/vc-data-integrity/) — `DataIntegrityProof` and `verificationMethod` requirements.
- [W3C Data Integrity ECDSA Cryptosuites](https://www.w3.org/TR/vc-di-ecdsa/) — `ecdsa-sd-2023`, P-256 binding.
- [W3C Data Integrity BBS Cryptosuites](https://www.w3.org/TR/vc-di-bbs/) — `bbs-2023`, BLS12-381 G2 binding.
- [Multicodec table](https://github.com/multiformats/multicodec/blob/master/table.csv) — multicodec code points.
3 changes: 3 additions & 0 deletions packages/w3c-issuer/src/did-key/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './types';
export * from './parse';
export * from './keyPair';
30 changes: 30 additions & 0 deletions packages/w3c-issuer/src/did-key/keyPair.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { describe, expect, it } from 'vitest';
import { CryptoSuite, VerificationType } from '../lib/types';
import { generateDidKeyPair } from './keyPair';
import { parseDidKey } from './parse';

describe('generateDidKeyPair', () => {
it('generates a P-256 did:key for ECDSA-SD-2023', async () => {
const result = await generateDidKeyPair(CryptoSuite.EcdsaSd2023);
expect(result.did.startsWith('did:key:z')).toBe(true);
expect(result.didKeyPairs.type).toBe(VerificationType.Multikey);
expect(result.didKeyPairs.controller).toBe(result.did);
expect(result.didKeyPairs.id).toBe(`${result.did}#${result.didKeyPairs.publicKeyMultibase}`);
expect(result.didKeyPairs.publicKeyMultibase).toBeDefined();
expect(result.didKeyPairs.secretKeyMultibase).toBeDefined();

// The generated DID must round-trip through the parser.
const parsed = parseDidKey(result.did);
expect(parsed.keyType).toBe('P-256');
});

it('generates a BLS12-381 G2 did:key for BBS-2023', async () => {
const result = await generateDidKeyPair(CryptoSuite.Bbs2023);
expect(result.did.startsWith('did:key:z')).toBe(true);
expect(result.didKeyPairs.type).toBe(VerificationType.Multikey);
expect(result.didKeyPairs.controller).toBe(result.did);

const parsed = parseDidKey(result.did);
expect(parsed.keyType).toBe('Bls12381G2');
});
});
Loading
Loading