13 Reference Implementations
Version: 0.1 (Draft) Date: 1 April 2026 Author: Ed Molyneux Status: Draft Parent: 00 — Architecture Overview
Table of Contents
Section titled “Table of Contents”- Purpose
- Repository Structure
- VC Validator (
@pdtf/vc-validator) - DID Resolver (
@pdtf/did-resolver) - Credential Builder (
@pdtf/vc-builder) - Graph Composer (
@pdtf/schemas) - Testing Strategy
- Package Publishing
- Security Considerations
- Open Questions
- Implementation Notes
1. Purpose
Section titled “1. Purpose”The PDTF 2.0 specification suite defines a trust architecture built on W3C Verifiable Credentials, Decentralised Identifiers, and a federated Trusted Issuer Registry. Specifications alone are insufficient — implementers need working code that demonstrates correct behaviour, provides ready-to-use libraries, and serves as the canonical interpretation of the spec when ambiguity arises.
The reference implementations serve four goals:
- Proof of correctness. Every sub-spec is validated by running code. If a design cannot be implemented cleanly, the spec is wrong — not the code.
- Implementer acceleration. LMS providers, conveyancing platforms, and data aggregators can integrate
@pdtfpackages directly rather than building from scratch. - Interoperability baseline. The test vectors and validation suites define the canonical behaviour all implementations must match.
- Living documentation. The TypeScript types serve as machine-readable documentation of the data model, complementing the prose specifications.
All reference implementations are open-source under the Apache 2.0 licence, published to npm under the @pdtf scope, and maintained within the property-data-standards-co GitHub organisation.
2. Repository Structure
Section titled “2. Repository Structure”All repositories live under the property-data-standards-co GitHub organisation.
| Repository | npm Package | Description |
|---|---|---|
pdtf-vc-validator | @pdtf/vc-validator | Credential validation pipeline |
pdtf-did-resolver | @pdtf/did-resolver | DID resolution for did:key and did:web |
pdtf-vc-builder | @pdtf/vc-builder | Credential construction and signing |
pdtf-schemas | @pdtf/schemas | JSON schemas, entity types, and Graph Composer |
2.1 Dependency Graph
Section titled “2.1 Dependency Graph”Each package occupies its own repository. This keeps dependency trees minimal, allows independent versioning, and means consumers install only what they need. The pdtf-schemas repo bundles the Graph Composer alongside the JSON schemas because composition logic is tightly coupled to the schema definitions.
@pdtf/vc-validator ├── @pdtf/did-resolver (peer dependency) └── @pdtf/schemas (peer dependency — for type definitions)
@pdtf/vc-builder ├── @pdtf/did-resolver (optional — for DID document verification) └── @pdtf/schemas (peer dependency — for credential type constants)
@pdtf/schemas (no @pdtf dependencies — leaf package)@pdtf/did-resolver (no @pdtf dependencies — leaf package)2.2 Common Repository Layout
Section titled “2.2 Common Repository Layout”pdtf-vc-validator/├── src/│ ├── index.ts # Public API exports│ ├── types.ts # TypeScript interfaces│ ├── validator.ts # Core implementation│ └── errors.ts # Error types├── test/│ ├── vectors/ # Test vector fixtures (JSON)│ ├── unit/│ └── integration/├── package.json├── tsconfig.json├── README.md├── CHANGELOG.md└── LICENSE # Apache 2.03. VC Validator (@pdtf/vc-validator)
Section titled “3. VC Validator (@pdtf/vc-validator)”The VC Validator takes a Verifiable Credential document and returns a comprehensive validation result covering structure, cryptographic integrity, issuer trust, and revocation status.
3.1 Validation Pipeline
Section titled “3.1 Validation Pipeline”The validator executes a sequential pipeline. Each stage can short-circuit on fatal errors:
1. Structure Validation → 2. Resolve Issuer DID → 3. Verify Signature → 4. Federation Lookup → 5. Expiry Check → 6. Revocation Check → ResultStage 1 — Structure Validation. Validates the VC JSON against W3C VC Data Model 2.0 structure and PDTF-specific requirements: @context includes the PDTF context URL, type includes a recognised PDTF credential type, credentialSubject conforms to the entity schema, and proof is present with a supported cryptosuite.
Stage 2 — Issuer DID Resolution. Resolves the issuer field using the configured @pdtf/did-resolver instance. Extracts the verification method matching the proof’s verificationMethod reference.
Stage 3 — Signature Verification. Canonicalises the credential using JCS (RFC 8785), then verifies the eddsa-jcs-2022 proof against the resolved public key.
Stage 4 — Federation Lookup. Resolves the issuer’s trust chain via OpenID Federation to the PDTF Trust Anchor, verifies the pdtf-verified-issuer Trust Mark, and confirms its delegation.authorised_paths covers the specific entity:path combination claimed by the credential. When verifying a credential issued by or about an Organisation with a did:key identifier, the validator MUST consult the relevant account-provider Trust Mark’s delegation.managed_organisations document to confirm the did:key is managed by a trusted provider.
Stage 5 — Expiry Check. Validates validFrom ≤ now ≤ validUntil (if present). Configurable clock skew tolerance (default: 60 seconds).
Stage 6 — Revocation Check. If the credential contains a credentialStatus of type BitstringStatusListEntry, fetches the referenced status list credential, validates it, and checks the specific bit index.
3.2 TypeScript Interfaces
Section titled “3.2 TypeScript Interfaces”interface ValidationResult { /** Overall validity — true only if all checks pass. */ valid: boolean; /** Trust level derived from Trust Mark delegation claim. */ trustLevel: TrustLevel; /** Resolved issuer information. */ issuer: ResolvedIssuer | null; /** Whether the credential has been revoked. */ revoked: boolean; /** Ordered list of errors encountered during validation. */ errors: ValidationError[]; /** Per-stage timing and status metadata. */ stages: StageResult[];}
type TrustLevel = | 'root' // Primary source issuer (e.g., HMLR for title data) | 'delegated' // Issuer with explicit delegation from root | 'proxy' // Trusted proxy (e.g., a platform aggregating data) | 'self-asserted' // Seller/owner self-declaration | 'unknown'; // Issuer not found in federation
interface ResolvedIssuer { did: string; name?: string; verificationMethodId: string; authorisedPaths: string[];}
interface ValidationError { code: ValidationErrorCode; message: string; stage: ValidationStage; fatal: boolean;}
type ValidationStage = | 'structure' | 'did-resolution' | 'signature' | 'tir-lookup' | 'expiry' | 'revocation';
interface StageResult { stage: ValidationStage; status: 'pass' | 'fail' | 'skip' | 'warn'; durationMs: number; errors: ValidationError[];}3.3 Error Taxonomy
Section titled “3.3 Error Taxonomy”type ValidationErrorCode = // Structure errors | 'INVALID_JSON' | 'MISSING_CONTEXT' | 'MISSING_TYPE' | 'UNKNOWN_CREDENTIAL_TYPE' | 'INVALID_SUBJECT' | 'MISSING_PROOF' | 'UNSUPPORTED_CRYPTOSUITE' // DID resolution errors | 'ISSUER_DID_NOT_FOUND' | 'ISSUER_DID_INVALID' | 'DID_RESOLUTION_NETWORK' | 'VERIFICATION_METHOD_MISSING' // Signature errors | 'INVALID_SIGNATURE' | 'CANONICALIZATION_ERROR' // Federation errors | 'ISSUER_NOT_IN_FEDERATION' | 'UNTRUSTED_PATH' | 'FEDERATION_NETWORK_ERROR' | 'ORG_DID_KEY_NOT_IN_MANAGED_ORGS' // Temporal errors | 'CREDENTIAL_NOT_YET_VALID' | 'CREDENTIAL_EXPIRED' // Revocation errors | 'CREDENTIAL_REVOKED' | 'STATUS_LIST_FETCH_ERROR' | 'STATUS_LIST_INVALID';3.4 Configuration
Section titled “3.4 Configuration”interface ValidatorConfig { /** URL of the Trusted Issuer Registry API. */ trustAnchor: string; /** DID resolver instance. If not provided, a default resolver is created. */ didResolver?: DIDResolver; /** Cache TTL for federation metadata in milliseconds. Default: 300_000 (5 min). */ federationCacheTtlMs?: number; /** Cache TTL for status list fetches in milliseconds. Default: 60_000 (1 min). */ statusListCacheTtlMs?: number; /** Clock skew tolerance in seconds for expiry checks. Default: 60. */ clockSkewSeconds?: number; /** Custom fetch implementation (for testing or custom transports). */ fetch?: typeof globalThis.fetch; /** Stages to skip. Use with caution — primarily for testing. */ skipStages?: ValidationStage[];}3.5 Usage
Section titled “3.5 Usage”import { createValidator } from '@pdtf/vc-validator';import { createResolver } from '@pdtf/did-resolver';
const validator = createValidator({ trustAnchor: 'https://trust.pdtf.org', didResolver: createResolver({ cacheTtlMs: 600_000 }),});
// Single credentialconst result: ValidationResult = await validator.validate(credentialJson);
if (result.valid) { console.log(`Valid credential from ${result.issuer?.name}`); console.log(`Trust level: ${result.trustLevel}`);} else { for (const error of result.errors) { console.error(`[${error.stage}] ${error.code}: ${error.message}`); }}
// Batch validationconst results = await validator.validateBatch(credentials, { concurrency: 5 });
// Single-stage validation (e.g., structure-only during ingestion)const structureResult = await validator.validateStructure(credentialJson);4. DID Resolver (@pdtf/did-resolver)
Section titled “4. DID Resolver (@pdtf/did-resolver)”Implements W3C DID Core §7 (DID Resolution) for the two DID methods used in PDTF 2.0: did:key (ephemeral and test identities) and did:web (organisational identities).
4.1 Supported Methods
Section titled “4.1 Supported Methods”did:key — Deterministic resolution, no network calls:
- Parse multibase prefix (
z= base58btc) and multicodec prefix (0xed01= Ed25519) - Extract the 32-byte Ed25519 public key
- Construct DID Document with a single
Ed25519VerificationKey2020verification method
did:web — Network-based resolution with TLS validation:
- Parse DID string:
did:web:<domain>[:path]* - Convert to URL:
https://<domain>[/path]*/.well-known/did.json - Fetch via HTTPS (TLS required — no HTTP fallback)
- Validate the
idfield matches the DID being resolved - Cache with configurable TTL
4.2 TypeScript Interfaces
Section titled “4.2 TypeScript Interfaces”/** W3C DID Resolution Result (DID Core §7.1). */interface DIDResolutionResult { didDocument: DIDDocument | null; didResolutionMetadata: DIDResolutionMetadata; didDocumentMetadata: DIDDocumentMetadata;}
interface DIDDocument { '@context': string | string[]; id: string; controller?: string | string[]; verificationMethod?: VerificationMethod[]; authentication?: (string | VerificationMethod)[]; assertionMethod?: (string | VerificationMethod)[]; service?: ServiceEndpoint[];}
interface VerificationMethod { id: string; type: string; controller: string; publicKeyMultibase?: string; publicKeyJwk?: JsonWebKey;}
interface DIDResolutionMetadata { contentType?: string; error?: 'notFound' | 'invalidDid' | 'representationNotSupported' | 'methodNotSupported' | 'networkError' | 'tlsError' | 'documentIdMismatch'; duration?: number; cached?: boolean;}
interface DIDDocumentMetadata { created?: string; updated?: string; deactivated?: boolean; versionId?: string;}4.3 Configuration and API
Section titled “4.3 Configuration and API”interface ResolverConfig { cacheTtlMs?: number; // Default: 300_000 (5 min) cacheMaxEntries?: number; // Default: 1000 (LRU eviction) fetchTimeoutMs?: number; // Default: 10_000 fetch?: typeof globalThis.fetch; allowInsecure?: boolean; // Testing only. Default: false methods?: Record<string, DIDMethodHandler>; // Extensible}
interface DIDMethodHandler { resolve(did: string): Promise<DIDResolutionResult>;}import { createResolver } from '@pdtf/did-resolver';
const resolver = createResolver({ cacheTtlMs: 600_000 });
// Resolve did:key (deterministic, instant)const keyResult = await resolver.resolve( 'did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK');
// Resolve did:web (network fetch, cached)const webResult = await resolver.resolve('did:web:hmlr.gov.uk');
// Cache managementresolver.invalidate('did:web:hmlr.gov.uk');resolver.clearCache();4.4 Caching Strategy
Section titled “4.4 Caching Strategy”did:key: Cached indefinitely (deterministic resolution). Only evicted by LRU pressure.did:web: Cached for the configured TTL. No stale-while-revalidate — a staledid:webdocument could reference rotated keys, leading to false validation results.- Error results are NOT cached — transient network failures should be retried.
4.5 Error Handling
Section titled “4.5 Error Handling”The resolver never throws exceptions. All failure modes are captured in DIDResolutionMetadata.error per W3C DID Core §7.1:
const result = await resolver.resolve('did:key:invalidMultibase');// result.didResolutionMetadata.error === 'invalidDid'// result.didDocument === null5. Credential Builder (@pdtf/vc-builder)
Section titled “5. Credential Builder (@pdtf/vc-builder)”Constructs W3C Verifiable Credentials conforming to the PDTF 2.0 data model, signs them using configurable signers, and outputs complete credentials.
5.1 Signer Interface
Section titled “5.1 Signer Interface”The builder separates credential construction from signing via a Signer interface:
interface Signer { /** Sign arbitrary data and return the signature bytes. */ sign(data: Uint8Array): Promise<Uint8Array>; /** Key ID used in the proof's verificationMethod field. */ keyId: string; /** Signing algorithm identifier. */ algorithm: string;}5.2 Signer Implementations
Section titled “5.2 Signer Implementations”Local Signer (Testing):
import { createLocalSigner } from '@pdtf/vc-builder';
// Generate a new Ed25519 key pairconst signer = await createLocalSigner();
// Or provide an existing private keyconst signer = createLocalSigner({ privateKey: existingEd25519PrivateKey, did: 'did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK',});Google Cloud KMS Signer:
import { createGcpKmsSigner } from '@pdtf/vc-builder/kms/gcp';
const signer = createGcpKmsSigner({ projectId: 'pdtf-platform-prod', locationId: 'europe-west2', keyRingId: 'pdtf-signing', keyId: 'property-credentials', keyVersion: '1', did: 'did:web:platform.example.com', verificationMethodFragment: '#key-1',});The GCP KMS signer calls asymmetricSign on the Cloud KMS API, requires the cloudkms.cryptoKeyVersions.useToSign IAM permission, and supports the EC_SIGN_ED25519 key type.
5.3 Build API
Section titled “5.3 Build API”interface BuildCredentialOptions { type: string | string[]; issuer: string; subject: string; claims: Record<string, unknown>; evidence?: Evidence[]; termsOfUse?: TermsOfUse[]; status?: CredentialStatusConfig; validFrom?: string; // Defaults to now validUntil?: string; // If omitted, no expiry id?: string; // If omitted, UUID URN generated}
interface CredentialStatusConfig { statusListCredential: string; statusListIndex: number; statusPurpose: 'revocation' | 'suspension';}import { createBuilder, createLocalSigner } from '@pdtf/vc-builder';
const signer = await createLocalSigner();const builder = createBuilder({ signer, defaultContext: [ 'https://www.w3.org/ns/credentials/v2', 'https://purl.org/pdtf/v2/context', ],});
const credential = await builder.buildCredential({ type: 'PropertyEPCCredential', issuer: signer.keyId.split('#')[0], subject: 'urn:pdtf:property:10001234', claims: { 'property:epc': { rating: 'C', score: 72, validUntil: '2033-06-15', certificateNumber: '0123-4567-8901-2345', }, }, evidence: [{ type: 'DataRetrievalEvidence', source: 'https://epc.opendatacommunities.org/api/v1/...', retrievedAt: '2026-03-24T12:00:00Z', }], status: { statusListCredential: 'https://platform.example.com/.well-known/status/1', statusListIndex: 42, statusPurpose: 'revocation', },});5.4 Signing Process
Section titled “5.4 Signing Process”The builder follows the eddsa-jcs-2022 Data Integrity cryptosuite:
- Assemble the unsigned credential — populate all fields, generate UUID URN if no
idprovided. - Construct proof options —
type: 'DataIntegrityProof',cryptosuite: 'eddsa-jcs-2022',created,verificationMethod,proofPurpose: 'assertionMethod'. - Canonicalise proof options via JCS (RFC 8785), hash with SHA-256 →
proofOptionsHash. - Canonicalise the credential (without proof) via JCS, hash with SHA-256 →
documentHash. - Concatenate:
hashData = proofOptionsHash || documentHash(64 bytes). - Sign
hashDatausing the configuredSigner→ signature bytes. - Encode signature as multibase (base58btc, prefix
z). - Attach the completed proof to the credential and return.
interface DataIntegrityProof { type: 'DataIntegrityProof'; cryptosuite: 'eddsa-jcs-2022'; created: string; verificationMethod: string; proofPurpose: 'assertionMethod'; proofValue: string; // Multibase-encoded signature}6. Graph Composer (@pdtf/schemas)
Section titled “6. Graph Composer (@pdtf/schemas)”The Graph Composer lives within @pdtf/schemas because it is tightly coupled to entity type definitions and JSON schemas. It assembles validated credentials into coherent entity state — either the v4 graph format or backward-compatible v3 flat format.
6.1 TypeScript Interfaces
Section titled “6.1 TypeScript Interfaces”interface ValidatedCredential { credential: VerifiableCredential; validation: ValidationResult;}
/** V4 entity state — the new PDTF 2.0 graph format. */interface V4EntityState { entityType: string; entityId: string; claims: Record<string, unknown>; provenance: Record<string, ClaimProvenance>; children: V4EntityState[];}
interface ClaimProvenance { credentialId: string; issuer: string; trustLevel: TrustLevel; issuedAt: string; conflictResolution?: ConflictResolution;}
interface ConflictResolution { rejectedCredentialId: string; reason: 'higher-trust-level' | 'more-recent' | 'manual-override';}
/** V3 flat state — backward-compatible with PDTF v3 consumers. */interface V3FlatState { [path: string]: { value: unknown; verified: boolean; source: string; updatedAt: string; };}
interface ComposerConfig { conflictStrategy: 'trust-then-recency' | 'recency-only' | 'strict-trust'; minimumTrustLevel?: TrustLevel; includeRevoked?: boolean; dependencyGraph?: DependencyGraph;}6.2 API
Section titled “6.2 API”import { composeV4StateFromGraph, composeV3StateFromGraph } from '@pdtf/schemas';
const v4State = composeV4StateFromGraph(validatedCredentials, { conflictStrategy: 'trust-then-recency', minimumTrustLevel: 'proxy',});
const v3State = composeV3StateFromGraph(validatedCredentials, { conflictStrategy: 'trust-then-recency',});
// Access claims with provenanceconsole.log(v4State.claims['epc']);console.log(v4State.provenance['epc'].trustLevel); // 'root'console.log(v4State.provenance['epc'].issuer); // 'did:web:epc.gov.uk'6.3 Dependency Pruning
Section titled “6.3 Dependency Pruning”The Graph Composer implements dependency pruning to remove entity branches that are incomplete or have broken trust chains:
interface DependencyGraph { dependencies: Record<string, DependencyRule[]>;}
interface DependencyRule { requiredPath: string; type: 'hard' | 'soft'; // Hard = prune if missing; soft = warn only}Algorithm:
- Build the entity tree from validated credentials.
- For each entity, check all
harddependencies. - If a hard dependency is missing or revoked, mark the entity and all its dependants for pruning.
- For
softdependencies, emit a warning but retain the entity. - Remove pruned entities from the final state.
6.4 Conflict Resolution
Section titled “6.4 Conflict Resolution”When multiple credentials assert claims for the same entity:path:
trust-then-recency(default): Compare trust levels (root>delegated>proxy>self-asserted>unknown). If tied, prefer more recently issued credential. Record decision inClaimProvenance.recency-only: Always prefer the most recently issued credential.strict-trust: Compare trust levels only. If tied, emit an error requiring manual resolution.
7. Testing Strategy
Section titled “7. Testing Strategy”7.1 Test Vectors
Section titled “7.1 Test Vectors”Each package ships with test/vectors/ containing known-good and known-bad fixtures:
test/vectors/├── valid/│ ├── property-epc-credential.json│ ├── title-register-credential.json│ ├── seller-capacity-credential.json│ └── multi-entity-graph.json├── invalid/│ ├── expired-credential.json│ ├── revoked-credential.json│ ├── bad-signature.json│ ├── missing-context.json│ ├── unknown-issuer.json│ └── untrusted-path.json└── keys/ ├── test-key-1.json ├── test-key-2.json └── test-did-documents.jsonAll vectors include pre-computed signatures. Private keys are published alongside — they are test-only and must never be used in production.
7.2 Interoperability Tests
Section titled “7.2 Interoperability Tests”- Digital Bazaar
vclibrary — verify credentials built by@pdtf/vc-buildervalidate with@digitalbazaar/vc - SpruceID DIDKit — cross-validate DID resolution results
- W3C VC Test Suite — run the official VC Data Model 2.0 test suite against builder output
7.3 Round-Trip Tests
Section titled “7.3 Round-Trip Tests”The most comprehensive test category — exercises the full pipeline:
describe('round-trip', () => { it('build → validate → compose → verify state', async () => { const signer = await createLocalSigner(); const builder = createBuilder({ signer }); const vc = await builder.buildCredential({ type: 'PropertyEPCCredential', issuer: signer.keyId.split('#')[0], subject: 'urn:pdtf:property:10001234', claims: { 'property:epc': { rating: 'C', score: 72 } }, });
const validator = createValidator({ trustAnchor: 'mock://trust-anchor', skipStages: ['tir-lookup', 'revocation'], }); const result = await validator.validate(vc); expect(result.valid).toBe(true);
const state = composeV4StateFromGraph( [{ credential: vc, validation: result }], { conflictStrategy: 'trust-then-recency' } ); expect(state.claims['property:epc']).toEqual({ rating: 'C', score: 72 }); expect(state.provenance['property:epc'].issuer).toBe( signer.keyId.split('#')[0] ); });});7.4 Fixture Generation
Section titled “7.4 Fixture Generation”# Generate all test vectors with fresh keysnpx @pdtf/vc-builder generate-fixtures --output test/vectors/
# Generate a specific credential typenpx @pdtf/vc-builder generate-fixtures \ --type PropertyEPCCredential \ --output test/vectors/valid/8. Package Publishing
Section titled “8. Package Publishing”8.1 npm Scope and Packages
Section titled “8.1 npm Scope and Packages”| Package | Description |
|---|---|
@pdtf/vc-validator | Credential validation pipeline |
@pdtf/did-resolver | DID resolution (did:key, did:web) |
@pdtf/vc-builder | Credential construction and signing |
@pdtf/schemas | JSON schemas, types, and Graph Composer |
8.2 Versioning
Section titled “8.2 Versioning”Semantic Versioning aligned with spec versions:
| Spec Version | Package Version Series |
|---|---|
| PDTF 2.0 Draft | 0.1.x – 0.9.x |
| PDTF 2.0 RC | 1.0.0-rc.x |
| PDTF 2.0 Final | 1.0.0 |
8.3 Build and Distribution
Section titled “8.3 Build and Distribution”All packages are TypeScript, distributed as ESM + CJS dual format with full .d.ts type declarations:
{ "name": "@pdtf/vc-validator", "version": "0.1.0", "type": "module", "exports": { ".": { "import": "./dist/esm/index.js", "require": "./dist/cjs/index.cjs", "types": "./dist/types/index.d.ts" } }, "files": ["dist/", "LICENSE", "README.md"], "engines": { "node": ">=18.0.0" }, "sideEffects": false}8.4 Dependency Policy
Section titled “8.4 Dependency Policy”Minimal external dependencies to reduce attack surface and enable browser/edge compatibility:
| Dependency | Purpose | Used by |
|---|---|---|
@noble/ed25519 | Ed25519 signatures | validator, builder |
@noble/hashes | SHA-256 hashing | validator, builder |
canonicalize | JCS (RFC 8785) | validator, builder |
multiformats | Multibase/multicodec | did-resolver |
Explicitly excluded: jsonld (too heavy), node-forge (native crypto preferred), framework-specific libraries.
9. Security Considerations
Section titled “9. Security Considerations”9.1 Supply Chain Security
Section titled “9.1 Supply Chain Security”- Lockfiles committed.
package-lock.jsonin version control for all repos. - Signed commits. GPG-signed maintainer commits; branch protection requires signed commits on
main. - Dependency auditing.
npm auditin CI on every PR. Critical/high vulnerabilities block merge. - Provenance attestation. Published packages include npm provenance attestations linking to source commit.
9.2 Key Material
Section titled “9.2 Key Material”- No private keys in packages. Test key fixtures excluded from
filesinpackage.json. - Test keys clearly labelled. All test fixtures include
"purpose": "test-only". - KMS signers never expose keys. The
Signerinterface accepts data and returns signatures — private keys never leave the KMS boundary.
9.3 Network Security
Section titled “9.3 Network Security”- TLS-only for
did:web. No HTTP fallback.allowInsecuregated behind explicit opt-in with warning. - Timeout enforcement. All network operations have configurable timeouts (10s DID resolution, 10s status list fetch, 30s federation resolution).
9.4 Input Validation
Section titled “9.4 Input Validation”- Schema-first. All credential input validated against JSON schemas before cryptographic operations.
- Canonicalization safety. JCS is deterministic and side-effect-free. No prototype pollution vectors.
- No
evalor dynamic code execution in any reference implementation.
10. Open Questions
Section titled “10. Open Questions”| # | Question | Context |
|---|---|---|
| 1 | Publish @pdtf/test-vectors as a separate package? | Allows third-party implementations to run our test suite independently. |
| 2 | Include status list credential building in @pdtf/vc-builder? | Status list credentials are VCs — the builder could create and update them. |
| 3 | DID method extensibility beyond did:key and did:web? | DIDMethodHandler interface allows pluggable methods. Support did:ion, did:pkh? |
| 4 | Streaming/incremental graph composition? | For large graphs, incremental composition may outperform batch. |
| 5 | Browser bundle size budget? | Target: <50KB gzipped per package. |
| 6 | WASM build for non-JS environments? | Ed25519 + JCS could compile to WASM for Go, Python, etc. |
| 7 | @pdtf/vc-presenter for Verifiable Presentations? | VP construction for selective disclosure — defer to future sub-spec? |
11. Implementation Notes
Section titled “11. Implementation Notes”11.1 Implementation Priority
Section titled “11.1 Implementation Priority”Packages should be implemented in dependency order:
@pdtf/did-resolver— no@pdtfdependencies; foundational@pdtf/schemas— defines types used everywhere@pdtf/vc-builder— depends on did-resolver (optional) and schemas@pdtf/vc-validator— depends on did-resolver and schemas; most complex
11.2 CI/CD Pipeline
Section titled “11.2 CI/CD Pipeline”Each repository uses GitHub Actions:
name: CIon: [push, pull_request]jobs: test: runs-on: ubuntu-latest strategy: matrix: node: [18, 20, 22] steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: '${{ matrix.node }}' } - run: npm ci && npm run build && npm test && npm audit --audit-level=high
publish: needs: test if: startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest permissions: { contents: read, id-token: write } steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: 22, registry-url: 'https://registry.npmjs.org' } - run: npm ci && npm run build && npm publish --provenance --access public env: { NODE_AUTH_TOKEN: '${{ secrets.NPM_TOKEN }}' }11.3 API Design Principles
Section titled “11.3 API Design Principles”- Factory functions over classes.
createValidator(),createResolver(),createBuilder()— hides implementation, allows refactoring. - Immutable configuration. Config read at construction; changing behaviour requires a new instance.
- No side effects on import. Importing never triggers network requests, file access, or global state mutation.
- Errors in return values, not exceptions. Following W3C DID Core: resolution errors in metadata, validation errors in result objects. Exceptions reserved for programmer errors.
11.4 Browser Compatibility
Section titled “11.4 Browser Compatibility”All packages target modern browsers (last 2 versions of Chrome, Firefox, Safari, Edge):
- No Node.js built-ins. Use
crypto.subtlevia@noble/ed25519— notnode:crypto. - No
fsorpath. Test fixtures loaded differently per environment. - Standard
fetchAPI. Customfetchinjectable for environments without native support. - Tree-shakeable. Named exports only. No default exports or barrel re-exports.
11.5 Performance Targets
Section titled “11.5 Performance Targets”| Operation | Target | Notes |
|---|---|---|
did:key resolution | < 1ms | Deterministic, no network |
did:web resolution (cached) | < 1ms | LRU cache hit |
did:web resolution (cold) | < 500ms | Network fetch + parse |
| VC structure validation | < 5ms | JSON schema validation |
| VC signature verification | < 10ms | JCS + Ed25519 verify |
| Full validation pipeline | < 100ms | All stages, warm caches |
| Build + sign credential | < 10ms | JCS + Ed25519 sign |
| Compose v4 state (10 VCs) | < 20ms | Conflict resolution + merge |
| Compose v4 state (100 VCs) | < 200ms | Larger graph |
11.6 Future Packages
Section titled “11.6 Future Packages”Anticipated but out of scope for initial release:
@pdtf/vc-presenter— Verifiable Presentation construction for selective disclosure@pdtf/status-list— Bitstring Status List credential creation and management@pdtf/tir-client— Typed client for the Trusted Issuer Registry API@pdtf/migration— Tools for migrating PDTF v1/v3 data to v4 credential format
References
Section titled “References”- W3C Verifiable Credentials Data Model v2.0
- W3C Decentralised Identifiers (DIDs) v1.0
- W3C DID Core §7 — DID Resolution
- Data Integrity EdDSA Cryptosuites v1.0
- RFC 8785 — JSON Canonicalization Scheme (JCS)
- Bitstring Status List v1.0
- Sub-spec 01 — Entity Graph & Schema
- Sub-spec 02 — VC Data Model
- Sub-spec 03 — DID Methods & Identifiers
- Sub-spec 04 — OpenID Federation
- Sub-spec 06 — Key Management
- Sub-spec 07 — State Assembly
- Sub-spec 14 — Credential Revocation