Skip to content
Proposed specification for review. We are seeking feedback on key architectural choices in the industry consultation.

00 Architecture Overview

Version: 0.1 (Draft) Date: 15 April 2026 Author: Ed Molyneux


PDTF 2.0 is the property-specific domain profile for the emerging UK digital identity and credentials ecosystem. It defines property credential types, trust marks, and composition rules within the OpenID Federation framework.

Where PDTF v1 bound property data to a single platform’s verified claims model, PDTF 2.0 makes property data portable, independently verifiable, and interoperable — by adopting the same standards that UK Smart Data, GOV.UK Wallet, and the EU Digital Identity Architecture are converging on: OpenID Federation for trust, OID4VCI for credential issuance, OID4VP for credential presentation, and FAPI 2.0 for high-assurance API security.

PDTF’s unique contribution is the domain layer: an entity graph that decomposes a property transaction into its constituent parts (Transaction, Property, Title, Person, Organisation, and the relationship credentials SellerCapacity, Offer, Gift, Representation, TransactionRole), a schema system that defines what property credentials contain, and composition rules that assemble individual credentials into coherent transaction state.

This document is the master reference for the PDTF 2.0 implementation. It links to sub-specs for each workstream and captures architectural decisions as they’re made.


AspectPDTF v1 (Current)PDTF 2.0
Data modelMonolithic pdtf-transaction.json (~4,000 paths)Entity graph: Transaction, Property, Title, Person, Organisation, plus role-embodying relationship credentials SellerCapacity, Offer, Gift, Representation, TransactionRole
ClaimsOpenID Connect verified claims with pathKey:value REPLACE semanticsW3C Verifiable Credentials with sparse objects, issued via OID4VCI
IdentityFirebase Auth UIDs, no universal identifiersDIDs (did:key for persons, did:web for organisations) within a governed OpenID Federation
Entity identifiersInternal Firestore document IDsURNs: urn:pdtf:titleNumber:{value}, urn:pdtf:uprn:{value}
VerificationTrust the platform serving the dataCryptographic proof — verify the credential signature, check issuer trust via federation chain
Credential exchangePlatform-specific API callsOID4VCI (issuance) + OID4VP (presentation) — standard protocols
ProvenanceOIDC-derived evidence schema (deeply nested)Simpler evidence model reflecting actual usage patterns
Access controlPlatform-enforced role checksPer-credential termsOfUse + OID4VP presentation with participation credentials
TrustSingle platform trustOpenID Federation trust chain with property-specific trust marks
InteroperabilityREST API, platform-specificFAPI 2.0 security profile, MCP + OpenAPI interface, federation metadata discovery
Data syncPlatform-to-platform API callsEncrypted VC replication — GDPR-safe sync with per-recipient envelope encryption (target architecture; Phase 1 uses platform-level access control)

The key shift is not “OIDC → DIDs” but “platform-bound claims → portable credentials within a federated trust ecosystem.” DIDs remain useful as identifiers for organisations, transactions, and persons — but they sit within a governed federation, not as standalone trust roots.


EntityIdentifierSchemaDescription
Transactiondid:webv4/Transaction.jsonThe intent to sell. Metadata, status, dates, and financial context (saleContext). References exactly one Property and one or more Titles. DID document hosts service endpoints.
Propertyurn:pdtf:uprn:{uprn}v4/Property.jsonThe physical land and buildings. Physical facts, construction, energy, environmental. Governed by the “logbook test” — only facts that survive the transaction belong here.
Titleurn:pdtf:titleNumber:{number}v4/Title.jsonThe legal right. Legal boundary, registered owner, tenure, charges.
Persondid:keyv4/Person.jsonA natural person (seller, buyer).
Organisationdid:webv4/Organisation.jsonA company (conveyancer, estate agent, lender). Includes regulatory IDs (SRA, Companies House). Outside the v3 round trip: a participant’s firm is on the Transaction roster, and the DID resolves to the Organisation.

3.2 Relationship Credentials (Thin Assertions)

Section titled “3.2 Relationship Credentials (Thin Assertions)”

These are signed assertions linking a party to the transaction. They contain minimal data beyond the relationship they describe — and they are the only place a party’s role is recorded.

EntityIdentifierSchemaAssertsRole
SellerCapacityurn:pdtf:capacity:{id}v4/SellerCapacity.jsonThis person/organisation sells, in this capacity (legal owner, executor, attorney…). Verified against Title.registerExtract.proprietorship. Issued for every seller. Revocable.implied: Seller
Offerurn:pdtf:offer:{id}v4/Offer.jsonThis person made this offer: amount, status, conditions, buyer circumstances.implied: Buyer
Gifturn:pdtf:gift:{id}v4/Gift.jsonThis person gifts funds towards an offer, on these terms.implied: Giftor
Representationurn:pdtf:representation:{id}v4/Representation.jsonThis party is instructed by that party. One per (representative, represented party) pair. Revocable.explicit: which kind (Conveyancer, Estate Agent, Mortgage Broker…)
TransactionRoleurn:pdtf:role:{id}v4/TransactionRole.jsonThis party takes this role, where no more specific relationship applies (Lender, Landlord, Tenant, Surveyor, Platform Support).explicit: which role
MortgageURN (generated)FutureTied to Offer/buyer. Flagged for growth — not in initial implementation.—

The Transaction keeps an ordered roster of parties (participants[]), but the roster carries only what stays true of a party regardless of any relationship: their DID, a transaction-local id, and the firm they work for. Role is stored nowhere else than on the relationship credential that embodies it (01 §3.2, D32).

Why: a second copy would not be revoked. Firing a conveyancer means revoking their Representation. If the Transaction also recorded “Seller’s Conveyancer” against that participant, revocation would remove the relationship and leave the role assertion standing. So dropping a credential from the graph gives back a transaction in which that party has no role and no relationship — which is exactly what revocation should mean.

The two intents. The Transaction is the seller’s intent to sell, embodied by SellerCapacity. An Offer is a buyer’s intent to buy; buyers participate only through Offers. Everyone else is linked to the party they act for — a buyer’s conveyancer holds a Representation whose represented party is the buyer — and every credential references the Transaction directly. Nothing nests.

Because every relationship is a credential that references the Transaction, the graph itself is the access control model. No central ACL is required.

Consider a mortgage lender who needs to view the property data to issue a formal mortgage offer:

  1. Decision in Principle: The buyer holds a MortgagePromise VC issued by the lender. The buyer presents this to the agent as part of their Offer.
  2. Participation: If the offer is accepted, the lender is added to the roster and issued a TransactionRole credential (role: "Lender"). They don’t have a Representation (they aren’t acting for the buyer, they are funding them).
  3. Graph Resolution: The TransactionRole acts as the capability token. The lender presents it to the agent’s MCP server/adapter. The server validates the chain: “This lender holds a TransactionRole on this Transaction, whose buyer holds an accepted Offer.” Therefore, the lender is authorised to traverse the transaction graph and read the Property and Title VCs, subject to termsOfUse.

How a scoped, time-limited consent should sit on top of this for finer-grained access is an open consultation question (01 §9.2).

PDTF 2.0 Entity Relationship Model

The relationship is Transaction-centric, not Property → Title → Transaction. This matters because:

  • Unregistered titles exist — no title number, so no urn:pdtf:titleNumber:*. We need an identifier method for titles which are currently unregistered but for which title evidence is being gathered.
  • A transaction may involve multiple titles (e.g. a house and its garage on separate titles).
  • The DID-based relationship model handles this naturally — a Transaction DID document references its associated Property and Title identifiers.
Transaction (did:web:platform.example.com:transactions:*)
├── property → Property (urn:pdtf:uprn:*)
│ (may have no title — new build, unregistered)
├── titlesToBeSold → Title[] (urn:pdtf:titleNumber:* OR urn:pdtf:unregisteredTitle:*)
│ (ordered; may span multiple properties)
│
├── participants[] → Person (did:key:*) / Organisation (did:web:*)
│ └── ROSTER: DID, local id, firm. No roles.
│
│ relationship credentials, each referencing the Transaction:
│
├── SellerCapacity seller → Person/Organisation ⇒ Seller
│ └── The capacity in which they sell. Verified against
│ Title.registerExtract.proprietorship. The Transaction's
│ Titles are "for sale" because someone with a
│ SellerCapacity credential says so.
│
├── Offer buyer → Person/Organisation, offerId ⇒ Buyer
│ └── amount, status, conditions, buyer circumstances
│
├── Gift donor → Person/Organisation, offerId ⇒ Giftor
│ └── gift terms the conveyancer must resolve
│
├── Representation representative → representedParty, role
│ ├── role: "Seller's Conveyancer" / "Estate Agent" (instructed by the seller)
│ └── role: "Buyer's Conveyancer" / "Mortgage Broker" (instructed by the buyer)
│ (one per pair; the representative's firm is on the roster)
│
└── TransactionRole participant, role
└── role: "Lender" / "Landlord" / "Tenant" / "Surveyor" / "Platform Support"

Participation decomposed: The old “Participation” entity is replaced by five precise relationship credentials:

  • SellerCapacity — the capacity in which a party sells, linking a Person or Organisation DID to the Transaction (and optionally to a Title). The owner starts by asserting it; the platform then verifies against Title.registerExtract.proprietorship (claim-vs-evidence separation). This is what establishes the right to sell.
  • Offer — a buyer’s offer. Always identifies exactly one buyer; joint purchasers each hold one with the same offerId.
  • Gift — a giftor’s contribution towards an offer. Carries the giftor’s offerId so that Offer ⇒ Buyer stays exact.
  • Representation — one party instructed by another. The retainer is with the client, so a conveyancer is instructed by a person, not by an offer; a prospective buyer with no offer yet can still be represented. The credential model supports both Person and Organisation on either side.
  • TransactionRole — a role with no instructing party: the lender, a landlord or tenant, platform support. Also the fallback until a more specific relationship is established, so that every party with a role holds exactly one role-bearing credential.

Person vs Organisation: Both can sell, buy, gift, represent, and hold a role. The difference is structural, not role-based: an Organisation has a Companies House identity, SRA registration, and PI insurance — attributes that don’t belong on a Person entity. Both get relationship credentials; both can be on either side of a transaction.

  • Buyers participate only through Offers — no Participation entity for buyers. This models the real-world relationship: a buyer doesn’t “participate” in the seller’s transaction until they make an offer, and multiple offers can exist simultaneously. Buyers can be Persons or Organisations (companies buy property too).
  • SellerCapacity establishes the right to sell — the legal owner self-asserts by issuing a SellerCapacity credential linking their DID to the Transaction. This is what puts a title “for sale”. The platform then verifies the claim against the proprietorship register. No separate “listing” entity is needed.
  • Role lives only on the relationship credential (D32) — the roster has no role field. Revoking the credential removes the role with it.
  • ID-keyed collections — v4 moves from arrays (participants[], searches[]) to ID-keyed maps (like current offers). Where order carried meaning it lives on the Transaction’s reference lists. Breaking change to schema structure but not to the underlying data — path handling code updates required.
  • Property-level VCs — EPC, flood risk, searches etc. are Property VCs with paths like /energyEfficiency/certificate, not first-class entity VCs. Primary issuers will use the same paths when they adopt the standard.

3.4 Entity Separation Principle — The Logbook Test

Section titled “3.4 Entity Separation Principle — The Logbook Test”

The governing question for field assignment: “Does this fact travel with the property to a new owner?”

  • Property = enduring facts (the “logbook”): EPC, flood risk, build info, legal questions, fixtures & fittings, environmental data. If a new buyer inherits it, it’s a Property fact.
  • Title = legal title facts: title number, extents (geoJSON), register extract (including proprietorship as evidence), ownership type (freehold/leasehold), leasehold terms and restrictions, isFirstRegistration, mortgage/charge information. The existing branch 263 work already merges ownershipsToBeTransferred into the Title entity.
  • Transaction = this-sale facts: numberOfSellers, numberOfNonUkResidentSellers, outstandingMortgage, existingLender, hasHelpToBuyEquityLoan, isLimitedCompanySale. None of these pass the logbook test — they describe this specific transaction, not the property itself.
  • SellerCapacity = the capacity in which a Person or Organisation sells (sellersCapacity.capacity, dateBecameOwnerOrAuthority). The owner starts by asserting this themselves — their claim is what establishes the right to sell. The evidence (proprietorship register) lives on the Title entity — SellerCapacity is the claim, Title holds the evidence.

The entity decomposition is already in progress on the schemas repo:

  • Branch: 263-extract-separate-entity-schemas-from-combinedjson-in-preparation-for-pdtf-20
  • 120 files changed, 224K lines added
  • Extraction utility: src/utils/decomposeSchema.js (576 lines)
  • Entity overlay system: per-entity overlay directories with form-specific overlays
  • Tests: entity validation, overlay application, extension handling
  • V4 entity schemas: Transaction, Property, Title, Person, Participation (Offer not yet created)

Each piece of property data becomes a signed Verifiable Credential. PDTF defines property-specific credential types within the W3C VC Data Model v2.0, issued and presented using OpenID protocols (see §4.5).

{
"@context": [
"https://www.w3.org/ns/credentials/v2",
"https://trust.propdata.org.uk/ns/pdtf/v2"
],
"type": ["VerifiableCredential", "PropertyCredential"],
"issuer": "did:key:z6Mkh...abc",
"validFrom": "2026-03-23T07:00:00Z",
"credentialSubject": {
"id": "urn:pdtf:uprn:100023456789",
"energyEfficiency": {
"certificate": {
"certificateNumber": "1234-5678-9012-3456-7890",
"currentEnergyRating": "C",
"currentEnergyEfficiency": 72,
"lodgementDate": "2024-01-15"
}
}
},
"evidence": [{
"type": "ElectronicRecord",
"source": "get-energy-performance-data.communities.gov.uk",
"retrievedAt": "2026-03-23T06:30:00Z",
"method": "API"
}],
"termsOfUse": [{
"type": "PdtfAccessPolicy",
"confidentiality": "public",
"pii": false
}],
"credentialStatus": {
"id": "https://adapters.propdata.org.uk/status/epc/12345#67890",
"type": "BitstringStatusListEntry",
"statusPurpose": "revocation",
"statusListIndex": "67890",
"statusListCredential": "https://adapters.propdata.org.uk/status/epc/12345"
},
"proof": {
"type": "DataIntegrityProof",
"cryptosuite": "eddsa-jcs-2022",
"verificationMethod": "did:key:z6Mkh...abc#key-1",
"proofPurpose": "assertionMethod",
"proofValue": "z3FXQje..."
}
}

4.2 Relationship to OpenID Verified Claims

Section titled “4.2 Relationship to OpenID Verified Claims”

PDTF v1 used OpenID Connect verified claims with a pathKey:value model. PDTF 2.0 moves to W3C Verifiable Credentials, but the transition is enabled — not complicated — by the OpenID ecosystem’s own evolution. OID4VCI and OID4VP now provide standard protocols for issuing and presenting VCs, meaning PDTF credentials live within the same ecosystem as the v1 OIDC claims, just in a more expressive and portable format.

Implementers migrating from v1 should note:

  • The credentialSubject sparse object replaces claimPath + claimValue
  • The evidence model simplifies the deeply nested OIDC evidence schema
  • termsOfUse carries the same confidentiality/PII/role semantics as before
  • credentialStatus adds revocation capability (absent in v1)
  • proof adds cryptographic verification (absent in v1)

4.3 Claims Representation — Sparse Objects + Dependency Pruning

Section titled “4.3 Claims Representation — Sparse Objects + Dependency Pruning”

Decision: Move away from pathKey:value REPLACE semantics toward sparse objects with dependency pruning. Consensus needed from LMS and other implementers.

Current approach (v1):

{ "claimPath": "/propertyPack/heating/heatingSystem/heatingType", "claimValue": "Central heating" }
{ "claimPath": "/propertyPack/heating/heatingSystem/centralHeatingDetails/fuelType", "claimValue": "Mains gas" }

When heatingType changes from “Central heating” to “None”, the centralHeatingDetails claim still exists in the database — REPLACE only overwrites the specific path.

New approach (v2):

{
"credentialSubject": {
"id": "urn:pdtf:uprn:100023456789",
"heating": {
"heatingSystem": {
"heatingType": "None"
}
}
}
}

State assembly uses MERGE semantics. A dependency pruning pass then strips centralHeatingDetails because the schema’s oneOf discriminator on heatingType makes it irrelevant when value is “None”.

Why this matters: The pruning pass is the clean, spec-compliant way to handle dependent data. It requires implementers to understand schema discriminators, but the reference implementation will handle it and the alternative (REPLACE semantics with potential stale nested data) is worse.

4.4 Proof Format: Data Integrity vs JWS/VC-JWT

Section titled “4.4 Proof Format: Data Integrity vs JWS/VC-JWT”

PDTF 2.0 uses Data Integrity proofs (eddsa-jcs-2022) as the primary securing mechanism for credentials at rest and in storage. Both Data Integrity and JWS are valid securing mechanisms for W3C VCs. The choice has meaningful consequences.

Data Integrity (PDTF primary)JWS / VC-JWT
Proof locationproof object embedded in the VC JSONDetached JWS or entire VC wrapped as a JWT (header.payload.signature)
CanonicalisationJCS (JSON Canonicalization Scheme, RFC 8785)None needed — signs raw bytes
Human readabilityVC is plain JSON, directly inspectableVC-JWT requires base64 decoding before any claims are visible
Selective disclosure pathFoundation for BBS+ and JSON-LD ZKP mechanismsRequires SD-JWT (separate spec)
W3C VC 2.0 positioningPrimary securing mechanismSupported but positioned as legacy
OID4VCI formatldp_vc credential formatjwt_vc_json credential format
Key algorithmEd25519 (via eddsa-jcs-2022 cryptosuite)Algorithm-agnostic (RS256, ES256, EdDSA, etc.)

Rationale for Data Integrity as primary format:

  1. JSON-native. PDTF VCs stay as parseable JSON throughout their lifecycle. Consumers, debuggers, and AI agents can read claims without decoding. VC-JWT produces opaque base64 blobs that must be unpacked before inspection.

  2. Selective disclosure upgrade path. Data Integrity is the foundation for BBS+ signatures, enabling future scenarios like “share the EPC rating but not the address” without re-issuance.

  3. W3C alignment. The VC Data Model v2.0 editors have positioned Data Integrity as the primary path forward.

  4. Deterministic serialisation. JCS provides a canonical JSON form regardless of whitespace or key ordering.

Where JWS/JWT is used in PDTF 2.0:

  • OID4VCI credential responses may use jwt_vc_json format when interacting with wallet implementations that prefer JWT. PDTF adapters MUST support ldp_vc and SHOULD support jwt_vc_json.
  • OID4VP presentation tokens use JWT as the transport envelope (VP Token), while the credentials inside remain Data Integrity VCs.
  • OpenID Federation entity statements are signed JWTs by definition — this is the federation layer, not the credential layer.
  • Status list credentials use Data Integrity for consistency, but JWS is technically permitted.

4.5 Credential Issuance and Presentation Protocols

Section titled “4.5 Credential Issuance and Presentation Protocols”

PDTF 2.0 uses OpenID standards for credential exchange:

OID4VCI (OpenID for Verifiable Credential Issuance) — how credentials are issued:

  • Adapters and primary sources act as OID4VCI credential issuers
  • Each issuer publishes a credential issuer metadata document at /.well-known/openid-credential-issuer
  • Supported credential formats: ldp_vc (primary), jwt_vc_json (interoperability)
  • Credential types are PDTF-defined: PropertyCredential, TitleCredential, SellerCapacityCredential, RepresentationCredential, etc.
  • Pre-authorised code flow for adapter-initiated issuance (no user interaction needed for data lookups)
  • Authorization code flow for user-initiated credential requests

OID4VP (OpenID for Verifiable Presentations) — how credentials are presented:

  • Participants present credentials to prove their relationship to a transaction
  • Presentation definition specifies which credential types are required (e.g. SellerCapacityCredential or RepresentationCredential)
  • VP Token contains the Verifiable Presentation with the requested credentials
  • Used for both human-initiated flows (wallet) and machine-to-machine (API access)
{
"presentation_definition": {
"id": "pdtf-transaction-access",
"input_descriptors": [{
"id": "participation-proof",
"constraints": {
"fields": [{
"path": ["$.type"],
"filter": {
"type": "array",
"contains": {
"enum": ["SellerCapacityCredential", "OfferCredential", "RepresentationCredential", "TransactionRoleCredential"]
}
}
}]
}
}]
}
}

DIDs serve as identifiers for organisations, persons, and transactions within the PDTF ecosystem. They are not standalone trust roots — trust is established through the OpenID Federation chain (§6), and DIDs provide the cryptographic binding between an entity and its keys.

EntityDID MethodExampleResolution
Personsdid:keydid:key:z6Mkh...abcSelf-resolving from public key, no hosting needed
Organisationsdid:key or did:webdid:key:z6Mkf...xyz or did:web:smithandjones.co.ukdid:key when managed by account provider (e.g. LMS); did:web when self-hosting identity
Transactionsdid:webdid:web:platform.example.com:transactions:abc123Hosted DID document at https://platform.example.com/transactions/abc123/did.json
Trusted Adaptersdid:webdid:web:adapters.propdata.org.uk:hmlrHosted DID document with service endpoints for VC requests + federation entity configuration
urn:pdtf:uprn:{uprn} → Property identifier
urn:pdtf:titleNumber:{number} → Title identifier
urn:pdtf:capacity:{uuid} → SellerCapacity (⇒ Seller)
urn:pdtf:offer:{uuid} → Offer (⇒ Buyer)
urn:pdtf:gift:{uuid} → Gift (⇒ Giftor)
urn:pdtf:representation:{uuid} → Representation (representative ↔ represented party, role)
urn:pdtf:role:{uuid} → TransactionRole (participant, role)

Discovery in PDTF 2.0 combines OpenID Federation metadata resolution with DID document resolution:

  1. Federation metadata — resolve the entity’s OpenID Federation entity configuration at /.well-known/openid-federation. This establishes trust (is this entity part of the federation?) and capabilities (what credential types does it issue?).

  2. DID document — resolve the entity’s DID document for cryptographic keys and service endpoints. For did:web entities, this is at the standard did.json path.

  3. Credential issuer metadata — for adapters/issuers, resolve /.well-known/openid-credential-issuer for OID4VCI-specific metadata (supported credential types, formats, endpoints).

For a transaction, the discovery flow is:

Transaction DID (did:web:platform.example.com:transactions:abc123)
→ DID Document (keys, service endpoints including PDTF API and MCP)
→ Federation entity configuration (trust chain, trust marks)
→ Credential issuer metadata (for adapters providing data to this transaction)

To access restricted or confidential VCs (or the pre-composed state derived from them), a requester must:

  1. Present a valid credential via OID4VP — a SellerCapacity, Offer, Representation, or TransactionRole credential proving their relationship to the transaction
  2. Prove control of their DID — implicit in the OID4VP flow (the VP is signed by the holder’s key)
  3. Revocation check — the presented credential must not be revoked (Bitstring Status List check)
  4. termsOfUse filtering — the system returns only VCs whose termsOfUse policy permits access for the requester’s role

Public VCs (title deeds, EPCs, searches) require no authentication.


PDTF 2.0 uses OpenID Federation (RFC 9396) as its trust infrastructure. The federation model establishes who is authorised to issue which property credentials, using a chain of signed entity statements from a trust anchor down to leaf entities.

┌──────────────────────────┐
│ Trust Anchor │
│ (propdata.org.uk) │
│ ────────────────── │
│ Entity Configuration │
│ + Trust Mark Issuer │
└──────────┬───────────────┘
│ subordinate statement
┌──────────▼───────────────┐
│ Intermediate Entity │
│ (sector authority, e.g. │
│ property data services) │
└──────────┬───────────────┘
│ subordinate statement
┌──────────▼───────────────┐
│ Leaf Entity │
│ (adapter / issuer) │
│ ────────────────── │
│ Entity Configuration │
│ + Trust Marks held │
│ + OID4VCI metadata │
└──────────────────────────┘

How it works:

  1. The Trust Anchor publishes its entity configuration at https://propdata.org.uk/.well-known/openid-federation, containing its signing keys and federation policy.

  2. The Trust Anchor issues subordinate entity statements for each authorised entity in the federation — these are signed JWTs that bind the subordinate’s identifier to its metadata and any constraints.

  3. Each leaf entity (adapter, issuer) publishes its own entity configuration at its domain, including the trust marks it holds and its OID4VCI credential issuer metadata.

  4. A verifier resolves the trust chain by fetching the leaf entity’s configuration, walking up through subordinate statements to the trust anchor, and validating signatures at each level.

Entity configuration example (adapter):

{
"iss": "https://adapters.propdata.org.uk/hmlr",
"sub": "https://adapters.propdata.org.uk/hmlr",
"iat": 1713186000,
"exp": 1744722000,
"jwks": {
"keys": [{
"kty": "OKP",
"crv": "Ed25519",
"x": "O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik",
"kid": "hmlr-adapter-key-1",
"use": "sig"
}]
},
"metadata": {
"openid_credential_issuer": {
"credential_issuer": "https://adapters.propdata.org.uk/hmlr",
"credential_endpoint": "https://adapters.propdata.org.uk/hmlr/credential",
"credentials_supported": [{
"format": "ldp_vc",
"types": ["VerifiableCredential", "TitleCredential"],
"cryptographic_binding_methods_supported": ["did:key", "did:web"]
}]
}
},
"trust_marks": [{
"id": "https://propdata.org.uk/trust-marks/title-data-provider",
"trust_mark": "eyJhbGciOiJFZERTQSIs..."
}],
"authority_hints": ["https://propdata.org.uk"]
}

Trust marks are the PDTF-specific mechanism for expressing what an entity is authorised to do within the property ecosystem. They use the OpenID Federation trust mark standard (see Sub-spec 04).

Each trust mark is a signed JWT issued by the trust anchor (or a delegated trust mark issuer), asserting that an entity meets the requirements for a specific role:

Trust Mark IDMeaningIssued To
https://propdata.org.uk/trust-marks/title-data-providerAuthorised to issue TitleCredentialsHMLR, adapters proxying HMLR data
https://propdata.org.uk/trust-marks/search-providerAuthorised to issue property search credentialsSearch providers, LLC adapters
https://propdata.org.uk/trust-marks/regulated-conveyancerSRA/CLC regulated conveyancing firmConveyancer organisations
https://propdata.org.uk/trust-marks/energy-data-providerAuthorised to issue EPC/energy credentialsMHCLG, EPC adapters
https://propdata.org.uk/trust-marks/environmental-data-providerAuthorised to issue environmental risk credentialsEA, flood risk adapters
https://propdata.org.uk/trust-marks/account-providerAuthorised to issue user DIDs on behalf of personsThe reference platform, LMS, wallet providers

Trust mark structure:

{
"iss": "https://propdata.org.uk",
"sub": "https://adapters.propdata.org.uk/hmlr",
"id": "https://propdata.org.uk/trust-marks/title-data-provider",
"iat": 1713186000,
"exp": 1744722000,
"ref": "https://propdata.org.uk/trust-marks/title-data-provider/policy",
"delegation": {
"authorised_paths": [
"Title:/titleNumber",
"Title:/titleExtents",
"Title:/registerExtract",
"Title:/ownership/*"
],
"trust_level": "trusted_proxy",
"proxy_for": "hmlr.gov.uk"
}
}

The delegation claim is a PDTF extension to the standard trust mark. It carries the entity:path authorisation — specifying exactly which credential subject paths an issuer is authorised to populate. This is PDTF’s domain-specific contribution to the trust mark: not just “this entity is a title data provider” but “this entity is authorised to issue credentials covering these specific data paths.”

How an entity obtains trust marks and joins the federation:

Phase 1 (bootstrap):

  1. Entity applies to the trust anchor operator (initially propdata.org.uk)
  2. Trust anchor verifies the entity’s identity and authorisation (e.g. SRA registration for conveyancers, contractual relationship for data adapters)
  3. Trust anchor issues a subordinate entity statement and the appropriate trust marks
  4. Entity publishes its entity configuration referencing the trust anchor

Phase 2+ (federated governance):

  1. A property sector governance body operates the trust anchor
  2. Multiple trust mark issuers may exist (e.g. SRA issues regulated-conveyancer trust marks directly)
  3. Trust chains can be deeper — a sector authority issues subordinate statements for categories of issuers

All federation metadata is signed-JWT-based: Entity Configurations, Subordinate Entity Statements, and Trust Marks. See Sub-spec 04: OpenID Federation for the full trust architecture.

OpenID Federation (PDTF)EBSI Root-TAO / TAO
Trust anchorTrust Anchor Entity StatementRoot TAO (governmental)
Authority scopeTrust marks + metadata_policyVerifiableAccreditation VC
DiscoveryHTTP .well-known/openid-federation chain resolutionDID resolution + on-chain registry
Chain depthFlexible (n levels)Fixed 3-tier (Root TAO → TAO → TI)
Chain formatSigned JWTs (Entity Statements)VCs (accreditations are VCs)
Revocation of trustExpire/withdraw Entity StatementRevoke the accreditation VC
GovernanceFederated (each anchor sets policy)Centralised (EU institutional)
InfrastructureHTTPS endpointsPermissioned blockchain (EBSI ledger)
UK ecosystem fitHigh (OIDC-native, UK gov direction)Low (EU-centric, blockchain dependency)

Why OpenID Federation is the right choice:

OpenID Federation provides:

  • Signed trust chains — every level of the chain is cryptographically verifiable, not just the leaf credentials
  • Decentralised governance — each trust anchor sets its own policy; no single registry to control
  • Ecosystem alignment — UK Smart Data, GOV.UK Wallet, and EUDI are all converging on OpenID Federation
  • Credential-format agnostic — works with both Data Integrity VCs and JWT VCs
  • Existing infrastructure — plugs into OAuth/OIDC infrastructure that platforms already operate

Why not EBSI’s model:

EBSI’s Root-TAO/TAO hierarchy is conceptually elegant — trust chains are VCs all the way down. But it requires a permissioned blockchain, assumes governmental top-down accreditation, and carries schema overhead that doesn’t fit the UK property ecosystem’s lateral trust relationships.

Phase 1 (now): The Platform as Federation Trust Anchor

  • The platform operator runs the trust anchor at propdata.org.uk
  • Existing collectors become OID4VCI credential issuers (adapters) as leaf entities
  • Each adapter holds trust marks issued by the trust anchor
  • Federation metadata served from the PDTF Trust Anchor at trust.pdtf.org
  • The reference platform is the sole account provider for user DIDs
  • “Map-and-wrap”: call existing APIs (HMLR OC1, EPC API, EA flood), issue as signed VCs via OID4VCI

Phase 2 (medium-term): Federated Governance

  • Property sector governance body operates the trust anchor (or becomes a higher-level trust anchor above the platform operator)
  • Multiple organisations can run adapters (TM Group, LMS) — each with their own entity configuration and trust marks
  • Adapters hosted independently (adapters.propdata.org.uk) with their own federation metadata
  • SRA/CLC issue regulated-conveyancer trust marks directly
  • Multiple account providers for user DIDs

Phase 3 (future): Government Sources as Trust Anchors

  • HMLR, MHCLG, Environment Agency publish their own federation entity configurations
  • They become trust anchors or intermediate entities in their own right, issuing credentials directly via OID4VCI
  • Trust marks for adapter proxies carry proxy_for indicating the primary source
  • Primary source credentials carry higher trust weight than proxy credentials
  • Verifiers can resolve trust chains to government sources without intermediaries

  • Google Cloud KMS for all key storage in production
  • Ed25519 key algorithm (expressed as JWK in federation metadata and DID documents)
  • One key per user (generates their did:key identity)
  • One key per adapter (for signing VCs and federation entity configuration)
  • One key for the platform / trust anchor (for signing trust marks and subordinate entity statements)

Keys are published in two places:

  1. Federation entity configuration — JWKS in the entity statement, used for federation trust chain verification
  2. DID documents — verification methods, used for VC signature verification

Both reference the same underlying key material. The federation JWKS uses standard JWK format:

{
"kty": "OKP",
"crv": "Ed25519",
"x": "O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik",
"kid": "hmlr-adapter-key-1",
"use": "sig"
}

Key rotation follows OpenID Federation metadata update semantics:

  1. Generate new key pair in Cloud KMS
  2. Update the entity configuration to include both old and new keys in the JWKS (overlap period)
  3. Update the DID document’s verificationMethod to include the new key
  4. Begin signing new credentials and entity statements with the new key
  5. After the overlap period (determined by exp on existing credentials and entity statements), remove the old key
  6. Trust anchor re-issues subordinate entity statements referencing the updated JWKS

The overlap period ensures that credentials signed with the old key remain verifiable until they expire or are superseded. Federation entity statements have their own exp — rotating the statement key requires the trust anchor to re-issue the subordinate statement.

All issuers must support revocation via W3C Bitstring Status List v2. This is critical for:

  • SellerCapacity/Representation credentials — must be revocable when a sale completes, a mandate is withdrawn, or a conveyancer is replaced
  • Property data VCs — revocable when data is superseded (e.g. new EPC issued, updated flood risk assessment)
  • User DID credentials — revocable when a user account is disabled or identity verification is invalidated
  • Trust marks — revocable when an entity’s accreditation is withdrawn (complementing the trust mark’s exp)

How it works:

  1. Each issuer hosts one or more Bitstring Status List credentials at a public URL
  2. Each VC includes a credentialStatus field pointing to its entry in the status list
  3. The status list is a compressed bitstring — each bit position maps to a credential
  4. To revoke: issuer flips the bit at the credential’s statusListIndex
  5. Verifiers fetch the status list (cacheable with short TTL) and check the bit

Adapter hosting: Each adapter hosts its own status list endpoints (e.g. adapters.propdata.org.uk/status/epc/{listId}). Status lists are signed by the same adapter key used for VC issuance.

Google Cloud KMS
├── Trust Anchor Key (propdata.org.uk)
│ └── Signs: subordinate entity statements, trust marks
│
├── Adapter Keys (did:web, per-adapter)
│ ├── hmlr-proxy-key → did:web:adapters.propdata.org.uk:hmlr
│ ├── epc-proxy-key → did:web:adapters.propdata.org.uk:epc
│ └── ea-flood-proxy-key → did:web:adapters.propdata.org.uk:ea-flood
│ (Each signs: VCs, entity configuration, status lists)
│
├── User Keys (did:key, per-user)
│ ├── user-{uid}-key → did:key:z6Mkh...abc
│ └── ...
│
└── Platform Key (platform operator identity)
└── pdtf-platform-key → did:web:platform.example.com

7.5 Custodial Cloud Wallets vs Bring-Your-Own-Wallet

Section titled “7.5 Custodial Cloud Wallets vs Bring-Your-Own-Wallet”

A strict Verifiable Credential model assumes users hold their own keys in a mobile wallet app, signing every assertion they make. In a property transaction, prompting a user to sign every page of a property information form (TA6) via a mobile app pop-up creates an unacceptable user experience.

To solve this while retaining cryptographic provenance, PDTF platforms use a Custodial Cloud Wallet pattern:

  1. Onboarding & IDV: The platform performs AML/ID checks and issues an Identity VC for the user.
  2. Key Provisioning: The platform generates a unique, secure enclave key pair (e.g. in Cloud KMS) for that specific user.
  3. Session Binding: When the user logs into the web portal, their session is securely bound to that cloud wallet key.
  4. Seamless Signing: As the user fills out forms, the platform backend uses the user’s KMS key to sign the assertions in the background.
  5. Presentation: The platform packages the signed assertions plus the Identity VC into an OID4VP presentation and delivers it to the verifier (e.g. the buyer’s conveyancer).

To the verifier, this looks exactly like a standard OpenID presentation from a personal digital wallet — they receive cryptographic proof that the verified person made the assertions. To the user, it feels like a normal web application.

Future migration to BYOW: When government or ecosystem wallets (e.g. GOV.UK Wallet, EUDI) become mainstream, users can choose to “Bring Your Own Wallet” (BYOW). They authenticate via OID4VP and sign a finalised document at the end of the process using their own device key. The underlying PDTF architecture and verification logic does not need to change to support this migration.


Three state assembly functions, used in sequence:

  1. composeStateFromClaims (current) — aggregates pathKey:value verified claims with REPLACE semantics. No changes, backward compatible, continues to power existing v3 endpoints.

  2. composeV3StateFromGraph — traverses the entity graph, assembles VCs into a v3-compatible flat state. Uses the existing combined.json schema shape. Replaces composeStateFromClaims once coverage is complete.

  3. composeV4StateFromGraph — traverses the entity graph, assembles VCs into a v4 entity-based state. Internal handlers migrate to this. Uses sparse object MERGE + dependency pruning.

Migration strategy is resolved via parallel running (Q6.1–Q6.3 resolved).

Phase 1: composeStateFromClaims (existing, v3 shape)
↓ parallel
Phase 2: composeV3StateFromGraph (same output as Phase 1, different input) - parallel running resolves Q6
→ validate: outputs must match
→ once confident, replace Phase 1 internally
↓ parallel
Phase 3: composeV4StateFromGraph (new entity-based shape)
→ internal handlers migrate one by one
→ external v3 API continues to use composeV3StateFromGraph

Current DE paths: propertyPack/heating/heatingSystem/heatingType Entity paths: property:heating/heatingSystem/heatingType

The property: prefix maps to the Property entity. pdtfPaths.js becomes a mapper that resolves entity:path to the appropriate entity and sub-path. The paths themselves aren’t entity-specific (no UPRN in the path) — the entity context is provided by which entity the path is being evaluated against.


Separate domain: adapters.propdata.org.uk (new GCP project, potentially open-sourced).

Each adapter is an OID4VCI credential issuer and an OpenID Federation leaf entity:

  • Has its own did:web identity (DID document at adapters.propdata.org.uk/{adapter}/did.json)
  • Publishes federation entity configuration at adapters.propdata.org.uk/{adapter}/.well-known/openid-federation
  • Publishes credential issuer metadata at adapters.propdata.org.uk/{adapter}/.well-known/openid-credential-issuer
  • Holds trust marks from the property sector trust anchor
  • Has its own signing key in Google Cloud KMS
  • Calls existing source APIs (HMLR OC1, EPC API, EA flood data, LLC API, etc.)
  • Issues signed VCs in PDTF 2.0 format via OID4VCI credential endpoint

9.2 Initial Adapters (from existing collectors)

Section titled “9.2 Initial Adapters (from existing collectors)”
AdapterSource APICredential TypesTrust MarkPriority
hmlrHMLR OC1/OC2TitleCredentialtitle-data-providerHigh
epcMHCLG EPC APIPropertyCredential (energy paths)energy-data-providerHigh (just rebuilt)
ea-floodEA Flood Risk APIPropertyCredential (flood paths)environmental-data-providerHigh
llcHMLR LLC APIPropertyCredential (LLC paths)search-providerMedium
bsrBSR Register APIPropertyCredential (building safety)search-providerMedium
voaVOA Council TaxPropertyCredential (council tax)search-providerLower
OID4VCI Credential Request
→ Adapter validates requester (OID4VP participation proof or pre-authorised code)
→ Adapter calls source API
→ Adapter maps response to PDTF entity schema
→ Adapter signs VC with its KMS key
→ Returns signed VC in OID4VCI credential response

The pre-authorised code flow is typical for adapter-initiated issuance: the platform requests a credential for a specific property/title, and the adapter issues it without user interaction. The authorization code flow is used when a user initiates a credential request through a wallet or application.

  1. Requester presents a SellerCapacity, Offer, Representation, or TransactionRole credential via OID4VP
  2. Adapter verifies the VP signature and the contained credential(s)
  3. Adapter checks credential is not revoked (Bitstring Status List)
  4. Adapter verifies its own trust chain is valid (federation metadata)
  5. Adapter checks termsOfUse of requested entity:paths against requester’s role
  6. If authorised: fetch data, issue VC, return
  7. If public data: no authentication required

(Full spec: papers/pdtf-v2/12-adapter-access-control.md — TBD)


ComponentDescriptionLanguageRepo
VC ValidatorValidates VC signature, resolves federation trust chain, checks trust marks, verifies proofTypeScriptproperty-data-standards-co/pdtf-vc-validator
Graph ComposerTraverses entity graph, assembles state from VCsTypeScriptPart of @pdtf/schemas
DID ResolverResolves did:key and did:web identifiersTypeScriptproperty-data-standards-co/pdtf-did-resolver
Credential BuilderCreates and signs VCs with PDTF context, OID4VCI-compatibleTypeScriptproperty-data-standards-co/pdtf-vc-builder
Federation ClientResolves OpenID Federation trust chains, validates trust marksTypeScriptproperty-data-standards-co/pdtf-federation-client
Input: VC document
→ Parse and validate structure (JSON-LD context, required fields)
→ Extract issuer DID
→ Resolve DID → public key
→ Verify proof signature against public key
→ Resolve federation trust chain for issuer:
→ Fetch issuer's entity configuration
→ Walk authority_hints to trust anchor
→ Validate subordinate entity statements at each level
→ Verify trust marks held by issuer
→ Check issuer's trust marks cover the credential's entity:path combinations
→ Check credential is not expired
→ Check credential revocation status:
→ Fetch Bitstring Status List from credentialStatus.statusListCredential (cached, short TTL)
→ Verify status list credential signature
→ Check bit at statusListIndex — if set, credential is revoked
→ Return: { valid: true, trustLevel: "trusted_proxy", trustMarks: ["title-data-provider"], revoked: false }

NPTN (National Property Transaction Network) is LMS’s implementation of PDTF v1 as a data hub. PDTF 2.0 needs to work with NPTN, not replace it.

  • NPTN continues as the transaction orchestration layer (the “road”)
  • PDTF 2.0 VCs flow through NPTN as the data format
  • NPTN validates VCs using the reference validator (which now includes federation trust chain resolution)
  • Credential exchange between NPTN and participants uses OID4VP — participants present credentials to prove their relationship, and NPTN presents credentials to participants
  • NPTN’s existing claim filtering (confidentiality, role-based) maps to VC termsOfUse
  • NPTN can act as a federation intermediate entity, with trust marks authorising it to relay credentials
  • LMS documentation (spec 10) explains the architecture in terms they can implement

Comprehensive guide covering:

  • Why VCs and OpenID Federation (business case, not just technical)
  • How NPTN handles VCs (receive via OID4VCI, validate, store, present via OID4VP, filter, serve)
  • Migration path from current verified claims to VCs
  • Reference validator and federation client integration
  • Timeline aligned with NPTN roadmap

PDTF 2.0 adopts FAPI 2.0 (Financial-grade API Security Profile) as its high-assurance security layer. Property transactions involve sensitive personal and financial data — the same security guarantees required in open banking apply here.

FAPI 2.0 provides:

  • Sender-constrained tokens (DPoP or mTLS) — tokens are bound to the client that requested them, preventing token theft/replay
  • PAR (Pushed Authorization Requests) — authorization parameters are sent directly to the server, not via browser redirect
  • JARM (JWT-Secured Authorization Response Mode) — authorization responses are signed, preventing response injection
  • PKCE — mandatory for all authorization code flows

This is not “FAPI for transport only” — FAPI 2.0 is the security profile for all API interactions, including OID4VCI credential requests and OID4VP presentation flows.

The core PDTF API is MCP-compliant (Model Context Protocol). Every transaction is a discoverable, agent-accessible resource via the transaction DID document’s service endpoints. The same underlying operations are exposed through both:

  • MCP binding — tools, resources, and prompts for AI agents. An agent can authenticate, browse transactions, fetch and verify credentials, compose state, and run diligence queries through MCP tool calls.
  • OpenAPI binding — conventional REST endpoints with typed schemas for traditional integrators building web applications, mobile apps, and backend services.

Both bindings share the same service layer, authentication model (FAPI 2.0), and credential access rules. The MCP binding is not a wrapper around the REST API — they are peer interfaces to the same operations.

Core operations (both bindings):

OperationDescription
resolveTransaction(did)Resolve a transaction DID → DID document, service endpoints, federation metadata
fetchCredentials(identifier, options)Fetch VCs by entity identifier (UPRN, title number, transaction DID) with optional type/path filtering
composeState(transactionDid, options)Traverse the full entity graph from a transaction DID, collect all VCs, compose state with dependency pruning. Options: v3/v4 format, include provenance
verifyCredential(vc)Verify a single VC: signature check, federation trust chain resolution, revocation status
issueCredential(type, subject, data)Issue a new VC via OID4VCI (adapter/platform only)
revokeCredential(id)Revoke a VC by flipping its status bit (issuer only)
listParticipants(transactionDid)List the roster and the relationship credentials (SellerCapacity, Offer, Gift, Representation, TransactionRole) for a transaction
submitOffer(transactionDid, offer)Submit a buyer offer
presentCredentials(presentationDefinition)OID4VP credential presentation

AI agent skill layer: PDTF publishes agent skills (tool definitions + usage documentation) that allow AI agents to:

  • Build interface code against the API (code generation from the skill)
  • Directly operate on transactions (fetch VCs, compose state, run diligence) via MCP tool calls
  • Authenticate and prove participation via OID4VP without manual credential management

Access to transaction data requires proof that the requester is a participant. Authentication uses OID4VP:

Phase 1: Account Provider Delegation (Custodial)

Section titled “Phase 1: Account Provider Delegation (Custodial)”

In Phase 1, the account provider (e.g. LMS) holds the user’s private key in KMS. Authentication works via OAuth + OID4VP:

  1. Agent/client authenticates via FAPI 2.0 flow with the account provider
  2. Account provider verifies the user’s identity and maps to their did:key
  3. Account provider constructs a Verifiable Presentation (VP) on behalf of the user using their KMS-held key — the VP contains the user’s participation credential(s)
  4. PDTF service verifies the VP via OID4VP, confirms participation credential validity and federation trust chain, grants access
Agent → FAPI 2.0 → Account Provider → KMS Sign VP → Agent → OID4VP → PDTF Service → Verify → Access

When users hold their own keys (wallet binding), standard OID4VP flow:

  1. PDTF service sends presentation request with a presentation definition specifying required credential types
  2. User’s wallet constructs VP containing the requested credentials, signed with the wallet key
  3. PDTF service verifies the VP signature, credential validity, federation trust chain, and revocation status

In both phases, the participation credential is the authorization — no separate role-check needed.

12.4 Platform-to-Platform Sync & VC Encryption

Section titled “12.4 Platform-to-Platform Sync & VC Encryption”

Phase 1 note: Envelope encryption is the target architecture for multi-platform sync. In Phase 1, where the reference platform is the sole platform, VC encryption is not implemented — data access is controlled through platform-level authentication and termsOfUse filtering. The encryption model described here will be specified in detail in Sub-spec 12 when multi-platform sync is introduced.

Platforms (LMS systems, conveyancer software, orchestrators) need to sync VC collections to ensure all participants have the latest data. The challenge: GDPR exposure. A platform shouldn’t hold decryptable personal data for transactions it’s not a participant in.

Solution: Envelope encryption on credential content.

PDTF uses ECDH-ES+A256KW (Elliptic Curve Diffie-Hellman Ephemeral Static + AES-256 Key Wrap) with X25519 key agreement, complementing the Ed25519 signing keys already in the architecture:

  1. Every DID document includes a keyAgreement verification method — an X25519 public key derived from (or alongside) the Ed25519 signing key
  2. Credential content is encrypted with a random AES-256-GCM content encryption key (CEK)
  3. The CEK is wrapped (encrypted) per-recipient — one wrapped key per participant DID that has access rights (based on termsOfUse role restrictions)
  4. The encrypted VC is a JWE (JSON Web Encryption) envelope containing: encrypted payload + per-recipient wrapped keys + metadata
{
"protected": { "alg": "ECDH-ES+A256KW", "enc": "A256GCM" },
"recipients": [
{ "header": { "kid": "did:key:z6Mk...#key-agreement" }, "encrypted_key": "..." },
{ "header": { "kid": "did:web:smithandjones.co.uk#key-agreement" }, "encrypted_key": "..." },
{ "header": { "kid": "did:web:platform.example.com#key-agreement" }, "encrypted_key": "..." }
],
"iv": "...",
"ciphertext": "...",
"tag": "..."
}

With envelope encryption, platforms can sync freely:

  • Replicate all encrypted VCs for a transaction — they’re opaque blobs to non-participants
  • Only participants with a wrapped key can decrypt — decryption requires the X25519 private key matching one of the recipients
  • A platform that can’t decrypt is not a data controller for that content under GDPR
  • Revocation status is still public (Bitstring Status Lists are unsigned data) — platforms can check revocation without decrypting content
  • VC signatures remain valid inside the encryption envelope — decrypt first, then verify the inner VC proof

When a new participant joins a transaction (e.g. buyer’s conveyancer appointed):

  1. New Representation credential is issued via OID4VCI
  2. Existing encrypted VCs are re-wrapped — the CEK for each VC is wrapped with the new participant’s X25519 public key and added to the recipients array
  3. No re-encryption of the content needed — only the key wrapping changes

When a participant is removed or a credential is revoked:

  1. The revoked participant’s wrapped key entry is removed
  2. Optionally, re-key: generate new CEK, re-encrypt content, wrap for remaining participants (provides forward secrecy)

Not all VCs need encryption. The encryption model follows the existing termsOfUse confidentiality levels:

ConfidentialityEncryptionRationale
publicNone — plaintext VCPublic register data, EPC ratings
transactionParticipantsEncrypted, all participant DIDs as recipientsMost property data
roleRestrictedEncrypted, only matching-role DIDs as recipientsFinancial data, personal details
partyOnlyEncrypted, only the data subject’s DID as recipientIdentity verification details

X25519 key agreement keys are derived alongside Ed25519 signing keys:

  • did:key (Persons, managed Organisations): The Ed25519 private key is converted to an X25519 private key using the birational map (RFC 7748 §6.1). The did:key document implicitly includes both verification and key agreement methods.
  • did:web (self-hosting Organisations, Transactions): The DID document explicitly lists both an Ed25519VerificationKey2020 (for signing) and an X25519KeyAgreementKey2020 (for encryption) in verificationMethod, with the latter referenced from keyAgreement.

(Full spec: papers/pdtf-v2/11-api-design.md — TODO, papers/pdtf-v2/12-adapter-access-control.md — TODO)

PDTF 2.0 uses did:web domains as the structural trust boundary between environments. A credential signed in staging is cryptographically untrusted in production because the issuer DID resolves to a different domain — no configuration flags or environment variables control this; it is an intrinsic property of the identifier.

TierIdentity ModelKey StorageTrust SourceDID Document Hosting
Local devdid:key onlyIn-memoryLocal JSON fileNone (no did:web resolution needed)
Stagingdid:web:*.staging.propdata.org.ukFirestoreStaging federation metadataGCS staging bucket
Productiondid:web:*.propdata.org.ukCloud KMS (HSM-backed)Production federation trust anchorGCS prod bucket + CDN
Production: did:web:adapters.propdata.org.uk:{adapter}
Staging: did:web:adapters.staging.propdata.org.uk:{adapter}
Production: did:web:transactions.propdata.org.uk:txn:{id}
Staging: did:web:transactions.staging.propdata.org.uk:txn:{id}
Production: did:web:auth.platform.example.com
Staging: did:web:auth.staging.platform.example.com
Federation:
Production: https://propdata.org.uk/.well-known/openid-federation
Staging: https://staging.propdata.org.uk/.well-known/openid-federation
  • Adapter DIDs — different domain, different key pairs, different DID documents
  • Transaction DIDs — different domain prefix, separate GCS bucket / CDN
  • Account provider DID — different auth domain
  • Federation trust anchor — separate trust anchor with separate signing key
  • Trust marks — issued by environment-specific trust anchor
  • Status lists — separate hosting domain (status.staging.propdata.org.uk)
  • Key material — staging uses Firestore for convenience; production uses Cloud KMS with audit logging
  • Application code — @pdtf/core, adapters, CLI tooling are environment-agnostic. The environment is determined entirely by configuration: which domain, which KeyProvider implementation, which federation trust anchor URL.
  • Schemas — entity graph structure, VC data model, and JSON Schema definitions are identical across all tiers.
  • Trust mark definitions — the trust mark IDs and their semantics are the same; only the issuer differs.

Because did:web encodes the domain into the identifier itself, a staging credential presented to a production verifier will fail federation trust chain resolution — the issuer’s entity configuration points to the staging trust anchor, not the production one. This is not a policy check; it is a structural impossibility. No “wrong environment” bug can cause staging data to be trusted in production unless someone manually copies keys, federation metadata, and trust marks between environments.

For local development and unit testing, did:key eliminates all infrastructure dependencies. The MemoryKeyProvider generates ephemeral keys, the VcValidator resolves did:key DIDs locally without network access, and an in-memory mock federation (Entity Statements + Trust Marks signed by a dev-only Trust Anchor key) replaces the live trust.pdtf.org endpoints. This means a developer can sign, verify, and compose credentials on a laptop with zero cloud access.


#DocumentStatusDescription
0000-architecture-overview.mdThis documentMaster reference
0101-entity-graph.mdDRAFTEDV4 schema decomposition, ID-keyed collections, entity relationships
0202-vc-data-model.mdDRAFTEDW3C VC mapping, evidence model, termsOfUse, claims representation
0303-did-methods.mdDRAFTEDdid:key, did:web, URN schemes, DID document structure
0404-openid-federation.mdDraftedOpenID Federation trust architecture: Trust Anchor, Entity Statements, Trust Marks, entity:path delegation claim.
0505-hosted-adapter-services.mdTODOAdapter architecture as OID4VCI issuers, federation leaf entities, issuance flow, deployment
0606-key-management.mdDRAFTEDGoogle Cloud KMS, key hierarchy, rotation, wallet binding, federation key handling. X25519 encryption key management deferred to Sub-spec 12.
0707-state-assembly.mdDRAFTEDcomposeV3/V4StateFromGraph, dependency pruning, migration
0808-diligence-engine-migration.mdTODOentity:path mapping, pdtfPaths.js evolution
0909-nptn-integration.mdTODOOID4VP credential exchange through NPTN, LMS migration guide
1010-lms-documentation.mdTODOArchitecture guide for LMS stakeholders
1111-api-design.mdTODOFAPI 2.0 security profile, MCP + OpenAPI interface, OID4VCI/OID4VP flows, AI agent skills
1212-access-control-and-encryption.mdTODOOID4VP authentication, VP presentation, VC envelope encryption (Phase 2+), platform sync
1313-reference-implementations.mdDRAFTEDVC validator, federation client, graph composer, DID resolver specs
1414-credential-revocation.mdDRAFTEDBitstring Status List hosting, revocation flows, cache strategy
1515-conformance-testing.mdDRAFTEDConformance levels, test vectors, interop protocols

All architectural decisions made through v0.3 of this document are baked into the spec text above. The decision log below tracks the consensus questions identified for industry review.

#QuestionDecisionDate
Q1.2Credential granularity for seller attestationsPDTF does not prescribe granularity. Issuers choose what subtree to assert per credential. This tradeoff absorbs into Q1.1 — see resolution note there.Apr 2026
Q1.3Multi-credential merge conflictsPDTF does not prescribe conflict resolution logic. The state assembly library provides a simple timestamp-ordered merge as a convenience; verifiers apply their own business logic (trust level weighting, recency, source preference) on top. All underlying credentials remain available for inspection.Apr 2026
Q2.2Trust-level conflict visibilityConflict surfacing is a verifier/UI concern, not a spec requirement. All trust levels and sources are carried in the credentials themselves, so consumers can render them however they wish.Apr 2026
Q3.3Credential id requiredYes — every credential MUST include an id for deduplication during state assembly. Privacy implications of credential correlation are secondary to assembly determinism. Format: urn:pdtf:vc:{uuid}.Apr 2026
Q4.1Organisation DID hosting for small firmsBoth self-hosted did:web and orchestrator-hosted DIDs are supported. In Phase 1 and beyond, small firms are expected to use orchestrator-hosted identities — orchestrators provide the account and auth UX firms already rely on, and manage DIDs on their behalf.Apr 2026
Q4.2Lender access patternRevised Oct 2026 (D32). Lenders are parties to the transaction: added to the roster and issued a TransactionRoleCredential (role: "Lender") per application (see sub-spec 02 §3.7). Restricted data requires a relationship credential on the transaction (SellerCapacity / Offer / Representation / TransactionRole), composed with termsOfUse.roleRestrictions. The earlier DelegatedConsentCredential, which carried an access scope and expiry, is withdrawn as an entity; whether a scoped consent credential should be layered on top for finer-grained access is an open consultation question. No role-based lender pooling.Oct 2026
Q5.2Multiple issuers per pathPermitted and expected. Multiple commercial search providers, valuation services, and similar will legitimately issue credentials against the same entity:path combinations. Trust marks do not enforce exclusivity.Apr 2026
Q6.1–Q6.3Migration strategyMigration proceeds by running PDTF v1 and v2 operations in parallel. New transactions start on v2; in-flight transactions continue on v1 until they close. When all active transactions support v2 output, v1 is retired. State assembly supports both formats throughout the overlap.Apr 2026
#QuestionSpec RefDecisionDate
Q1.1Claims merge strategy: REPLACE vs MERGE vs hybrid.02 §5, 07 §4Issuer-driven credential granularity cannot be mandated (see Q1.2 resolution), which weakens the REPLACE case: REPLACE requires issuers to understand path boundaries precisely. MERGE with schema-driven pruning is simpler for issuers but requires the assembler to understand dependencies. Tradeoff still open for industry consensus.
Q2.1Multi-property transactions: how do overlays, form mappings, and v3 propertyPack (singular) handle multiple properties?01 §9.1, 07 §12.1

Decomposing the v1 monolithic property pack schema into entity-scoped schemas raises design questions that weren’t captured in the original themes. The Entity Graph (sub-spec 01) defines the entities and their relationships, but the exact field-level seams between them require validation. This theme collects those decisions.

#QuestionPreferred directionStatus
Q7.1Where does the schema-level ownership object decompose to?Top-level properties of v1 ownership (numberOfSellers, outstandingMortgage, existingLender, helpToBuyEquityLoan, limitedCompanySale, etc.) move to Transaction.saleContext.*. The legal interest being transferred (formerly ownershipsToBeTransferred[]) moves to the top level of the Title entity, because a TitleCredential fundamentally represents an ownership interest being conveyed. Supporting register evidence (register extract, charges) moves under a title sub-object on the Title entity. Note: this is the schema decomposition question — distinct from the entity-graph-level SellerCapacity credential (the thin Person↔Title assertion, see sub-spec 02 §3.4), which is unchanged.Preferred
Q7.2Identifier for unregistered titlesurn:pdtf:unregisteredTitle:{uuid} per sub-spec 03 §10.1, but UUID derivation method (v4 random vs v5 deterministic from UPRN) and first-registration transition mechanism still open. This is a hard dependency for Q7.1 — Title.ownershipToBeTransferred applies to registered and unregistered titles equally, so the identifier question blocks finalisation.Open
Q7.3Field-level seams between Property and TitleThe boundary between physical property facts (EPC, flood, construction) and legal title facts (charges, proprietorship, lease terms) is clear in principle, but edge cases exist (e.g., boundary disputes, rights of way). Validate on a field-by-field basis during schema review.Open

Reordered to reflect the OpenID ecosystem alignment:

  1. Entity graph spec (01) — formalise v4 schemas, build on existing branch work
  2. VC data model (02) — define the credential format, evidence, termsOfUse
  3. OpenID Federation (04) — Trust Anchor, Entity Statements, Trust Marks, entity:path delegation authorisation
  4. DID methods (03) — key generation, DID document hosting, federation entity configuration
  5. Key management (06) — Google Cloud KMS setup, federation key handling
  6. One adapter PoC (05) — EPC adapter as OID4VCI credential issuer with federation entity configuration
  7. State assembly (07) — composeV3StateFromGraph with validation against existing output
  8. Reference implementations (13) — VC validator with federation trust chain resolution, federation client
  9. API design (11) — FAPI 2.0 security profile, OID4VCI/OID4VP endpoints, MCP binding
  10. Access control (12) — OID4VP participation credential verification
  11. DE migration (08) — entity:path mapping
  12. NPTN integration (09) — OID4VP credential exchange design for LMS
  13. LMS documentation (10) — stakeholder guide