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
154 changes: 154 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# CLAUDE.md

Guidance for working in this repo — for human developers and for Claude Code.

> **Keep this file alive.** It's only useful if it stays true. Treat it as part of the
> code: when a change makes something here wrong or incomplete, update it *in the same
> commit/PR*. See [Maintaining this file](#maintaining-this-file).

## What this repo is

`@trustvc/trustvc` is the **umbrella SDK** that ties together document issuance,
signing and verification across **two worlds**:

- **OpenAttestation / OpenCert** — hash-based documents, token registry / document
store, on-chain transferable records (ethers + hardhat).
- **W3C Verifiable Credentials / Presentations** — Data Integrity proofs, selective
disclosure, DID-based issuers/holders.

It depends on the **published** `@trustvc/w3c*` packages (the crypto core lives in the
separate `w3c` monorepo — see [w3c section](#relationship-to-the-w3c-monorepo)):

```
@trustvc/w3c @trustvc/w3c-vc @trustvc/w3c-context
@trustvc/w3c-credential-status @trustvc/w3c-issuer → all pinned ^2.3.0
```

Source map (`src/`):

| Path | What |
| --- | --- |
| `src/core/verify.ts` | **`verifyDocument()`** — the unified verify entry point (OA + W3C). |
| `src/verify/verify.ts` | `verificationBuilder`, `openAttestationVerifiers`, `w3cVerifiers`. |
| `src/verify/fragments/` | Verifier fragments by dimension: `document-integrity`, `document-status`, `issuer-identity`, `presentation`. |
| `src/w3c/` | The W3C surface: `sign`, `derive`, `verify`, **`presentation`** (VP wrappers), `types`. |

## Commands

Node **≥ 20** — `engines` is enforced. Use `nvm use 20`; on Node 18 the install/tests fail.

```bash
npm test # vitest --run --test-timeout=15000
npm run type-check # tsc --noEmit
npm run lint # eslint, --max-warnings=0 (CI fails on ANY warning)
npm run build # clean + tsup
npm run test:e2e # hardhat node + on-chain tests (token registry / document store)

# One file / one test:
npx vitest --run src/__tests__/w3c/presentation.test.ts
npx vitest --run <file> -t "does not match the holder"
```

**Before "done": run `npm run type-check` AND `npm run lint`.** `lint` is
`--max-warnings=0`; a single warning is a red build.

**Tests hit the network.** did:web resolution and StatusList fetches go to
`trustvc.github.io` (e.g. `.../did/1`, `.../credentials/statuslist/1`). These are real
integration checks — don't mock them away. On-chain tests need the hardhat node
(`test:e2e`).

## Verifiable Presentations (`src/w3c/presentation.ts`)

The trustvc layer is **opinionated**: it wraps the raw `@trustvc/w3c-vc` primitives and
**enforces policies so callers can't disable them.**

**`signW3CPresentation(credentials, keyPair, options)`** — create **and** sign in one
call. Enforced:
- **`fullDisclosure`** — a base (non-derived) SD credential is **auto-full-disclosed**;
callers may pass underived credentials.
- **`checkHolderBinding`** — signing-key DID **==** holder **==** every
`credentialSubject.id`. The **issuer is deliberately NOT part of this** — a credential
issued by a different party (e.g. a did:web issuer) is fine.
- **Mandatory lifetime** — the caller MUST pass `expiresInSeconds` or `validUntil`.
- **`version: 'v2'`** — the presentation **envelope is always VC Data Model v2.0**
(`validFrom`/`validUntil`); `version` is dropped from the caller options. Embedded
credentials keep their own version (a v1.1 VC can sit inside a v2 envelope).
- Suite is `ecdsa-rdfc-2019` (a non-ECDSA key → error). `challenge` → `authentication`
proof; no challenge → `assertionMethod`.

**`verifyW3CPresentation(presentation, options)`** — enforces **`checkHolderBinding`**;
verifies each embedded credential (signature **+ expiry + revocation**) and the holder
proof. An unsigned VP **fails** here.

**When you add or change a policy, change it in the wrapper — not by trusting callers —
and keep create/verify symmetric** (if create rejects something, verify must too).

## VP verification fragments (`src/verify/fragments/presentation/`)

Three aggregate verifiers plug into `verifyDocument()`'s pipeline via `w3cVerifiers`:

- `w3cVpSignatureIntegrity` (DOCUMENT_INTEGRITY) — **requires a holder proof** (unsigned
→ INVALID), verifies the proof crypto, and enforces **holder binding** in-fragment.
Freshness (challenge/domain) is intentionally out of scope — a stateless pipeline
can't check it.
- `w3cVpCredentialStatus` (DOCUMENT_STATUS) — VP expiry + each embedded credential's
StatusList revocation.
- `w3cVpIssuerIdentity` (ISSUER_IDENTITY) — each embedded issuer resolves.

`isVpDocument()` (the `test()` gate) routes on shape only (`type` includes
`VerifiablePresentation` + has `verifiableCredential`) — it does **not** look at `proof`,
so an unsigned VP is still routed in and then judged INVALID by the integrity fragment.

**Consistency note:** the fragment pipeline and `verifyW3CPresentation` were deliberately
aligned to both enforce proof-presence + holder binding. If you touch one, keep the other
in step.

## Gotchas (hard-won — add to this list)

- **Selective disclosure keeps the subject `id`.** If a credential was issued *with* a
`credentialSubject.id`, deriving it (even revealing only other fields) **retains that
id**. To test/produce a credential with *no* subject id, it must be issued without one.
- **Holder binding is string-equality of DIDs and is method-agnostic** (did:key and
did:web both work). It's independent of the issuer.
- **StatusList test indices** on `.../statuslist/1`: index **5 → revoked**, index
**10 → not revoked**. Reuse these instead of inventing new ones.
- **Test fixtures share key material** across did:key and did:web (the same ECDSA key is
published under `did:key:zDnae…` and `did:web:trustvc.github.io:did:1#multikey-1`). Handy
for tests, but it means "different DID" ≠ "different key" in fixtures.
- **`VerificationFragment` is a union** — `reason`/`data` aren't on every member; narrow or
cast when asserting on them in tests.

## Relationship to the w3c monorepo

The VP/VC crypto logic lives in `@trustvc/w3c-vc` (separate repo, `../w3c-10`). To test an
**unpublished** w3c-vc change here, `npm pack` it there and install the tarball; once
published, repoint the dep to the version (`^2.3.0`). Real changes should be validated
here because this repo resolves the packages' full dep tree (and stricter jsonld) — a
green w3c-vc build alone doesn't prove integration.

## Conventions

- **No `!` non-null assertions** in tests — use the `assertDefined` helper.
- Prefer `as never` at test boundaries for intentionally loose fixture typing; avoid `any`.
- Conventional commits (commitlint + semantic-release drive versioning/CHANGELOG).
- Match the surrounding file's style; keep comments explaining *why* for the subtle rules
above.

## Maintaining this file

**Documentation-as-code. Keep it in sync in the same change that makes it stale — not
"later".** Update this file when your change touches:

- **A public export or its behavior** (a new/renamed function, changed signature).
- **A VP policy or invariant** — the enforced flags, holder binding, v2 lock, create/verify
symmetry, the fragment pipeline's proof-presence check.
- **Commands, tooling, or Node/engine requirements** — keep the Commands section runnable.
- **A gotcha you just spent time on** — new gotchas are the highest-value additions.

Guidelines: small-and-true beats big-and-stale (delete guidance that no longer holds);
keep it repo-specific and actionable; link to source-of-truth over duplicating detail;
preserve the *why* on load-bearing rules.

**For Claude Code specifically:** at the end of a task that changed any of the above,
check whether this file is now inaccurate and propose the edit as part of the same
work — don't wait to be asked.
75 changes: 61 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,18 @@ TrustVC is a comprehensive wrapper library designed to simplify the signing and
- [a) OpenAttestation Signing (signOA) v2](#a-openattestation-signing-signoa-v2)
- [b) TrustVC W3C Signing (signW3C)](#b-trustvc-w3c-signing-signw3c)
- [3. **Deriving (Selective Disclosure)**](#3-deriving-selective-disclosure)
- [4. **Verifying**](#4-verifying)
- [5. **Encryption**](#5-encryption)
- [6. **Decryption**](#6-decryption)
- [7. **TradeTrust Token Registry**](#7-tradetrust-token-registry)
- [4. **Verifiable Presentations (VP)**](#4-verifiable-presentations-vp)
- [5. **Verifying**](#5-verifying)
- [6. **Encryption**](#6-encryption)
- [7. **Decryption**](#7-decryption)
- [8. **TradeTrust Token Registry**](#8-tradetrust-token-registry)
- [Usage](#usage-2)
- [TradeTrustToken](#tradetrusttoken)
- [a) Token Registry v4](#a-token-registry-v4)
- [b) Token Registry V5](#b-token-registry-v5)
- [8. **Document Builder**](#8-document-builder)
- [9. **Document Store**](#9-document-store)
- [10. **Transaction Cancel**](#10-transaction-cancel)
- [9. **Document Builder**](#9-document-builder)
- [10. **Document Store**](#10-document-store)
- [11. **Transaction Cancel**](#11-transaction-cancel)

## Installation

Expand Down Expand Up @@ -342,7 +343,53 @@ const derivationResult = await deriveW3C(signedDocument, {

---

### 4. **Verifying**
### 4. **Verifiable Presentations (VP)**

> A Verifiable Presentation lets a **holder** bundle one or more of their credentials and cryptographically prove they own them. TrustVC exposes an opinionated, single-call API. `signW3CPresentation` **creates and signs** a VP in one step, with these policies **ENFORCED** (callers cannot disable them):
>
> - **Full disclosure** — an underived selective-disclosure credential is auto-derived; you can pass credentials as-is.
> - **Holder binding** — the signing key's DID must equal the presentation `holder` and every `credentialSubject.id`. (The **issuer is independent** — a credential issued by a different party is fine.)
> - **Mandatory lifetime** — you must pass `expiresInSeconds` or an explicit `validUntil`.
> - **v2 envelope** — the presentation envelope is always VC Data Model v2.0 (embedded credentials keep their own version).
>
> Credentials carrying a `TransferableRecords` status cannot be presented (they are controlled on-chain). The holder proof uses the `ecdsa-rdfc-2019` cryptosuite and reuses the holder's ECDSA (P-256) Multikey. A `challenge` produces an `authentication` proof (anti-replay); omitting it produces an `assertionMethod` proof.

Only the **lifetime** is required. `challenge` and `domain` are **optional**: pass a `challenge` for anti-replay (authentication proof); omit it for a plain `assertionMethod` proof. `domain` may only be used together with a `challenge`.

```ts
import { signW3CPresentation, verifyW3CPresentation } from '@trustvc/trustvc';

// `derivedCredential` is a signed (and, for SD suites, derived) W3C VC whose
// credentialSubject.id is the holder. `holderKeyPair` is the holder's ECDSA Multikey.

// Minimal — no challenge/domain → an assertionMethod proof (only lifetime is required):
const { signed } = await signW3CPresentation(derivedCredential, holderKeyPair, {
holder: 'did:key:zDnae...',
expiresInSeconds: 600, // REQUIRED (or `validUntil`)
});
const result = await verifyW3CPresentation(signed); // no challenge needed to verify it

// With anti-replay — pass a challenge (→ authentication proof); domain is optional:
const { signed: authVp } = await signW3CPresentation(derivedCredential, holderKeyPair, {
holder: 'did:key:zDnae...',
expiresInSeconds: 600,
challenge: 'nonce-issued-by-the-verifier', // optional → authentication proof (anti-replay)
domain: 'verifier.example.com', // optional; only valid with a challenge
});
// Verify: holder proof + holder binding + VP expiry + every embedded credential.
// For an authentication proof, pass the SAME challenge the verifier issued.
const authResult = await verifyW3CPresentation(authVp, {
challenge: 'nonce-issued-by-the-verifier',
domain: 'verifier.example.com',
});
// result.verified, result.presentationResult, result.credentialResults
```

> A presentation can also flow through the unified [`verifyDocument`](#5-verifying) pipeline, which emits VP fragments: `W3CVpSignatureIntegrity` (holder proof + holder binding — an unsigned VP is INVALID), `W3CVpCredentialStatus` (VP expiry + embedded revocation) and `W3CVpIssuerIdentity` (embedded issuers resolve).

---

### 5. **Verifying**

> TrustVC simplifies the verification process with a single function that supports W3C Verifiable Credentials (VCs) and OpenAttestation Verifiable Documents (VDs), including OpenCert Verifiable Documents. 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.

Expand Down Expand Up @@ -387,7 +434,7 @@ const resultFragments = await verifyDocument(signedDocument);

---

### 5. **Encryption**
### 6. **Encryption**

> The `encrypt` function encrypts plaintext messages using the **ChaCha20** encryption algorithm, ensuring the security and integrity of the input data. It supports custom keys and nonces, returning the encrypted message in hexadecimal format.

Expand Down Expand Up @@ -464,7 +511,7 @@ It also relies on the `ts-chacha20` library for encryption operations.

---

### 6. **Decryption**
### 7. **Decryption**

> The `decrypt` function decrypts messages encrypted with the **ChaCha20** algorithm. It converts the input from a hexadecimal format back into plaintext using the provided key and nonce.

Expand Down Expand Up @@ -547,7 +594,7 @@ It also relies on the `ts-chacha20` library for decryption operations.

---

### 7. **TradeTrust Token Registry**
### 8. **TradeTrust Token Registry**

> The Electronic Bill of Lading (eBL) is a digital document that can be used to prove the ownership of goods. It is a standardized document that is accepted by all major shipping lines and customs authorities. The [Token Registry](https://github.com/TradeTrust/token-registry) repository contains both the smart contract (v4 and v5) code for token registry (in `/contracts`) as well as the node package for using this library (in `/src`).
> The TrustVC library not only simplifies signing and verification but also imports and integrates existing TradeTrust libraries and smart contracts for token registry (V4 and V5), making it a versatile tool for decentralized identity and trust solutions.
Expand Down Expand Up @@ -737,7 +784,7 @@ function rejectTransferOwners(bytes calldata _remark) external;

For more information on Token Registry and Title Escrow contracts **version v5**, please visit the readme of [TradeTrust Token Registry V5](https://github.com/TradeTrust/token-registry/blob/master/README.md)

### 8. **Document Builder**
### 9. **Document Builder**
> The `DocumentBuilder` class helps build and manage W3C Verifiable Credentials (VCs) with credential status features, implementing the **W3C VC Data Model 2.0** specification. It supports creating documents with two types of credential statuses: `transferableRecords` and `verifiableDocument`. It can sign the document using modern cryptographic signature schemes including **ECDSA-SD-2023** (default) and **BBS-2023**, verify its signature, and serialize the document to a JSON format. Additionally, it allows for configuration of document rendering methods and expiration dates.

#### Usage
Expand Down Expand Up @@ -935,7 +982,7 @@ const documentJson = builder.toString();
console.log(documentJson);
```

## 9. Document Store
## 10. Document Store

> TrustVC provides comprehensive Document Store functionality for managing blockchain-based document storage and verification. The Document Store module supports both standard DocumentStore and TransferableDocumentStore contracts, enabling secure document issuance, revocation, and role management on various blockchain networks.

Expand Down Expand Up @@ -1153,7 +1200,7 @@ for (const hash of documentHashes) {

---

## 10. Transaction Cancel
## 11. Transaction Cancel

TrustVC provides a utility to cancel a pending Ethereum transaction by replacing it with a 0-value transaction to the same address, using the same nonce and a higher gas price (replace-by-fee). This works with both ethers v5 and v6 signers.

Expand Down
Loading
Loading