02 Verifiable Credentials Data Model
Version: 0.1 (Draft) Date: 9 April 2026 Author: Ed Molyneux Status: Draft Parent: 00 — Architecture Overview
1. Purpose
Section titled “1. Purpose”This sub-spec defines how PDTF property data is represented as Verifiable Credentials. It specifies the credential types, their structure, the claims representation model, evidence, terms of use, revocation, the securing mechanism (SD-JWT-VC — provisional; see §2.4), and the type metadata / optional JSON-LD context that binds it all together.
Every piece of property data in PDTF 2.0 — from an EPC rating to an ownership assertion to a conveyancer’s mandate — is a signed, independently verifiable credential. This document is the authoritative reference for how those credentials are structured.
Scope:
- Credential envelope structure (W3C VC 2.0 conformance)
- PDTF-specific credential types and their
credentialSubjectshapes - Claims representation: sparse objects with MERGE semantics and dependency pruning
- Evidence model (simplified from OIDC-derived schema)
- Terms of use (access control metadata)
- Credential status (revocation via Bitstring Status List)
- Securing mechanism (SD-JWT-VC — provisional; Data Integrity / eddsa-jcs-2022 retained as superseded fallback)
- Type metadata (
vct) and optional JSON-LD context definition - Migration path from current OIDC verified claims
Out of scope:
- Entity graph structure and field mapping (see 01 — Entity Graph)
- DID methods and resolution (see 03 — DID Methods)
- OpenID Federation trust architecture and Trust Mark schema (see 04 — OpenID Federation)
- State assembly algorithms (see 07 — State Assembly)
- Bitstring Status List hosting infrastructure (see 14 — Credential Revocation)
2. W3C VC 2.0 Conformance
Section titled “2. W3C VC 2.0 Conformance”PDTF 2.0 credentials conform to the W3C Verifiable Credentials Data Model v2.0 (CR as of 2024). This section specifies which parts of the W3C model we use, which we constrain, and which we omit.
2.1 Required Properties
Section titled “2.1 Required Properties”Every PDTF credential MUST include:
| Property | W3C Status | PDTF Requirement | Notes |
|---|---|---|---|
@context | Optional | Optional | Optional JSON-LD semantic overlay only (§10) — not required under SD-JWT-VC |
type | Required | Required | Always includes VerifiableCredential + PDTF-specific type |
issuer | Required | Required | DID string (not object form) |
validFrom | Optional in W3C | Required in PDTF | ISO 8601 datetime — when data was asserted/retrieved |
credentialSubject | Required | Required | Single subject (not array). id is always present. |
credentialStatus | Optional in W3C | Required in PDTF | BitstringStatusListEntry — see §8 |
proof | — | — | Under SD-JWT-VC the credential is JWS-secured (§9), not an embedded proof; the proof block appears only in the superseded Data-Integrity representation |
2.2 Optional Properties (Used)
Section titled “2.2 Optional Properties (Used)”| Property | PDTF Usage | Notes |
|---|---|---|
id | Required | Credential identifier URI. REQUIRED for deduplication (Q3.3 resolved). Format: urn:pdtf:vc:{uuid}. |
validUntil | Optional | Expiry datetime. Used for time-limited credentials (e.g. EPC with known expiry). |
evidence | Used | Simplified evidence model — see §6 |
termsOfUse | Used | PdtfAccessPolicy — see §7 |
2.3 Optional Properties (Not Used)
Section titled “2.3 Optional Properties (Not Used)”| Property | Reason for Omission |
|---|---|
credentialSchema | Schema validation handled by PDTF tooling against entity schemas, not via in-credential schema references. May revisit for interoperability. |
refreshService | Not needed — credentials are re-issued when data changes, not refreshed in-place. |
name / description | Human-readable metadata not needed for machine-processed property data. |
relatedResource | Not needed in initial implementation. |
2.4 Securing Mechanism
Section titled “2.4 Securing Mechanism”PDTF secures credentials as SD-JWT-VC (IETF SD-JWT VC) — a JWS-signed credential with selective disclosure and holder key binding — and verifies mdoc (ISO/IEC 18013-5) at the GOV.UK identity boundary. Rationale:
- Ecosystem alignment — SD-JWT-VC + mdoc are the formats mandated by eIDAS 2.0 and profiled by OpenID4VC HAIP; the GOV.UK Wallet issues mdoc. Plain JSON-LD Data Integrity is issued by neither.
- Selective disclosure / data minimisation — native to SD-JWT-VC (salted-hash disclosures), so a holder presents only the fields a party needs, governed by
termsOfUse. (This reverses the earlier assumption that selective disclosure was unnecessary for property data.) - Holder binding — SD-JWT-VC key binding (
cnf+ Key Binding JWT) gives cryptographic proof of possession at presentation — the same primitive the cross-party identity-reuse flow relies on (03 §10.5). - Legibility — SD-JWT-VC decodes to plain JSON (unlike binary-CBOR mdoc), so credentials stay developer- and inspection-friendly. (AI-agent legibility is driven by the composed entity graph exposed over the API, not the wire format — see §13.4.)
Superseded approach. Earlier drafts used embedded W3C Data Integrity proofs (DataIntegrityProof, eddsa-jcs-2022) over JSON-LD; that is retained as a documented fallback/alternative in §9.1 but is no longer primary. PDTF MAY keep a JSON-LD @context purely as an optional semantic overlay (§10), decoupled from the securing mechanism.
2.5 Credential Subject Constraints
Section titled “2.5 Credential Subject Constraints”PDTF credentials use a single credentialSubject (not an array). The credentialSubject.id is always present and MUST be a valid DID or URN from the PDTF identifier scheme (see 01 — Entity Graph §5).
{ "credentialSubject": { "id": "urn:pdtf:uprn:100023456789", "energyEfficiency": { ... } }}Not:
{ "credentialSubject": [ { "id": "urn:pdtf:uprn:100023456789", ... }, { "id": "urn:pdtf:uprn:200034567890", ... } ]}If a credential needs to make assertions about multiple entities, issue separate credentials. This keeps the trust chain clean: one issuer, one subject, one set of claims, one proof.
3. PDTF Credential Types
Section titled “3. PDTF Credential Types”Each entity type in the PDTF entity graph (see 01 — Entity Graph) has a corresponding credential type. The credential type determines the expected shape of credentialSubject and the valid credentialSubject.id identifier format.
3.1 Type Summary
Section titled “3.1 Type Summary”| Credential Type | Entity | Subject ID Format | Issuer | Description |
|---|---|---|---|---|
PropertyCredential | Property | urn:pdtf:uprn:{uprn} | Trusted proxy / root issuer / user | Property facts: EPC, flood, build info, legal questions, fixtures, searches |
TitleCredential | Title | urn:pdtf:titleNumber:{n} or urn:pdtf:unregisteredTitle:{id} | HMLR proxy / root issuer | Register extract, ownership type, leasehold terms, encumbrances |
SellerCapacityCredential | SellerCapacity | urn:pdtf:capacity:{id} | Account provider | The capacity in which a party sells. Implies Seller. |
OfferCredential | Offer | urn:pdtf:offer:{id} | Buyer (Person) or platform | Buyer DID, amount, status, conditions. Implies Buyer. |
GiftCredential | Gift | urn:pdtf:gift:{id} | Giftor or platform | Donor DID, offer, gift terms. Implies Giftor. |
RepresentationCredential | Representation | urn:pdtf:representation:{id} | Instructing party (Person/Org) | Representative DID, represented party DID, kind of representation |
TransactionRoleCredential | TransactionRole | urn:pdtf:role:{id} | Platform | Participant DID, role, where no more specific relationship applies |
TransactionCredential | Transaction | did:web:... | Platform | Transaction metadata, status, milestones, financial context |
3.2 PropertyCredential
Section titled “3.2 PropertyCredential”Purpose: Asserts facts about the physical property that travel with the property across transactions (the logbook — see 01 §2.1).
Subject ID: urn:pdtf:uprn:{uprn} — the property’s Unique Property Reference Number.
Credential subject shape: Sparse subset of the Property entity schema. A single PropertyCredential covers one or more paths on the Property entity. It does NOT need to contain the full Property schema — only the paths the issuer is asserting.
Key design decision (D4): EPC data, flood risk, searches, and other property facts are represented as PropertyCredentials with paths on the Property entity — not as separate first-class entity types. An EPC is a PropertyCredential with credentialSubject.energyEfficiency, not an “EPCCredential”. This keeps the entity model clean and the credential type set small. Primary issuers (MHCLG, EA) will use the same paths when they adopt the standard.
Example paths and typical issuers:
| Path | Data | Typical Issuer |
|---|---|---|
energyEfficiency.* | EPC certificate, recommendations | EPC proxy / MHCLG root issuer |
environmentalIssues.flooding.* | Flood risk zones, history | EA proxy / EA root issuer |
buildInformation.* | Build date, type, materials | Seller (UserAttestation) |
residentialPropertyFeatures.* | Bedrooms, bathrooms, parking | Estate agent / seller |
heating.* | Heating system type, fuel | Seller (UserAttestation) |
fixturesAndFittings.* | What’s included/excluded | Seller (UserAttestation) |
councilTax.* | Band, amount | VOA proxy / VOA root issuer |
localSearches.* | Local land charges, authority searches | LLC proxy / search provider |
searches.* | Environmental, drainage searches | Search provider |
disputesAndComplaints.* | Boundary disputes | Seller (UserAttestation) |
alterationsAndChanges.* | Planning, building regs | Seller + council records |
connectivity.* | Broadband, mobile | Ofcom data / seller |
address.* | Property address | Platform / OS AddressBase |
Minimal PropertyCredential (EPC only) — shown as the SD-JWT-VC issuer-signed payload (decoded; the compact JWS and any selective-disclosure digests are omitted for readability — see §9 for the full envelope). This is a public adapter credential, so it is not holder-bound (no cnf):
{ "iss": "did:web:adapters.propdata.org.uk:epc", "vct": "urn:pdtf:vct:PropertyCredential", "sub": "urn:pdtf:uprn:100023456789", "iat": 1774339200, "exp": 2088547200, "status": { "status_list": { "idx": 18293, "uri": "https://adapters.propdata.org.uk/status/epc/list-042" } }, "credentialSubject": { "id": "urn:pdtf:uprn:100023456789", "energyEfficiency": { "certificate": { "certificateNumber": "1234-5678-9012-3456-7890", "currentEnergyRating": "C", "currentEnergyEfficiency": 72, "potentialEnergyRating": "B", "potentialEnergyEfficiency": 85, "lodgementDate": "2024-01-15", "expiryDate": "2034-01-15" } } }, "evidence": [{ "type": "ElectronicRecord", "source": "get-energy-performance-data.communities.gov.uk", "retrievedAt": "2026-03-24T09:58:00Z", "method": "API" }], "termsOfUse": [{ "type": "PdtfAccessPolicy", "confidentiality": "public", "pii": false }]}The signature is the enclosing JWS (§9), not an embedded proof; revocation is the status (Token Status List) claim, replacing the former credentialStatus.
Seller-attested PropertyCredential (heating):
{ "@context": [ "https://www.w3.org/ns/credentials/v2", "https://trust.propdata.org.uk/ns/pdtf/v2" ], "type": ["VerifiableCredential", "PropertyCredential"], "issuer": "did:web:platform.example.com", "validFrom": "2026-03-20T14:30:00Z", "credentialSubject": { "id": "urn:pdtf:uprn:100023456789", "heating": { "heatingSystem": { "heatingType": "Central heating", "centralHeatingDetails": { "fuelType": "Mains gas", "boilerType": "Combination boiler", "boilerAge": "5-10 years" } } } }, "evidence": [{ "type": "UserAttestation", "source": "did:key:z6MkhSellerAbc123", "attestedAt": "2026-03-20T14:30:00Z", "method": "BASPI form completion" }], "termsOfUse": [{ "type": "PdtfAccessPolicy", "confidentiality": "restricted", "pii": false, "roleRestrictions": ["sellerConveyancer", "buyerConveyancer", "estateAgent", "buyer"] }], "credentialStatus": { "id": "https://api.platform.example.com/status/property/list-007#4521", "type": "BitstringStatusListEntry", "statusPurpose": "revocation", "statusListIndex": "4521", "statusListCredential": "https://api.platform.example.com/status/property/list-007" }, "proof": { "type": "DataIntegrityProof", "cryptosuite": "eddsa-jcs-2022", "verificationMethod": "did:web:platform.example.com#platform-key-1", "proofPurpose": "assertionMethod", "created": "2026-03-20T14:30:00Z", "proofValue": "z3hQ8xNr..." }}Note on issuer for user attestations: When a seller fills in a form, the platform signs the credential on their behalf (custodial key management — see D14 in Architecture Overview). The evidence section records that the attestation came from the seller’s DID. When wallet-held keys are available (future), the seller will sign directly.
3.3 TitleCredential
Section titled “3.3 TitleCredential”Purpose: Asserts facts about the legal title — register data, ownership type, leasehold terms, encumbrances.
Subject ID: urn:pdtf:titleNumber:{number} for registered titles, urn:pdtf:unregisteredTitle:{id} for unregistered titles.
Credential subject shape: Sparse subset of the Title entity schema.
Typical issuer: HMLR proxy adapter (trusted proxy for HMLR data), or HMLR directly as root issuer (future).
{ "@context": [ "https://www.w3.org/ns/credentials/v2", "https://trust.propdata.org.uk/ns/pdtf/v2" ], "type": ["VerifiableCredential", "TitleCredential"], "issuer": "did:web:adapters.propdata.org.uk:hmlr", "validFrom": "2026-03-24T08:15:00Z", "credentialSubject": { "id": "urn:pdtf:titleNumber:AB12345", "ownershipType": "Freehold", "title": { "registerExtract": { "proprietorship": { "owners": [ { "name": "John Smith", "address": "1 Example Street, London, SW1A 1AA" } ], "priceStatedPaid": 350000, "dateOfRegistration": "2018-06-15" }, "restrictions": [], "charges": [ { "chargee": "Nationwide Building Society", "dateOfCharge": "2018-06-15" } ] } } }, "evidence": [{ "type": "ElectronicRecord", "source": "landregistry.data.gov.uk", "retrievedAt": "2026-03-24T08:14:00Z", "method": "OC1 API" }], "termsOfUse": [{ "type": "PdtfAccessPolicy", "confidentiality": "restricted", "pii": true, "roleRestrictions": ["sellerConveyancer", "buyerConveyancer"] }], "credentialStatus": { "id": "https://adapters.propdata.org.uk/status/hmlr/list-015#7742", "type": "BitstringStatusListEntry", "statusPurpose": "revocation", "statusListIndex": "7742", "statusListCredential": "https://adapters.propdata.org.uk/status/hmlr/list-015" }, "proof": { "type": "DataIntegrityProof", "cryptosuite": "eddsa-jcs-2022", "verificationMethod": "did:web:adapters.propdata.org.uk:hmlr#key-1", "proofPurpose": "assertionMethod", "created": "2026-03-24T08:15:00Z", "proofValue": "zR9kW2pL..." }}Leasehold TitleCredential example (partial):
{ "credentialSubject": { "id": "urn:pdtf:titleNumber:CD67890", "ownershipType": "Leasehold", "leaseholdDetails": { "originalLeaseLength": 125, "remainingLeaseLength": 98, "leaseStartDate": "2001-03-01", "groundRent": { "amount": 250, "frequency": "Annual", "reviewType": "Fixed" }, "serviceCharge": { "annualAmount": 1800, "managingAgent": "ABC Property Management Ltd" }, "freeholderName": "Freehold Estates Ltd" }, "title": { "registerExtract": { ... } } }}3.4 SellerCapacityCredential
Section titled “3.4 SellerCapacityCredential”Purpose: A thin signed assertion of the capacity in which a Person or Organisation sells — legal owner, personal representative for a deceased owner, attorney under a power of attorney, mortgagee in possession, company director, trustee. It embodies the Seller role: the Transaction roster carries no role, so a party is a seller because this credential says so (01 §3.2, D32).
Key design decision (D28): The SellerCapacityCredential does NOT duplicate title register details. Those belong on the TitleCredential. The claim is verified by cross-referencing against Title.registerExtract.proprietorship — claim-vs-evidence separation. The SellerCapacityCredential says “X sells, as legal owner”. The TitleCredential provides the evidence from HMLR that proves it.
Subject ID: urn:pdtf:capacity:{id} — a generated URN for this assertion.
Issuer: The account provider that verified the user’s identity and cross-referenced against the title register. Issued for every seller, whether or not a capacity has been declared yet, so the credential embodying the Seller role is never missing while a form is still being filled in.
{ "@context": [ "https://www.w3.org/ns/credentials/v2", "https://trust.propdata.org.uk/ns/pdtf/v2" ], "type": ["VerifiableCredential", "SellerCapacityCredential"], "issuer": "did:web:platform.example.com", "validFrom": "2026-03-18T09:00:00Z", "credentialSubject": { "id": "urn:pdtf:capacity:own-a1b2c3", "seller": "did:key:z6MkhSellerAbc123", "transaction": "did:web:platform.example.com:transactions:tx-789", "sellersCapacity": { "capacity": "Legal Owner" }, "dateBecameOwnerOrAuthority": "2014-06-01" }, "evidence": [{ "type": "ElectronicRecord", "source": "landregistry.data.gov.uk", "retrievedAt": "2026-03-18T08:55:00Z", "method": "OC1 proprietorship name match" }], "termsOfUse": [{ "type": "PdtfAccessPolicy", "confidentiality": "restricted", "pii": true, "roleRestrictions": ["Seller's Conveyancer", "Buyer's Conveyancer", "Estate Agent"] }], "credentialStatus": { "id": "https://api.platform.example.com/status/capacity/list-001#892", "type": "BitstringStatusListEntry", "statusPurpose": "revocation", "statusListIndex": "892", "statusListCredential": "https://api.platform.example.com/status/capacity/list-001" }, "proof": { "type": "DataIntegrityProof", "cryptosuite": "eddsa-jcs-2022", "verificationMethod": "did:web:platform.example.com#platform-key-1", "proofPurpose": "assertionMethod", "created": "2026-03-18T09:00:00Z", "proofValue": "z5tPqR7s..." }}SellerCapacityCredential fields:
| Field | Type | Required | Description |
|---|---|---|---|
seller | DID string | Required | DID of the Person or Organisation selling |
transaction | DID string | Required | did:web of the transaction |
title | URN string | Optional | urn:pdtf:titleNumber:* or urn:pdtf:unregisteredTitle:*, where a capacity is specific to one of several titles |
sellersCapacity.capacity | enum | Optional | Legal Owner, Personal Representative for a Deceased Owner, Under Power of Attorney, Mortgagee in Possession, Company Director, Company Secretary, Trustee, Assistant, Other |
dateBecameOwnerOrAuthority | ISO date | Optional | When the seller acquired the title or the authority to sell it |
How verified: the degree of verification is carried in evidence, not in the subject: UserAttestation (the seller’s own declaration), ElectronicRecord (OC1 proprietorship cross-reference), ProfessionalVerification (conveyancer confirmation). A verifier reads the strongest evidence present.
Why thin? A verifier who wants to confirm the right to sell checks:
- The SellerCapacityCredential links Person DID X to the Transaction with a declared capacity
- The TitleCredential for each title in
Transaction.titlesToBeSoldhasregisterExtract.proprietorshipshowing the registered owner - The two are consistent — the SellerCapacityCredential’s
evidencepoints back to the register cross-reference - Both credentials are signed and not revoked
This separation means the capacity can be revoked (sale completes, a personal representative is replaced) without affecting the title register data. And title data can be updated (charge removed) without re-issuing the capacity assertion.
3.5 RepresentationCredential
Section titled “3.5 RepresentationCredential”Purpose: Records that one party has been instructed by another: “I, the seller, instruct Smith & Co Solicitors as my conveyancer.” It carries the kind of representation as its role — a property of the representation, not a duplicated participant attribute. Acting for a seller tells you the side, not whether the party is the conveyancer, the agent or the surveyor.
Subject ID: urn:pdtf:representation:{id} — a generated URN for this representation.
Issuer: The party granting the authority. In practice, during Phase 1, the platform signs on behalf of the person (custodial keys), so the issuer is did:web:platform.example.com and the evidence records the person’s DID and their explicit instruction.
Key design decision (D33): One credential per (representative, represented party) pair. A conveyancer instructed jointly by two sellers holds two, each presentable or revocable on its own. A separated couple instructing their own conveyancers yields one each. The retainer is with the client, so representedParty is a person or organisation, never an offer — a prospective buyer with no offer yet can still be represented. The representative’s firm is recorded on the Transaction roster, not here, because where someone works does not stop being true when a representation ends; the professional duty, PI insurance and regulatory obligations are resolved through that firm.
{ "@context": [ "https://www.w3.org/ns/credentials/v2", "https://trust.propdata.org.uk/ns/pdtf/v2" ], "type": ["VerifiableCredential", "RepresentationCredential"], "issuer": "did:web:platform.example.com", "validFrom": "2026-03-15T11:00:00Z", "credentialSubject": { "id": "urn:pdtf:representation:rep-d4e5f6", "representative": "did:key:z6MkhConveyancerDef456", "representedParty": "did:key:z6MkhSellerAbc123", "role": "Seller's Conveyancer", "transaction": "did:web:platform.example.com:transactions:tx-789" }, "evidence": [{ "type": "UserAttestation", "source": "did:key:z6MkhSellerAbc123", "attestedAt": "2026-03-15T10:55:00Z", "method": "Platform instruction flow" }], "termsOfUse": [{ "type": "PdtfAccessPolicy", "confidentiality": "restricted", "pii": true, "roleRestrictions": ["Seller's Conveyancer", "Buyer's Conveyancer"] }], "credentialStatus": { "id": "https://api.platform.example.com/status/representation/list-001#334", "type": "BitstringStatusListEntry", "statusPurpose": "revocation", "statusListIndex": "334", "statusListCredential": "https://api.platform.example.com/status/representation/list-001" }, "proof": { "type": "DataIntegrityProof", "cryptosuite": "eddsa-jcs-2022", "verificationMethod": "did:web:platform.example.com#platform-key-1", "proofPurpose": "assertionMethod", "created": "2026-03-15T11:00:00Z", "proofValue": "zK8mN4rJ..." }}RepresentationCredential fields:
| Field | Type | Required | Description |
|---|---|---|---|
representative | DID string | Required | DID of the Person or Organisation who is instructed |
representedParty | DID string | Required | DID of the Person or Organisation who instructed them |
role | enum | Required | The kind of representation: Seller's Conveyancer, Buyer's Conveyancer, Estate Agent, Buyer's Agent, Mortgage Broker, Surveyor (the v3 participant role enum) |
transaction | DID string | Required | did:web of the transaction this applies to |
Revocation is critical: When a seller changes conveyancer, the old RepresentationCredential MUST be revoked. Without revocation, a former conveyancer could still present a valid credential. Because role lives only here, revocation also removes the “Seller’s Conveyancer” role from the transaction. See §8 for the revocation mechanism.
3.6 GiftCredential
Section titled “3.6 GiftCredential”Purpose: Records that a party is gifting funds towards a purchase, and the terms a conveyancer must resolve before reporting to a lender: whether the gift is repayable, whether the giftor will have a beneficial interest in the property, whether they will occupy it. It embodies the Giftor role.
Subject ID: urn:pdtf:gift:{id} — a generated URN for this gift.
Issuer: The platform on behalf of the giftor.
The giftor’s link to the offer lives here (offerId) rather than on the Offer, so that an OfferCredential always identifies a buyer.
{ "@context": [ "https://www.w3.org/ns/credentials/v2", "https://trust.propdata.org.uk/ns/pdtf/v2" ], "type": ["VerifiableCredential", "GiftCredential"], "issuer": "did:web:platform.example.com", "validFrom": "2026-03-22T16:00:00Z", "credentialSubject": { "id": "urn:pdtf:gift:gf-m4n5o6", "donor": "did:key:z6MkhParentPqr456", "transaction": "did:web:platform.example.com:transactions:tx-789", "offerId": "o1", "giftDetails": { "amount": 50000, "currency": "GBP", "relationshipToBuyer": "Parent", "fundsTransferred": "None", "repayable": false, "confersBeneficialInterest": false, "willOccupyProperty": false } }, "evidence": [{ "type": "UserAttestation", "source": "did:key:z6MkhParentPqr456", "attestedAt": "2026-03-22T15:50:00Z", "method": "Gift declaration flow" }], "termsOfUse": [{ "type": "PdtfAccessPolicy", "confidentiality": "confidential", "pii": true, "roleRestrictions": ["Buyer's Conveyancer", "Lender"] }], "credentialStatus": { "id": "https://api.platform.example.com/status/gift/list-001#156", "type": "BitstringStatusListEntry", "statusPurpose": "revocation", "statusListIndex": "156", "statusListCredential": "https://api.platform.example.com/status/gift/list-001" }, "proof": { "type": "DataIntegrityProof", "cryptosuite": "eddsa-jcs-2022", "verificationMethod": "did:web:platform.example.com#platform-key-1", "proofPurpose": "assertionMethod", "created": "2026-03-22T16:00:00Z", "proofValue": "z7bQm3vR..." }}GiftCredential fields:
| Field | Type | Required | Description |
|---|---|---|---|
donor | DID string | Required | DID of the Person or Organisation making the gift |
transaction | DID string | Required | Transaction scope |
offerId | string | Optional | The offer this gift contributes towards, as keyed in Transaction.offers |
giftDetails | object | Optional | amount, currency, relationshipToBuyer, fundsTransferred (None, Part, All), repayable, confersBeneficialInterest, willOccupyProperty |
3.7 TransactionRoleCredential
Section titled “3.7 TransactionRoleCredential”Purpose: Asserts a party’s role where no more specific relationship credential applies — Lender, Landlord, Tenant, Surveyor, Platform Support — or where the specific relationship is not yet established. Every party carrying a role holds exactly one role-bearing credential; this is the one that guarantees it.
Subject ID: urn:pdtf:role:{id} — a generated URN.
Issuer: The platform.
This is how a lender becomes a party to the transaction (Q4.2 in 00). It asserts participation in a role, not a relationship to another named party. Access is then governed by termsOfUse.roleRestrictions on the credentials the lender requests.
{ "@context": [ "https://www.w3.org/ns/credentials/v2", "https://trust.propdata.org.uk/ns/pdtf/v2" ], "type": ["VerifiableCredential", "TransactionRoleCredential"], "issuer": "did:web:platform.example.com", "validFrom": "2026-03-22T16:00:00Z", "credentialSubject": { "id": "urn:pdtf:role:tr-g7h8i9", "participant": "did:web:bigbank.co.uk", "role": "Lender", "transaction": "did:web:platform.example.com:transactions:tx-789" }, "termsOfUse": [{ "type": "PdtfAccessPolicy", "confidentiality": "restricted", "pii": false, "roleRestrictions": ["Buyer's Conveyancer", "Seller's Conveyancer", "Estate Agent"] }], "credentialStatus": { "id": "https://api.platform.example.com/status/role/list-001#41", "type": "BitstringStatusListEntry", "statusPurpose": "revocation", "statusListIndex": "41", "statusListCredential": "https://api.platform.example.com/status/role/list-001" }, "proof": { "type": "DataIntegrityProof", "cryptosuite": "eddsa-jcs-2022", "verificationMethod": "did:web:platform.example.com#platform-key-1", "proofPurpose": "assertionMethod", "created": "2026-03-22T16:00:00Z", "proofValue": "z9cRn4wS..." }}TransactionRoleCredential fields:
| Field | Type | Required | Description |
|---|---|---|---|
participant | DID string | Required | DID of the Person or Organisation holding the role |
role | enum | Required | The v3 participant role enum: Lender, Landlord, Tenant, Surveyor, Platform Support, … |
transaction | DID string | Required | Transaction scope |
3.7a OfferCredential
Section titled “3.7a OfferCredential”Purpose: Records a buyer’s offer on a transaction. Buyers exist in the transaction only through Offers — this models reality: a buyer doesn’t participate until they make an offer. It embodies the Buyer role, and always identifies exactly one buyer; joint purchasers each hold an OfferCredential with the same offerId.
Subject ID: urn:pdtf:offer:{id} — a generated URN for this offer.
Issuer: The platform on behalf of the buyer. Future: buyer signs directly with wallet-held key.
{ "@context": [ "https://www.w3.org/ns/credentials/v2", "https://trust.propdata.org.uk/ns/pdtf/v2" ], "type": ["VerifiableCredential", "OfferCredential"], "issuer": "did:web:platform.example.com", "validFrom": "2026-03-20T09:30:00Z", "credentialSubject": { "id": "urn:pdtf:offer:off-j1k2l3", "buyer": "did:key:z6MkhBuyerXyz789", "transaction": "did:web:platform.example.com:transactions:tx-789", "offerId": "o1", "amount": 450000, "currency": "GBP", "status": "Accepted", "conditions": [ "Subject to survey", "Subject to mortgage" ], "buyerCircumstances": { "isFirstTimeBuyer": true, "chainStatus": "No chain", "mortgageRequired": true, "mortgageAgreedInPrinciple": true } }, "evidence": [{ "type": "UserAttestation", "source": "did:key:z6MkhBuyerXyz789", "attestedAt": "2026-03-20T09:25:00Z", "method": "Offer submission" }], "termsOfUse": [{ "type": "PdtfAccessPolicy", "confidentiality": "confidential", "pii": true, "roleRestrictions": ["Seller's Conveyancer", "Buyer's Conveyancer", "Estate Agent"] }], "credentialStatus": { "id": "https://api.platform.example.com/status/offers/list-001#2041", "type": "BitstringStatusListEntry", "statusPurpose": "revocation", "statusListIndex": "2041", "statusListCredential": "https://api.platform.example.com/status/offers/list-001" }, "proof": { "type": "DataIntegrityProof", "cryptosuite": "eddsa-jcs-2022", "verificationMethod": "did:web:platform.example.com#platform-key-1", "proofPurpose": "assertionMethod", "created": "2026-03-20T09:30:00Z", "proofValue": "zW2nP9sK..." }}OfferCredential fields:
| Field | Type | Required | Description |
|---|---|---|---|
buyer | DID string | Required | DID of the Person or Organisation making the offer |
transaction | DID string | Required | The transaction this offer is for |
offerId | string | Optional | The key of this offer in Transaction.offers |
amount | number | Optional | Offer amount |
currency | string | Optional | ISO 4217 currency code |
status | enum | Optional | Pending, Accepted, Withdrawn, Rejected, Note of Interest |
conditions | string[] | Optional | Free-text conditions |
inclusions | string[] | Optional | Items included in the offer |
exclusions | string[] | Optional | Items excluded from the offer |
buyerCircumstances | object | Optional | First-time buyer, chain status, mortgage requirement |
3.8 TransactionCredential
Section titled “3.8 TransactionCredential”Purpose: Records transaction metadata — status, milestones, financial context, chain information. The Transaction is the root of the entity graph.
Subject ID: did:web:{host}:transactions:{id} — the transaction’s own DID.
Issuer: The platform hosting the transaction.
Note: The saleContext fields were previously part of the monolithic v1 ownership object and have been decomposed per Q7.1.
{ "@context": [ "https://www.w3.org/ns/credentials/v2", "https://trust.propdata.org.uk/ns/pdtf/v2" ], "type": ["VerifiableCredential", "TransactionCredential"], "issuer": "did:web:platform.example.com", "validFrom": "2026-03-10T12:00:00Z", "credentialSubject": { "id": "did:web:platform.example.com:transactions:tx-789", "status": "Active", "milestones": { "listed": "2026-03-01T00:00:00Z", "saleAgreed": "2026-03-20T00:00:00Z" }, "saleContext": { "numberOfSellers": 1, "numberOfNonUkResidentSellers": 0, "outstandingMortgage": "Yes", "existingLender": "Nationwide", "hasHelpToBuyEquityLoan": "No", "isLimitedCompanySale": "No" }, "property": "urn:pdtf:uprn:100023456789", "titlesToBeSold": ["urn:pdtf:titleNumber:AB12345"], "participants": [ { "participant": "did:key:z6MkhSellerAbc123", "participantId": "s1" }, { "participant": "did:key:z6MkhBuyerXyz789", "participantId": "b1" }, { "participant": "did:key:z6MkhConveyancerDef456", "participantId": "c1", "organisation": "Smith & Co Law", "organisationReference": "SC/2026/118" } ] }, "termsOfUse": [{ "type": "PdtfAccessPolicy", "confidentiality": "restricted", "pii": false, "roleRestrictions": ["Seller's Conveyancer", "Buyer's Conveyancer", "Estate Agent"] }], "credentialStatus": { "id": "https://api.platform.example.com/status/transactions/list-001#5567", "type": "BitstringStatusListEntry", "statusPurpose": "revocation", "statusListIndex": "5567", "statusListCredential": "https://api.platform.example.com/status/transactions/list-001" }, "proof": { "type": "DataIntegrityProof", "cryptosuite": "eddsa-jcs-2022", "verificationMethod": "did:web:platform.example.com#platform-key-1", "proofPurpose": "assertionMethod", "created": "2026-03-10T12:00:00Z", "proofValue": "zL5jH9wQ..." }}4. Credential Subject
Section titled “4. Credential Subject”4.1 Subject Identification
Section titled “4.1 Subject Identification”The credentialSubject.id field identifies the entity the credential makes assertions about. It MUST be present and MUST use the PDTF identifier scheme:
| Credential Type | Subject ID Format | Example |
|---|---|---|
| PropertyCredential | urn:pdtf:uprn:{uprn} | urn:pdtf:uprn:100023456789 |
| TitleCredential | urn:pdtf:titleNumber:{n} | urn:pdtf:titleNumber:AB12345 |
| SellerCapacityCredential | urn:pdtf:capacity:{id} | urn:pdtf:capacity:own-a1b2c3 |
| OfferCredential | urn:pdtf:offer:{id} | urn:pdtf:offer:off-j1k2l3 |
| GiftCredential | urn:pdtf:gift:{id} | urn:pdtf:gift:gf-m4n5o6 |
| RepresentationCredential | urn:pdtf:representation:{id} | urn:pdtf:representation:rep-d4e5f6 |
| TransactionRoleCredential | urn:pdtf:role:{id} | urn:pdtf:role:tr-g7h8i9 |
| TransactionCredential | did:web:{host}:transactions:{id} | did:web:platform.example.com:transactions:tx-789 |
4.2 Sparse Object Model
Section titled “4.2 Sparse Object Model”A credential’s credentialSubject contains only the paths the issuer is asserting — not the full entity schema. This is the sparse object model.
A PropertyCredential issued by the EPC adapter contains only energyEfficiency:
{ "credentialSubject": { "id": "urn:pdtf:uprn:100023456789", "energyEfficiency": { "certificate": { "currentEnergyRating": "C", "currentEnergyEfficiency": 72 } } }}A PropertyCredential issued by the flood adapter contains only environmentalIssues.flooding:
{ "credentialSubject": { "id": "urn:pdtf:uprn:100023456789", "environmentalIssues": { "flooding": { "floodZone": "1", "surfaceWaterRisk": "Low", "historicalFlooding": "No" } } }}State assembly merges these sparse objects to build the complete entity state. See §5 for the merge semantics.
4.3 Subject and Entity Relationship
Section titled “4.3 Subject and Entity Relationship”The credentialSubject.id connects the credential to the entity graph:
┌─────────────────────────────┐ │ PropertyCredential (EPC) │ │ credentialSubject.id: │ │ urn:pdtf:uprn:10002345... │──┐ └─────────────────────────────┘ │ │ same entity ┌─────────────────────────────┐ │ │ PropertyCredential (flood) │ │ │ credentialSubject.id: │──┤ │ urn:pdtf:uprn:10002345... │ │ └─────────────────────────────┘ │ │ ┌─────────────────────────────┐ │ │ Entity Graph │ │ │ properties: │ │ │ "urn:pdtf:uprn:10002345": │◄─┘ │ { merged state } │ └─────────────────────────────┘Multiple credentials with the same credentialSubject.id assert different facts about the same entity. State assembly merges them, with later credentials (by validFrom) taking precedence for overlapping paths.
5. Claims Representation
Section titled “5. Claims Representation”5.1 From REPLACE to MERGE + Prune
Section titled “5.1 From REPLACE to MERGE + Prune”Decision D5 — needs LMS consensus.
The current PDTF v1 system uses pathKey:value pairs with REPLACE semantics:
[ { "claimPath": "/propertyPack/heating/heatingSystem/heatingType", "claimValue": "Central heating" }, { "claimPath": "/propertyPack/heating/heatingSystem/centralHeatingDetails/fuelType", "claimValue": "Mains gas" }, { "claimPath": "/propertyPack/heating/heatingSystem/centralHeatingDetails/boilerType", "claimValue": "Combination boiler" }]PDTF 2.0 replaces this with sparse objects using MERGE semantics and dependency pruning.
5.2 MERGE Semantics
Section titled “5.2 MERGE Semantics”When assembling state from multiple credentials, claims are merged using deep-merge:
State = {}For each credential (ordered by validFrom, ascending): State = deepMerge(State, credential.credentialSubject)Deep merge rules:
- Object + Object → recursive merge (keys from both, later wins on conflict)
- Primitive + Primitive → later value wins
- Array + Array → later array replaces entirely (arrays are not element-merged)
- Any + undefined → existing value preserved
- undefined + Any → new value applied
This means a newer credential can update specific paths without needing to re-state the entire entity. The EPC adapter can issue a new PropertyCredential with just energyEfficiency and it merges cleanly with the seller’s attestation of heating.
5.3 Dependency Pruning
Section titled “5.3 Dependency Pruning”MERGE alone isn’t sufficient. Consider this scenario:
Step 1: Seller attests heating system:
{ "credentialSubject": { "id": "urn:pdtf:uprn:100023456789", "heating": { "heatingSystem": { "heatingType": "Central heating", "centralHeatingDetails": { "fuelType": "Mains gas", "boilerType": "Combination boiler", "boilerAge": "5-10 years" } } } }}Step 2: Seller updates — the property now has no heating:
{ "credentialSubject": { "id": "urn:pdtf:uprn:100023456789", "heating": { "heatingSystem": { "heatingType": "None" } } }}After MERGE (without pruning):
{ "heating": { "heatingSystem": { "heatingType": "None", "centralHeatingDetails": { "fuelType": "Mains gas", "boilerType": "Combination boiler", "boilerAge": "5-10 years" } } }}Problem: heatingType is “None” but centralHeatingDetails still exists from the earlier credential. The state is internally inconsistent.
Dependency pruning resolves this. After MERGE, a pruning pass inspects the schema’s discriminator/conditional logic:
- The schema defines
centralHeatingDetailsas conditional onheatingTypebeing"Central heating"(via JSON SchemaoneOf/if-then-else). - Since
heatingTypeis now"None", the pruning pass stripscentralHeatingDetailsfrom the assembled state.
After MERGE + Prune:
{ "heating": { "heatingSystem": { "heatingType": "None" } }}The state is now consistent.
5.4 Pruning Rules
Section titled “5.4 Pruning Rules”Dependency pruning operates on the entity schema:
-
oneOf discriminators — When a discriminator field changes, prune branches that are no longer valid. The
heatingType→centralHeatingDetailscase above. -
if-then-else conditionals — When the
ifcondition is no longer met, prune fields defined only in thethenblock. -
enum-gated sections — When an enum value changes and a section is only valid for the previous value, prune that section.
-
Explicit dependencies — The PDTF schema MAY annotate fields with a
x-pdtf-dependsOnextension keyword to express dependencies not captured by JSON Schema conditional constructs.
Implementation: The pruning pass is implemented in the reference state assembly library (see 07 — State Assembly). It walks the schema tree, evaluates each conditional, and strips paths that fail their conditions.
5.5 Pruning and the Old Credential
Section titled “5.5 Pruning and the Old Credential”An important subtlety: after pruning removes centralHeatingDetails, the original credential that asserted those details is not modified or revoked. The credential is still valid — it accurately records what the seller attested at that point in time. Pruning operates on assembled state, not on individual credentials.
The old credential’s centralHeatingDetails data is simply no longer included in the assembled state because the newer credential’s heatingType: "None" makes it irrelevant per the schema.
If the seller later changes back to heatingType: "Central heating", the old centralHeatingDetails data could theoretically re-emerge from the earlier credential. Whether to allow this or require fresh attestation is an implementation decision for the state assembly layer.
5.6 Design Constraint: Issuers Are Stateless
Section titled “5.6 Design Constraint: Issuers Are Stateless”Issuers assert what they know at the time of issuance. They have no visibility of the current assembled state and no obligation to know what other credentials exist. This means:
- An issuer changing
heatingTypetoNonedoes not know that a previous credential assertedcentralHeatingDetails - Issuers cannot and should not be expected to explicitly clear dependent paths
- Pruning of schema-dependent paths (e.g., removing
centralHeatingDetailswhenheatingTypechanges toNone) is necessarily a state assembly concern, not an issuance concern - The schema’s existing
if/then/elseandoneOfdiscriminators define the dependency rules; the assembler applies them
This constraint shapes the merge semantics debate. Section-level REPLACE avoids the pruning question entirely (the issuer replaces the whole subtree). Incremental MERGE requires the assembler to understand schema dependencies and prune accordingly. A hybrid approach — REPLACE for adapter-issued institutional data, MERGE for seller-attested incremental data — may be the pragmatic path, but requires clear rules about which credential types use which strategy.
Why this matters per credential type:
- Adapter-issued credentials (EPC, title register, searches): Section-level REPLACE works naturally. These issuers are authoritative for the whole subtree and re-issue complete data every time. When the EPC adapter issues a credential, it replaces the entire
energyEfficiencybranch. - Seller-attested credentials (TA6, TA7, fixtures & fittings): Incremental MERGE is necessary because data arrives piecemeal as the seller fills in forms over time. A seller answering the heating section doesn’t re-submit the entire property pack. Finer credential granularity (per-section or per-field) amplifies this need — see Q1.2.
The consensus questions this raises:
- Should pruning happen at all? (vs letting contradictory data coexist with the newer credential winning on the discriminator)
- If yes, where are the dependency rules defined? (schema-level
if/then/elseandoneOfdiscriminators are natural candidates — they already exist in the v3 schema) - What is the assembler’s obligation? (MUST prune? SHOULD prune? MAY flag but retain?)
5.7 Consensus Required
Section titled “5.7 Consensus Required”D5 status: 🟡 Needs consensus
Sparse objects with dependency pruning is a significant departure from the current REPLACE semantics. The benefits are clear (structured data, natural JSON, schema-driven consistency), but the implementation complexity is higher. LMS and other implementers need to agree before this is finalised.
Fallback option: If consensus is not reached, an alternative is to use full-object REPLACE at the section level — each credential replaces the entire top-level path it touches (e.g. a credential asserting heating replaces the entire heating subtree). This is simpler but loses the fine-grained merge capability.
6. Evidence Model
Section titled “6. Evidence Model”6.1 Simplified from OIDC
Section titled “6.1 Simplified from OIDC”Decision D6: The current OIDC-derived evidence schema is deeply nested and over-specified for actual usage patterns. PDTF 2.0 simplifies evidence to four types that reflect how property data is actually sourced.
The current v1 evidence model inherits from OpenID Connect’s verification.evidence[] structure, which was designed for identity verification (vouching, documents, electronic records). Property data sourcing has different patterns — API fetches, PDF extraction, seller forms, professional checks — and needs a simpler model that captures provenance without the OIDC baggage.
6.2 Evidence Types
Section titled “6.2 Evidence Types”| Type | Description | Key Fields | When Used |
|---|---|---|---|
ElectronicRecord | Data retrieved from an authoritative API | source, retrievedAt, method | EPC API, HMLR OC1, EA flood API, LLC API |
DocumentExtraction | Data extracted from a document (PDF, scan, etc.) | source, extractedAt, method, documentHash | Title deeds PDF, search result documents, lease documents |
UserAttestation | Data declared by a user (seller, buyer) | source, attestedAt, method | BASPI form, TA6/TA7/TA10, fixtures & fittings form |
ProfessionalVerification | Data verified by a professional (conveyancer, surveyor) | source, verifiedAt, method, professionalRole | Conveyancer title review, surveyor inspection |
6.3 Common Fields
Section titled “6.3 Common Fields”All evidence types share:
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Required | One of: ElectronicRecord, DocumentExtraction, UserAttestation, ProfessionalVerification |
source | string | Required | Origin of the evidence — API hostname, person DID, or document reference |
method | string | Optional | How the evidence was obtained (e.g. “API”, “PDF extraction”, “Form completion”) |
6.4 Type-Specific Fields
Section titled “6.4 Type-Specific Fields”ElectronicRecord:
| Field | Type | Required | Description |
|---|---|---|---|
retrievedAt | ISO datetime | Required | When the API call was made |
apiEndpoint | string | Optional | Specific API endpoint called |
requestId | string | Optional | API request identifier for audit trail |
{ "type": "ElectronicRecord", "source": "get-energy-performance-data.communities.gov.uk", "retrievedAt": "2026-03-24T09:58:00Z", "method": "API", "apiEndpoint": "/api/v4/domestic/certificate/1234-5678-9012-3456-7890"}DocumentExtraction:
| Field | Type | Required | Description |
|---|---|---|---|
extractedAt | ISO datetime | Required | When extraction was performed |
documentHash | string | Optional | SHA-256 hash of the source document |
documentType | string | Optional | Type of document (e.g. “Title Register”, “Environmental Search Report”) |
pageRange | string | Optional | Pages from which data was extracted |
{ "type": "DocumentExtraction", "source": "HMLR Official Copy (Title Register)", "extractedAt": "2026-03-24T10:15:00Z", "method": "PDF extraction — structured data parser", "documentHash": "sha256:a3f2b8c9d1e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0", "documentType": "Title Register"}UserAttestation:
| Field | Type | Required | Description |
|---|---|---|---|
attestedAt | ISO datetime | Required | When the user made the declaration |
formType | string | Optional | The form used (e.g. “BASPI”, “TA6”, “TA7”) |
questionRef | string | Optional | Specific question reference (e.g. “TA6.7.1”) |
{ "type": "UserAttestation", "source": "did:key:z6MkhSellerAbc123", "attestedAt": "2026-03-20T14:30:00Z", "method": "BASPI form completion", "formType": "BASPI", "questionRef": "BASPI.3.2"}ProfessionalVerification:
| Field | Type | Required | Description |
|---|---|---|---|
verifiedAt | ISO datetime | Required | When verification was performed |
professionalRole | string | Required | Role of the verifier (e.g. “Conveyancer”, “Surveyor”) |
firmDid | string | Optional | DID of the professional’s firm |
notes | string | Optional | Verification notes |
{ "type": "ProfessionalVerification", "source": "did:web:smithandco.law", "verifiedAt": "2026-03-24T11:00:00Z", "method": "Title review — proprietorship cross-reference", "professionalRole": "Conveyancer", "firmDid": "did:web:smithandco.law"}6.5 Multiple Evidence Items
Section titled “6.5 Multiple Evidence Items”A credential MAY have multiple evidence items. This is common when data has been both fetched from an API and verified by a professional:
{ "evidence": [ { "type": "ElectronicRecord", "source": "landregistry.data.gov.uk", "retrievedAt": "2026-03-24T08:14:00Z", "method": "OC1 API" }, { "type": "ProfessionalVerification", "source": "did:web:smithandco.law", "verifiedAt": "2026-03-24T11:00:00Z", "method": "Conveyancer title review", "professionalRole": "Conveyancer" } ]}6.6 Source Documents
Section titled “6.6 Source Documents”Evidence often refers to a source file — a title register PDF, an EPC certificate, a survey report, a search result document. The evidence model needs to support referencing these files and controlling access to them.
6.6.1 The sourceDocument Object
Section titled “6.6.1 The sourceDocument Object”Any evidence type MAY include a sourceDocument field referencing the underlying file:
| Field | Type | Required | Description |
|---|---|---|---|
digest | string | Required | Content hash for integrity verification. Format: sha256:{hex} |
mediaType | string | Required | MIME type (e.g. application/pdf, image/jpeg) |
size | integer | Optional | File size in bytes |
name | string | Optional | Human-readable filename |
url | string | Required | Retrieval URL — a pdtf:// URI that resolves via the Transaction DID’s document endpoint |
confidentiality | string | Required | Access level — same values as termsOfUse (see §7): public, transactionParticipants, roleRestricted, partyOnly |
authorisedRoles | string[] | Conditional | Required when confidentiality is roleRestricted. Role identifiers from the same table as termsOfUse role restrictions. |
Example — title register PDF referenced from a DocumentExtraction evidence item:
{ "type": "DocumentExtraction", "source": "HMLR Official Copy (Title Register)", "extractedAt": "2026-03-24T10:15:00Z", "method": "PDF extraction — structured data parser", "documentHash": "sha256:a3f2b8c9d1e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0", "documentType": "Title Register", "sourceDocument": { "digest": "sha256:a3f2b8c9d1e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0", "mediaType": "application/pdf", "size": 245760, "name": "Official Copy - Title Register NK123456.pdf", "url": "pdtf://transactions/abc123/documents/doc-tr-nk123456", "confidentiality": "roleRestricted", "authorisedRoles": ["sellerConveyancer", "buyerConveyancer"] }}Note that documentHash on the evidence item and digest on sourceDocument are the same value here — the evidence is about the document, and the document is the source. They don’t have to match (evidence could reference a different file from the one that was hashed at extraction time), but when they do, it’s a strong integrity chain.
Example — survey report with restricted access:
{ "type": "ProfessionalVerification", "source": "did:web:abcsurveys.co.uk", "verifiedAt": "2026-03-25T14:00:00Z", "method": "Level 2 HomeBuyer Report", "professionalRole": "Surveyor", "sourceDocument": { "digest": "sha256:f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8", "mediaType": "application/pdf", "size": 4521984, "name": "HomeBuyer Report - 42 Oak Lane.pdf", "url": "pdtf://transactions/abc123/documents/doc-survey-hb-001", "confidentiality": "partyOnly", "authorisedRoles": [] }}A partyOnly survey report is accessible only to the party who commissioned it (typically the buyer). They may choose to share it by upgrading confidentiality or adding authorised roles.
6.6.2 Document Retrieval Protocol
Section titled “6.6.2 Document Retrieval Protocol”The pdtf:// URL scheme resolves through the Transaction DID document:
- Parse the
pdtf://URL to extract the transaction identifier and document path - Resolve the Transaction DID (
did:web:platform.example.com:transactions:{txnId}) - Find the
PdtfDocumentEndpointservice in the DID document:
{ "service": [{ "id": "did:web:platform.example.com:transactions:abc123#documents", "type": "PdtfDocumentEndpoint", "serviceEndpoint": "https://platform.example.com/api/transactions/abc123/documents" }]}- Present a Verifiable Presentation containing the requester’s participation credential to the service endpoint
- The endpoint verifies the VP, checks the requester’s role against the document’s
confidentialityandauthorisedRoles, and returns the file or a 403 - The requester verifies the file’s content against the
digestfield
GET /api/transactions/abc123/documents/doc-tr-nk123456Authorization: Bearer <VP-token>
→ 200 OKContent-Type: application/pdfPDTF-Digest: sha256:a3f2b8c9d1e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0
<file bytes>6.6.3 Confidentiality Levels for Documents
Section titled “6.6.3 Confidentiality Levels for Documents”Document confidentiality uses the same levels as credential termsOfUse (§7), ensuring a single access control model across the framework:
| Level | Who Can Access | Typical Documents |
|---|---|---|
public | Anyone with the URL | EPC certificates, flood zone maps |
transactionParticipants | Any party with a valid participation credential | Title registers, local authority searches |
roleRestricted | Parties whose role matches authorisedRoles | Environmental search reports (conveyancers only), contract drafts |
partyOnly | The data subject / commissioning party only | Survey reports (buyer), mortgage offers (buyer), identity documents |
Documents follow the same encryption model as credentials when synced between platforms (see Architecture Overview D30). The confidentiality level determines the recipient set for per-document encryption keys.
6.6.4 Design Principles
Section titled “6.6.4 Design Principles”- Files are referenced, not embedded. A 50MB survey PDF does not belong inside a VC. The credential carries the provenance metadata and integrity hash; the file lives behind an authenticated endpoint.
- One access control model. Documents use the same confidentiality levels and VP-based authentication as credentials. No parallel auth system.
- Digest is the anchor. The SHA-256 digest binds the evidence claim to a specific file. A verifier can confirm the file hasn’t been tampered with regardless of where it was fetched from.
pdtf://URIs are portable. They resolve through DID documents, not hardcoded hostnames. If a transaction migrates between platforms, the URIs still resolve — the new platform updates the Transaction DID’s service endpoint.
6.7 Migration from v1 Evidence
Section titled “6.7 Migration from v1 Evidence”The current OIDC-derived evidence types map as follows:
| v1 Evidence Type | v2 Evidence Type | Notes |
|---|---|---|
electronic_record | ElectronicRecord | Direct mapping. Flatten nested record object. |
document | DocumentExtraction | Flatten document_details. source from document.issuer.name. |
vouch | ProfessionalVerification | voucher becomes source (DID). attestation becomes notes. |
| (user form data — no explicit v1 type) | UserAttestation | New. Currently inferred from claim context, not explicitly typed. |
7. Terms of Use
Section titled “7. Terms of Use”7.1 PdtfAccessPolicy
Section titled “7.1 PdtfAccessPolicy”Every PDTF credential SHOULD include a termsOfUse entry defining its access policy. The PdtfAccessPolicy type carries the same semantics as the current v1 terms_of_use but in a cleaner structure aligned with W3C VC termsOfUse.
{ "termsOfUse": [{ "type": "PdtfAccessPolicy", "confidentiality": "restricted", "pii": true, "roleRestrictions": ["sellerConveyancer", "buyerConveyancer"] }]}7.2 Fields
Section titled “7.2 Fields”| Field | Type | Required | Values | Description |
|---|---|---|---|---|
type | string | Required | "PdtfAccessPolicy" | Discriminator |
confidentiality | enum | Required | public, restricted, confidential | Access tier |
pii | boolean | Required | true, false | Whether the data contains personally identifiable information |
roleRestrictions | string[] | Conditional | Role identifiers | Required when confidentiality is restricted or confidential |
7.3 Confidentiality Levels
Section titled “7.3 Confidentiality Levels”public — Available without authentication. Anyone can read it.
- Examples: EPC data, flood risk zones, listed building status, council tax band
roleRestrictionsSHOULD be omitted or emptypiiMUST befalse
restricted — Available to authenticated participants with a matching role.
- Examples: Seller contact details, legal questions, title register extract, offer details
roleRestrictionsspecifies which roles can accesspiimay betrueorfalse
confidential — Available only to specifically authorised parties.
- Examples: AML verification results, gift terms, internal financial data
roleRestrictionstypically limited to direct legal representativespiiis typicallytrue
7.4 Role Identifiers
Section titled “7.4 Role Identifiers”Role identifiers are the v3 participant role enum. Each is carried by exactly one relationship credential (01 §3.2, D32): implied by SellerCapacity, Offer and Gift, explicit on Representation and TransactionRole.
| Role | Source | Description |
|---|---|---|
Seller | SellerCapacityCredential | Person or organisation selling, in a declared capacity |
Buyer | OfferCredential (accepted) | Person with an accepted offer |
Prospective Buyer | OfferCredential (pending) | Person with an offer not yet accepted |
Giftor | GiftCredential | Person gifting funds towards an offer |
Seller's Conveyancer | RepresentationCredential | Instructed by the seller |
Buyer's Conveyancer | RepresentationCredential | Instructed by the buyer |
Estate Agent | RepresentationCredential | Instructed by the seller |
Buyer's Agent | RepresentationCredential | Instructed by the buyer |
Mortgage Broker | RepresentationCredential | Instructed by the buyer |
Surveyor | RepresentationCredential or TransactionRoleCredential | Instructed surveyor, or surveyor with no instructing party in the graph |
Lender | TransactionRoleCredential | Mortgage lender, party to the transaction |
Landlord, Tenant, Platform Support | TransactionRoleCredential | Roles with no instructing party |
7.5 Query-Time Filtering
Section titled “7.5 Query-Time Filtering”When a requester queries transaction state (either via the API or through the graph composer), the system applies termsOfUse filtering:
-
Determine requester’s roles — from their presented credentials (SellerCapacity →
Seller, Offer →Buyer, Gift →Giftor, Representation → itsrole, TransactionRole → itsrole). Each credential MUST reference the transaction being queried. -
Filter credentials — for each credential in the entity graph:
- If
confidentialityispublic→ include - If
confidentialityisrestrictedorconfidential:- Check if the requester has at least one role listed in
roleRestrictions
- Check if the requester has at least one role listed in
- If no match → exclude the credential from the response
- If
-
Assemble filtered state — compose state only from included credentials.
This means different requesters see different views of the same transaction. A buyer’s conveyancer sees title register details, AML status, and legal questions. A potential buyer who hasn’t yet been accepted sees only public data.
Buyer's Conveyancer requests state → Present RepresentationCredential (role: Buyer's Conveyancer) → Filter: include all public + restricted/confidential where "Buyer's Conveyancer" ∈ roleRestrictions → Result: property data, title register, ownership claims, offer details, legal questions
Estate Agent requests state → Present RepresentationCredential (role: Estate Agent) → Filter: include all public + restricted where "Estate Agent" ∈ roleRestrictions → Result: property data, basic offer info — NOT title register, NOT AML details
Unauthenticated request → No credential presented → Filter: include only public → Result: EPC rating, flood zone, address — nothing sensitive7.6 PII Handling
Section titled “7.6 PII Handling”The pii flag enables additional processing:
- Logging: PII-flagged data must not appear in application logs.
- Caching: PII-flagged credentials require shorter cache TTL or no caching.
- Data subject requests: PII-flagged credentials must be discoverable for GDPR subject access/erasure requests.
- Export: PII-flagged credentials must be excluded from anonymised datasets.
The pii flag is informational — it doesn’t affect access control (that’s confidentiality + roleRestrictions). It affects how the data is handled after access is granted.
8. Credential Status
Section titled “8. Credential Status”8.1 Mandatory Revocation Support
Section titled “8.1 Mandatory Revocation Support”Decision D18: Every PDTF credential MUST include a credentialStatus field pointing to a W3C Bitstring Status List v2 entry. There are no exceptions.
Rationale: In a property transaction, data changes frequently (new EPC, price reduction, change of conveyancer, sale falling through). Without revocation, stale credentials are indistinguishable from current ones. A verifier must be able to check whether a credential is still valid.
8.2 BitstringStatusListEntry
Section titled “8.2 BitstringStatusListEntry”Every PDTF credential includes:
{ "credentialStatus": { "id": "https://adapters.propdata.org.uk/status/epc/list-042#18293", "type": "BitstringStatusListEntry", "statusPurpose": "revocation", "statusListIndex": "18293", "statusListCredential": "https://adapters.propdata.org.uk/status/epc/list-042" }}| Field | Type | Description |
|---|---|---|
id | URI | Unique identifier for this status entry (list URL + fragment) |
type | string | Always "BitstringStatusListEntry" |
statusPurpose | string | Always "revocation" for PDTF (not "suspension") |
statusListIndex | string | Bit position in the status list bitstring |
statusListCredential | URI | URL of the status list credential |
8.3 Status List Credentials
Section titled “8.3 Status List Credentials”Each issuer hosts one or more Bitstring Status List credentials. These are themselves VCs:
{ "@context": [ "https://www.w3.org/ns/credentials/v2", "https://www.w3.org/ns/credentials/status/v2" ], "type": ["VerifiableCredential", "BitstringStatusListCredential"], "issuer": "did:web:adapters.propdata.org.uk:epc", "validFrom": "2026-03-24T00:00:00Z", "credentialSubject": { "id": "https://adapters.propdata.org.uk/status/epc/list-042", "type": "BitstringStatusList", "statusPurpose": "revocation", "encodedList": "H4sIAAAAAAAAA-3BMQEAAADCoPVPbQ..." }, "proof": { "type": "DataIntegrityProof", "cryptosuite": "eddsa-jcs-2022", "verificationMethod": "did:web:adapters.propdata.org.uk:epc#key-1", "proofPurpose": "assertionMethod", "created": "2026-03-24T00:00:00Z", "proofValue": "zH7kN3pR..." }}The encodedList is a GZIP-compressed, base64url-encoded bitstring. Each bit position corresponds to a statusListIndex. Bit = 0 means “not revoked”; bit = 1 means “revoked”.
8.4 Revocation Flow
Section titled “8.4 Revocation Flow”1. Issuer decides to revoke (data superseded, error found, mandate withdrawn)2. Issuer flips bit at statusListIndex in the relevant status list3. Issuer re-signs the status list credential4. Verifiers fetch the status list (HTTP GET, cacheable with short TTL)5. Verifier checks the bit at the credential's statusListIndex6. If bit = 1 → credential is revoked → exclude from state assembly8.5 Revocation Scenarios
Section titled “8.5 Revocation Scenarios”| Scenario | Credential Revoked | Trigger |
|---|---|---|
| New EPC issued | Old PropertyCredential (EPC paths) | New EPC VC replaces old |
| Seller changes conveyancer | Old RepresentationCredential | Seller instructs new firm |
| Sale completes | SellerCapacityCredential, RepresentationCredential, OfferCredential | Transaction closes |
| Sale falls through | OfferCredential | Offer withdrawn |
| Data correction | Any credential with incorrect data | Error discovered |
| Lender withdraws | TransactionRoleCredential | Lender no longer party to the sale |
| Gift cancelled | GiftCredential | Giftor withdraws |
8.6 Hosting and Caching
Section titled “8.6 Hosting and Caching”Status list credentials are hosted at stable URLs by each issuer:
- Adapters:
https://adapters.propdata.org.uk/status/{adapter}/{list-id} - Platform:
https://api.platform.example.com/status/{entity-type}/{list-id}
Caching: Status lists SHOULD be served with Cache-Control: max-age=300 (5 minutes). Verifiers SHOULD cache status lists and refresh on cache expiry. For time-sensitive revocations (e.g. conveyancer change), the issuer can invalidate the cache by updating the list and notifying known verifiers.
For full hosting infrastructure details, see 14 — Credential Revocation.
9. Securing Mechanism
Section titled “9. Securing Mechanism”PDTF issues credentials as SD-JWT-VC (IETF draft-ietf-oauth-sd-jwt-vc, built on the SD-JWT selective-disclosure mechanism): a JWS over the entity claims, with per-field selective disclosure and holder key binding.
Payload claims:
| Claim | Description |
|---|---|
iss | Issuer identifier (DID or HTTPS origin); signing key discovered via the issuer DID document or JWKS |
vct | Verifiable Credential Type — identifies the PDTF credential type and resolves to its Type Metadata (§10), e.g. urn:pdtf:vct:PropertyCredential |
sub | Entity identifier — the DID/URN that the claims model calls credentialSubject.id |
iat / exp | Issued-at / optional expiry |
cnf | Holder public key (confirmation), for key binding |
status | IETF Token Status List reference for revocation (replaces W3C BitstringStatusList — the revocation sub-spec 14 needs a matching update, tracked in §13.4) |
_sd, _sd_alg | Selective-disclosure digests and hash algorithm |
| (entity claims) | The property/entity data (e.g. energyEfficiency, registerExtract, or a nested credentialSubject object) |
- Selective disclosure. Issuer-marked claims are replaced in the payload by salted digests (
_sd); the plaintext + salt (a Disclosure) travels alongside and can be withheld per verifier, honouringtermsOfUse. - Key binding. At presentation the holder appends a Key Binding JWT signed over the verifier’s
nonceandaud, proving possession and preventing replay. This is the same proof-of-control primitive used by cross-party identity reuse (03 §10.5). - Signatures.
EdDSA(Ed25519, per D16) orES256. - Verification. Verify the issuer JWS via the
isskey; recompute disclosure digests and reconcile against_sd; verify the Key Binding JWT againstcnf; checkstatus(Token Status List) andexp; validatevctagainst the expected type + Type Metadata (§10).
Worked example — a holder-bound SD-JWT-VC (SellerCapacity). Entity claims stay under credentialSubject so the entity’s own status field does not collide with the top-level Token Status List status claim.
Compact serialisation — issuer-signed JWS, then ~-separated Disclosures, then the Key Binding JWT (base64url; truncated):
eyJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJkaWQ6d2Vic...fQ.<sig>~WyJzNGw3X3NhbHQiLCJkYXRlQmVjYW1lT3duZXJPckF1dGhvcml0eSIsIjIwMTQtMDYtMDEiXQ~eyJhbGciOiJFZERTQSJ9.eyJhdWQiOiJkaWQ6d2ViOmpvbmVzbGVnYWwuY28udWsi...fQ.<kbSig>Decoded issuer-signed payload:
{ "iss": "did:web:platform.example.com", "vct": "urn:pdtf:vct:SellerCapacityCredential", "sub": "urn:pdtf:capacity:own-1a2b", "iat": 1774339200, "cnf": { "jwk": { "kty": "OKP", "crv": "Ed25519", "x": "l8k…holderPubKey" } }, "status": { "status_list": { "idx": 94, "uri": "https://platform.example.com/status/capacity/1" } }, "_sd_alg": "sha-256", "credentialSubject": { "id": "urn:pdtf:capacity:own-1a2b", "seller": "did:key:z6Mkh…abc", "transaction": "did:web:platform.example.com:transactions:tx-789", "sellersCapacity": { "capacity": "Legal Owner" }, "_sd": ["9gYy…digestOfDateBecameOwnerOrAuthority"] }}Decoded Disclosure ([salt, claimName, value]) for the selectively-disclosable dateBecameOwnerOrAuthority:
["s4l7…salt", "dateBecameOwnerOrAuthority", "2014-06-01"]Decoded Key Binding JWT payload (holder proves possession over the verifier’s nonce/audience):
{ "iat": 1774340000, "aud": "did:web:joneslegal.co.uk", "nonce": "b3f1c9…", "sd_hash": "X0pq…hashOfPresentedSDJWT"}9.1 Superseded — Data Integrity (eddsa-jcs-2022)
Section titled “9.1 Superseded — Data Integrity (eddsa-jcs-2022)”(Prior-draft embedded-proof mechanism, retained as a documented alternative; no longer primary.)
All PDTF credentials use the Data Integrity securing mechanism with the eddsa-jcs-2022 cryptosuite.
Why eddsa-jcs-2022:
- Ed25519 keys (D16) — fast, small signatures, widely supported in the DID ecosystem
- JCS (JSON Canonicalization Scheme) — deterministic JSON serialisation for signing. No ambiguity about whitespace, key ordering, or Unicode normalisation.
- W3C standard track — part of the EdDSA Cryptosuite v2022 specification.
9.2 Proof Structure
Section titled “9.2 Proof Structure”{ "proof": { "type": "DataIntegrityProof", "cryptosuite": "eddsa-jcs-2022", "verificationMethod": "did:web:adapters.propdata.org.uk:epc#key-1", "proofPurpose": "assertionMethod", "created": "2026-03-24T10:00:00Z", "proofValue": "z4oJ9Bvn..." }}| Field | Type | Description |
|---|---|---|
type | string | Always "DataIntegrityProof" |
cryptosuite | string | Always "eddsa-jcs-2022" |
verificationMethod | DID URL | Points to the issuer’s public key in their DID document. Format: {issuer-did}#{key-id} |
proofPurpose | string | Always "assertionMethod" for PDTF credentials |
created | ISO datetime | When the proof was generated |
proofValue | string | Multibase-encoded Ed25519 signature over the JCS-canonicalised document (excluding the proof property itself) |
9.3 Verification Flow
Section titled “9.3 Verification Flow”To verify a PDTF credential’s proof:
- Extract proof — remove the
proofproperty from the credential document. - Canonicalise — apply JCS (RFC 8785) to the remaining document to produce a deterministic byte representation.
- Resolve verification method — resolve the DID in
verificationMethodto obtain the public key.- For
did:key: self-resolving — the key is encoded in the DID itself. - For
did:web: fetch the DID document fromhttps://{domain}/.well-known/did.json(or the path-based resolution for subpath DIDs).
- For
- Extract public key — from the resolved DID document’s
verificationMethodarray, find the entry matching the fragment (e.g.#key-1). - Verify signature — verify the Ed25519 signature (
proofValue, multibase-decoded) against the canonicalised document using the public key. - Check proof purpose — confirm the key is listed under the DID document’s
assertionMethodrelationship.
9.4 Key Rotation
Section titled “9.4 Key Rotation”When an issuer rotates keys:
- New key is added to the DID document’s
verificationMethodarray (e.g.#key-2). - New credentials reference
#key-2in theirverificationMethod. - Old key (
#key-1) is retained in the DID document for a transition period (existing credentials still verify). - After all credentials signed with
#key-1are expired or revoked, the old key can be removed.
The DID document is the single source of truth for which keys are valid. Key rotation does not require re-issuing existing credentials.
10. Type Metadata & JSON-LD Context
Section titled “10. Type Metadata & JSON-LD Context”10.1 PDTF v2 Context (optional semantic overlay)
Section titled “10.1 PDTF v2 Context (optional semantic overlay)”Every PDTF credential MAY include two @context entries:
{ "@context": [ "https://www.w3.org/ns/credentials/v2", "https://trust.propdata.org.uk/ns/pdtf/v2" ]}-
W3C VC v2 context (
https://www.w3.org/ns/credentials/v2) — defines the base credential vocabulary:VerifiableCredential,issuer,credentialSubject,evidence,termsOfUse,credentialStatus,proof, etc. -
PDTF v2 context (
https://trust.propdata.org.uk/ns/pdtf/v2) — defines PDTF-specific terms.
10.2 What the PDTF Context Defines
Section titled “10.2 What the PDTF Context Defines”The PDTF v2 JSON-LD context defines:
Credential types:
PropertyCredentialTitleCredentialSellerCapacityCredentialOfferCredentialGiftCredentialRepresentationCredentialTransactionRoleCredentialTransactionCredential
Evidence types:
ElectronicRecordDocumentExtractionUserAttestationProfessionalVerification
Terms of use types:
PdtfAccessPolicy
Credential subject properties:
- All Property entity paths (e.g.
energyEfficiency,environmentalIssues,heating,buildInformation) - All Title entity paths (e.g.
registerExtract,ownership,titleExtents) - SellerCapacity entity fields (
seller,title,sellersCapacity,dateBecameOwnerOrAuthority) - Offer entity fields (
buyer,offerId,amount,status,buyerCircumstances) - Gift entity fields (
donor,offerId,giftDetails) - Representation entity fields (
representative,representedParty,role) - TransactionRole entity fields (
participant,role) - Transaction entity fields (
milestones,saleContext,property,titlesToBeSold,participants) - The
transactionreference shared by every relationship credential
Evidence properties:
source,retrievedAt,attestedAt,extractedAt,verifiedAtmethod,apiEndpoint,requestIddocumentHash,documentType,pageRangeformType,questionRefprofessionalRole,firmDid
Access policy properties:
confidentiality,pii,roleRestrictions
10.3 Context Hosting
Section titled “10.3 Context Hosting”The context document is hosted at https://trust.propdata.org.uk/ns/pdtf/v2 and MUST be:
- Immutable for a given version — once published, the v2 context does not change.
- Versioned — breaking changes result in a new version (e.g.
v3). - Cached — clients SHOULD cache the context document. It is static content.
- Available — hosted with high availability (CDN-backed static file).
Minor additions (new optional fields) can be added without version bumps, following JSON-LD’s open-world assumption. Removals or semantic changes require a new version.
10.4 Context Document Structure (Excerpt)
Section titled “10.4 Context Document Structure (Excerpt)”{ "@context": { "@version": 1.1, "pdtf": "https://trust.propdata.org.uk/ns/pdtf/v2#",
"PropertyCredential": "pdtf:PropertyCredential", "TitleCredential": "pdtf:TitleCredential", "SellerCapacityCredential": "pdtf:SellerCapacityCredential", "OfferCredential": "pdtf:OfferCredential", "GiftCredential": "pdtf:GiftCredential", "RepresentationCredential": "pdtf:RepresentationCredential", "TransactionRoleCredential": "pdtf:TransactionRoleCredential", "TransactionCredential": "pdtf:TransactionCredential",
"ElectronicRecord": "pdtf:ElectronicRecord", "DocumentExtraction": "pdtf:DocumentExtraction", "UserAttestation": "pdtf:UserAttestation", "ProfessionalVerification": "pdtf:ProfessionalVerification",
"PdtfAccessPolicy": "pdtf:PdtfAccessPolicy", "confidentiality": "pdtf:confidentiality", "pii": {"@id": "pdtf:pii", "@type": "http://www.w3.org/2001/XMLSchema#boolean"}, "roleRestrictions": {"@id": "pdtf:roleRestrictions", "@container": "@set"},
"seller": {"@id": "pdtf:seller", "@type": "@id"}, "buyer": {"@id": "pdtf:buyer", "@type": "@id"}, "donor": {"@id": "pdtf:donor", "@type": "@id"}, "representative": {"@id": "pdtf:representative", "@type": "@id"}, "representedParty": {"@id": "pdtf:representedParty", "@type": "@id"}, "participant": {"@id": "pdtf:participant", "@type": "@id"}, "transaction": {"@id": "pdtf:transaction", "@type": "@id"}, "title": {"@id": "pdtf:title", "@type": "@id"}, "property": {"@id": "pdtf:property", "@type": "@id"}, "titlesToBeSold": {"@id": "pdtf:titlesToBeSold", "@container": "@list", "@type": "@id"}, "participants": {"@id": "pdtf:participants", "@container": "@list"}, "offerId": "pdtf:offerId", "sellersCapacity": "pdtf:sellersCapacity", "giftDetails": "pdtf:giftDetails",
"dateBecameOwnerOrAuthority": {"@id": "pdtf:dateBecameOwnerOrAuthority", "@type": "http://www.w3.org/2001/XMLSchema#date"}, "verifiedAt": {"@id": "pdtf:verifiedAt", "@type": "http://www.w3.org/2001/XMLSchema#dateTime"}, "status": "pdtf:status", "role": "pdtf:role", "scope": {"@id": "pdtf:scope", "@container": "@set"}, "purpose": "pdtf:purpose", "amount": {"@id": "pdtf:amount", "@type": "http://www.w3.org/2001/XMLSchema#decimal"}, "currency": "pdtf:currency",
"source": "pdtf:source", "retrievedAt": {"@id": "pdtf:retrievedAt", "@type": "http://www.w3.org/2001/XMLSchema#dateTime"}, "attestedAt": {"@id": "pdtf:attestedAt", "@type": "http://www.w3.org/2001/XMLSchema#dateTime"}, "extractedAt": {"@id": "pdtf:extractedAt", "@type": "http://www.w3.org/2001/XMLSchema#dateTime"}, "method": "pdtf:method", "documentHash": "pdtf:documentHash", "documentType": "pdtf:documentType", "formType": "pdtf:formType", "questionRef": "pdtf:questionRef", "professionalRole": "pdtf:professionalRole", "firmDid": {"@id": "pdtf:firmDid", "@type": "@id"},
"energyEfficiency": "pdtf:energyEfficiency", "environmentalIssues": "pdtf:environmentalIssues", "heating": "pdtf:heating", "buildInformation": "pdtf:buildInformation", "residentialPropertyFeatures": "pdtf:residentialPropertyFeatures", "fixturesAndFittings": "pdtf:fixturesAndFittings", "councilTax": "pdtf:councilTax", "connectivity": "pdtf:connectivity", "registerExtract": "pdtf:registerExtract", "milestones": "pdtf:milestones", "saleContext": "pdtf:saleContext", "buyerCircumstances": "pdtf:buyerCircumstances" }}Note: This is an excerpt. The full context will include all Property, Title, and other entity fields. The context is generated from the v4 entity schemas to ensure consistency.
11. Full Examples
Section titled “11. Full Examples”11.1 EPC PropertyCredential (Trusted Proxy Issuer)
Section titled “11.1 EPC PropertyCredential (Trusted Proxy Issuer)”A complete EPC credential issued by the PDTF EPC adapter (trusted proxy for MHCLG):
{ "@context": [ "https://www.w3.org/ns/credentials/v2", "https://trust.propdata.org.uk/ns/pdtf/v2" ], "id": "urn:pdtf:vc:epc-7f3a2b1c-9d4e-5f6a-8b7c-0d1e2f3a4b5c", "type": ["VerifiableCredential", "PropertyCredential"], "issuer": "did:web:adapters.propdata.org.uk:epc", "validFrom": "2026-03-24T10:00:00Z", "validUntil": "2034-01-15T00:00:00Z", "credentialSubject": { "id": "urn:pdtf:uprn:100023456789", "energyEfficiency": { "certificate": { "certificateNumber": "1234-5678-9012-3456-7890", "currentEnergyRating": "C", "currentEnergyEfficiency": 72, "potentialEnergyRating": "B", "potentialEnergyEfficiency": 85, "environmentalImpactCurrent": 58, "environmentalImpactPotential": 74, "lodgementDate": "2024-01-15", "expiryDate": "2034-01-15", "totalFloorArea": 85, "typeOfAssessment": "RdSAP", "assessmentDate": "2024-01-10" }, "recommendations": [ { "sequence": 1, "improvement": "Floor insulation (suspended floor)", "indicativeCost": "£800 - £1,200", "typicalSaving": "£60/year" }, { "sequence": 2, "improvement": "Solar water heating", "indicativeCost": "£4,000 - £6,000", "typicalSaving": "£35/year" } ] } }, "evidence": [{ "type": "ElectronicRecord", "source": "get-energy-performance-data.communities.gov.uk", "retrievedAt": "2026-03-24T09:58:00Z", "method": "API", "apiEndpoint": "/api/v4/domestic/certificate/1234-5678-9012-3456-7890" }], "termsOfUse": [{ "type": "PdtfAccessPolicy", "confidentiality": "public", "pii": false }], "credentialStatus": { "id": "https://adapters.propdata.org.uk/status/epc/list-042#18293", "type": "BitstringStatusListEntry", "statusPurpose": "revocation", "statusListIndex": "18293", "statusListCredential": "https://adapters.propdata.org.uk/status/epc/list-042" }, "proof": { "type": "DataIntegrityProof", "cryptosuite": "eddsa-jcs-2022", "verificationMethod": "did:web:adapters.propdata.org.uk:epc#key-1", "proofPurpose": "assertionMethod", "created": "2026-03-24T10:00:00Z", "proofValue": "z4oJ9BvnXp8kM2nRqY7tL3wS5vU1xZ6bA9dF0gH3jK4mN7pQ8rT2uW5yB1cE4fI6hL9oR2sV3xZ0bD5gJ7kM8nP1qS4tU6wY9aC2eG3iK5lN8oQ0rT7uW1yB4dF6hI9jL2mO3pR5sV8xZ1bD4gJ6kM9nP2qS0tU7wY3aC5eG8iK1lN4oQ6rT9uW2yB0dF3hI5jL8mO1pR7sV4xZ6bD2gJ9kM0nP5qS3tU8wY1aC7eG4iK6lN9oQ2rT0uW5yB3dF8hI1jL4mO6pR9sV2xZ7bD0gJ5kM3nP8qS1tU6wY4aC9eG2iK0lN7oQ5rT3uW8yB1dF6hI4jL9mO2pR0sV7xZ5bD3gJ8kM1nP6qS4tU9wY2aC0eG7iK3lN5oQ8rT1uW6yB4dF9hI2jL0mO7pR5sV3xZ8bD1gJ6kM4nP9qS2tU0wY7aC5eG3iK8lN1oQ6rT4uW9yB2dF0hI7jL5mO3pR8sV1xZ6bD4gJ9kM2nP0qS7tU5wY3aC8eG1iK6lN4oQ9rT2uW0yB7dF5hI3jL8mO1pR6sV4xZ9bD2gJ0kM7nP5qS3tU8wY1aC6eG4iK9lN2oQ0rT7uW5yB3dF8hI1jL6mO4pR9sV2xZ0bD7gJ5kM3nP8qS1tU6wY4aC9eG2iK0l" }}11.2 SellerCapacityCredential (Thin Claim)
Section titled “11.2 SellerCapacityCredential (Thin Claim)”A complete ownership credential — thin assertion only, no duplicated title data:
{ "@context": [ "https://www.w3.org/ns/credentials/v2", "https://trust.propdata.org.uk/ns/pdtf/v2" ], "id": "urn:pdtf:vc:own-3a2b1c7f-4e9d-6a5f-7c8b-1e0d2f3a4b5c", "type": ["VerifiableCredential", "SellerCapacityCredential"], "issuer": "did:web:platform.example.com", "validFrom": "2026-03-18T09:00:00Z", "credentialSubject": { "id": "urn:pdtf:capacity:own-a1b2c3", "seller": "did:key:z6MkhRqN4v5sW8xZ1bD4gJ6kM9nP2qS0tSellerAbc", "transaction": "did:web:platform.example.com:transactions:tx-789", "sellersCapacity": { "capacity": "Legal Owner" }, "dateBecameOwnerOrAuthority": "2014-06-01" }, "evidence": [ { "type": "ElectronicRecord", "source": "landregistry.data.gov.uk", "retrievedAt": "2026-03-18T08:55:00Z", "method": "OC1 proprietorship name match" }, { "type": "UserAttestation", "source": "did:key:z6MkhRqN4v5sW8xZ1bD4gJ6kM9nP2qS0tSellerAbc", "attestedAt": "2026-03-18T08:50:00Z", "method": "SellerCapacity declaration during onboarding" } ], "termsOfUse": [{ "type": "PdtfAccessPolicy", "confidentiality": "restricted", "pii": true, "roleRestrictions": ["sellerConveyancer", "buyerConveyancer", "estateAgent"] }], "credentialStatus": { "id": "https://api.platform.example.com/status/ownership/list-001#892", "type": "BitstringStatusListEntry", "statusPurpose": "revocation", "statusListIndex": "892", "statusListCredential": "https://api.platform.example.com/status/ownership/list-001" }, "proof": { "type": "DataIntegrityProof", "cryptosuite": "eddsa-jcs-2022", "verificationMethod": "did:web:platform.example.com#platform-key-1", "proofPurpose": "assertionMethod", "created": "2026-03-18T09:00:00Z", "proofValue": "z5tPqR7sK2mN4vL8xZ1bD4gJ6kM9nP2qS0tU7wY3aC5eG8iK1lN4oQ6rT9uW2yB0dF3hI5jL8mO1pR7sV4xZ6bD2gJ9kM0nP5qS3tU8wY1aC7eG4iK6lN9oQ2rT0uW5yB3dF8hI1jL4mO6pR9sV2xZ7bD0gJ5kM3nP8qS1tU6wY4aC9eG2iK0lN7oQ5rT3uW8yB1dF6hI4jL9mO2pR0sV7xZ5bD3gJ8kM1nP6qS4tU9wY2aC0eG7iK3lN5oQ8rT1uW6yB4dF9hI2jL0mO7pR5sV3xZ8bD1gJ6kM4nP9qS2tU0wY7" }}11.3 RepresentationCredential
Section titled “11.3 RepresentationCredential”A complete representation credential — seller instructs a conveyancer, whose firm is on the Transaction roster:
{ "@context": [ "https://www.w3.org/ns/credentials/v2", "https://trust.propdata.org.uk/ns/pdtf/v2" ], "id": "urn:pdtf:vc:rep-1c7f3a2b-9d4e-5f6a-8b7c-0d1e2f3a4b5c", "type": ["VerifiableCredential", "RepresentationCredential"], "issuer": "did:web:platform.example.com", "validFrom": "2026-03-15T11:00:00Z", "credentialSubject": { "id": "urn:pdtf:representation:rep-d4e5f6", "representative": "did:key:z6MkjConveyancerT3uW8yB1dF6hI4jL9mO2pR0sV7xDef", "representedParty": "did:key:z6MkhRqN4v5sW8xZ1bD4gJ6kM9nP2qS0tSellerAbc", "role": "Seller's Conveyancer", "transaction": "did:web:platform.example.com:transactions:tx-789" }, "evidence": [{ "type": "UserAttestation", "source": "did:key:z6MkhRqN4v5sW8xZ1bD4gJ6kM9nP2qS0tSellerAbc", "attestedAt": "2026-03-15T10:55:00Z", "method": "Platform instruction flow — seller selected conveyancer" }], "termsOfUse": [{ "type": "PdtfAccessPolicy", "confidentiality": "restricted", "pii": true, "roleRestrictions": ["sellerConveyancer", "buyerConveyancer"] }], "credentialStatus": { "id": "https://api.platform.example.com/status/representation/list-001#334", "type": "BitstringStatusListEntry", "statusPurpose": "revocation", "statusListIndex": "334", "statusListCredential": "https://api.platform.example.com/status/representation/list-001" }, "proof": { "type": "DataIntegrityProof", "cryptosuite": "eddsa-jcs-2022", "verificationMethod": "did:web:platform.example.com#platform-key-1", "proofPurpose": "assertionMethod", "created": "2026-03-15T11:00:00Z", "proofValue": "zK8mN4rJP2sL7vX0bD3gJ5kM8nQ1tU4wY6aC9eG2iK0lN7oQ5rT3uW8yB1dF6hI4jL9mO2pR0sV7xZ5bD3gJ8kM1nP6qS4tU9wY2aC0eG7iK3lN5oQ8rT1uW6yB4dF9hI2jL0mO7pR5sV3xZ8bD1gJ6kM4nP9qS2tU0wY7aC5eG3iK8lN1oQ6rT4uW9yB2dF0hI7jL5mO3pR8sV1xZ6bD4gJ9kM2nP0qS7tU5wY3aC8eG1iK6lN4oQ9rT2uW0yB7dF5hI3jL8mO1pR6sV4xZ9bD2gJ0kM7nP5qS3tU8wY1aC6eG4iK9lN2oQ0rT7" }}12. Migration from Verified Claims
Section titled “12. Migration from Verified Claims”12.1 Overview
Section titled “12.1 Overview”The current PDTF v1 system uses an OIDC-derived verified claims model. Each claim has a path (claimPath), a value (claimValue), and verification metadata (verification). PDTF 2.0 replaces this with W3C Verifiable Credentials.
This section provides a detailed mapping to guide the migration.
12.2 Structural Mapping
Section titled “12.2 Structural Mapping”| v1 Verified Claim Field | v2 VC Equivalent | Notes |
|---|---|---|
claimPath | Position within credentialSubject object | e.g. /propertyPack/heating/heatingType → credentialSubject.heating.heatingSystem.heatingType |
claimValue | Value at the corresponding path in credentialSubject | Direct value assignment |
verification.trust_framework | issuer DID + OpenID Federation trust resolution | Trust is cryptographic, not framework-declared |
verification.time | validFrom | When the verification/assertion was made |
verification.evidence | evidence | Simplified — see §6 |
verification.evidence[].type (electronic_record) | evidence[].type (ElectronicRecord) | PascalCase, simplified fields |
verification.evidence[].type (document) | evidence[].type (DocumentExtraction) | Renamed, flattened |
verification.evidence[].type (vouch) | evidence[].type (ProfessionalVerification) | Renamed, clearer semantics |
verification.evidence[].record.source.name | evidence[].source | Flattened — no nested record.source |
verification.evidence[].record.created_at | evidence[].retrievedAt | Renamed for clarity |
verification.evidence[].document.issuer.name | evidence[].source | Flattened |
verification.evidence[].document.document_details | evidence[].documentType, evidence[].documentHash | Key fields extracted, rest dropped |
verification.evidence[].voucher.name | evidence[].source (DID) | Person/org name → DID reference |
verification.evidence[].attestation | evidence[].notes (ProfessionalVerification) | Free text preserved |
terms_of_use.confidentiality | termsOfUse[].confidentiality | Same semantics |
terms_of_use.pii | termsOfUse[].pii | Same semantics |
terms_of_use.roleRestrictions | termsOfUse[].roleRestrictions | Same semantics, same values |
| (no equivalent) | credentialStatus | New — revocation support |
| (no equivalent) | proof | New — cryptographic signature |
| (no equivalent) | @context | New — JSON-LD context |
verifiedClaim.id | id (optional) | Credential identifier |
12.3 Path Mapping
Section titled “12.3 Path Mapping”The claimPath in v1 uses the v3 schema paths. In v2, paths are relative to the entity’s credentialSubject:
| v1 claimPath | v2 Entity | v2 credentialSubject Path |
|---|---|---|
/propertyPack/energyEfficiency/certificate/currentEnergyRating | PropertyCredential | energyEfficiency.certificate.currentEnergyRating |
/propertyPack/heating/heatingSystem/heatingType | PropertyCredential | heating.heatingSystem.heatingType |
/propertyPack/environmentalIssues/flooding/floodZone | PropertyCredential | environmentalIssues.flooding.floodZone |
/propertyPack/buildInformation/buildDate | PropertyCredential | buildInformation.buildDate |
/propertyPack/fixturesAndFittings/bathroom/items | PropertyCredential | fixturesAndFittings.bathroom.items |
/propertyPack/address/line1 | PropertyCredential | address.line1 |
/propertyPack/titlesToBeSold/0/registerExtract/proprietorship | TitleCredential | registerExtract.proprietorship |
/propertyPack/titlesToBeSold/0/titleNumber | TitleCredential | (part of subject ID) |
/propertyPack/ownership/ownershipsToBeTransferred/0/ownershipType | TitleCredential | ownership.ownershipType |
/status | TransactionCredential | status |
/milestones/saleAgreed | TransactionCredential | milestones.saleAgreed |
/offers/{id}/amount | OfferCredential | amount |
/offers/{id}/status | OfferCredential | status |
/participants/0/name | (Person entity — not a VC path) | (Person entities are not claimPath-based) |
12.4 Migration Strategy
Section titled “12.4 Migration Strategy”The migration is not a big-bang cutover. It follows the dual state assembly approach (see 00 — Architecture Overview §8):
- Phase 1: Continue issuing v1 verified claims.
composeStateFromClaimsworks unchanged. - Phase 2: Begin issuing VCs in parallel. Each adapter produces both a v1 claim and a v2 VC for the same data.
composeV3StateFromGraphruns in shadow mode, output compared againstcomposeStateFromClaims. - Phase 3: Once outputs match consistently, switch internal consumers to
composeV3StateFromGraph(orcomposeV4StateFromGraphfor new consumers). - Phase 4: Stop issuing v1 verified claims. All data is VC-only.
The migration is per-adapter, not all-at-once. The EPC adapter migrates first (just rebuilt, natural candidate), then HMLR, then others.
12.5 Claim Grouping
Section titled “12.5 Claim Grouping”In v1, each claimPath:claimValue pair is an independent claim. In v2, related paths are grouped into a single credential’s credentialSubject. The grouping follows the entity paths — all EPC data goes into one PropertyCredential, all HMLR data into one TitleCredential, etc.
v1 (4 separate claims):
[ { "claimPath": "/propertyPack/energyEfficiency/certificate/currentEnergyRating", "claimValue": "C" }, { "claimPath": "/propertyPack/energyEfficiency/certificate/currentEnergyEfficiency", "claimValue": 72 }, { "claimPath": "/propertyPack/energyEfficiency/certificate/lodgementDate", "claimValue": "2024-01-15" }, { "claimPath": "/propertyPack/energyEfficiency/certificate/certificateNumber", "claimValue": "1234-5678-9012-3456-7890" }]v2 (1 credential):
{ "type": ["VerifiableCredential", "PropertyCredential"], "credentialSubject": { "id": "urn:pdtf:uprn:100023456789", "energyEfficiency": { "certificate": { "currentEnergyRating": "C", "currentEnergyEfficiency": 72, "lodgementDate": "2024-01-15", "certificateNumber": "1234-5678-9012-3456-7890" } } }}This is more natural, more efficient (one signature instead of four), and preserves the relationship between related data points.
13. Open Questions
Section titled “13. Open Questions”13.1 For LMS / Implementer Discussion
Section titled “13.1 For LMS / Implementer Discussion”-
D5 consensus: Sparse objects + dependency pruning vs section-level REPLACE. The pruning approach is more elegant but more complex to implement. Do implementers prefer the simpler section-level REPLACE? What’s the minimum viable merge strategy?
-
Credential granularity for seller forms. When a seller fills in BASPI, should each section (heating, fixtures, legal questions) become a separate PropertyCredential, or should the entire form submission be one credential? Separate credentials allow finer-grained revocation and re-attestation. One credential is simpler to issue.
-
termsOfUse defaults. Should there be default access policies per credential type, or must every credential explicitly declare its termsOfUse? Defaults reduce boilerplate but add implicit behaviour.
-
Evidence granularity. Should evidence be per-credential (current design) or per-path within a credential? Per-credential is simpler but means all paths in a credential share the same evidence metadata. Per-path is more precise but adds significant complexity.
-
Credential ID assignment. Should all credentials have an
idfield, or should it be optional for privacy reasons? IDs enable deduplication and reference but create correlation vectors.
13.2 Architectural
Section titled “13.2 Architectural”-
Multi-credential merge conflicts. When two credentials for the same entity have overlapping paths with conflicting values, which wins? Current design: later
validFromwins. But what about concurrent issuance? Should there be a priority based on issuer trust level (root issuer > trusted proxy > user attestation)? -
Credential versioning. When an issuer re-issues a credential for the same data (e.g. new EPC), should it explicitly reference the credential it supersedes? The W3C VC model doesn’t have a built-in “supersedes” mechanism.
-
Status list sizing. How many credentials per status list? The W3C spec suggests 16KB minimum (131,072 bit positions). Is that sufficient for PDTF’s expected credential volume per issuer?
-
Context evolution. How do we handle adding new property data paths to the JSON-LD context without breaking existing credentials? JSON-LD’s open-world assumption helps, but tooling may not handle unknown terms gracefully.
13.3 Internal (Platform)
Section titled “13.3 Internal (Platform)”-
Custodial signing for user attestations. In Phase 1, the platform signs on behalf of users. The evidence records the user’s DID. But the proof doesn’t come from the user’s key. Is this semantically honest? Should we use a different proof mechanism (e.g. counter-signature) that makes the custodial relationship explicit?
-
Adapter credential caching. Should adapters cache issued credentials (return the same VC for repeated requests for the same data) or always issue fresh ones? Caching is efficient but means the adapter stores credentials. Fresh issuance is stateless but means multiple VCs for the same data.
-
Person/Organisation VCs. This spec deliberately doesn’t define PersonCredential or OrganisationCredential. Person and Organisation data is identity data, not property data. Should there be separate identity credentials, or are the entity records in the graph sufficient?
13.4 Credential Format & Securing Mechanism
Section titled “13.4 Credential Format & Securing Mechanism”(New — July 2026, following a review of the GOV.UK Wallet and eIDAS 2.0 credential-format commitments.)
-
Primary credential format: Data Integrity (JSON-LD) vs SD-JWT-VC vs mdoc. §2.4/§9/§10 currently commit to embedded Data Integrity proofs over JSON-LD. The wallet ecosystem PDTF must interoperate with points elsewhere:
- GOV.UK Wallet mandates
mdoc(ISO/IEC 18013-5) for new credentials; JSON-LD JWT-VC is a legacy exception only, and neither SD-JWT-VC nor JSON-LD Data Integrity is otherwise surfaced. - eIDAS 2.0 mandates SD-JWT-VC + mdoc; W3C VCDM is optional (EAAs only). OpenID4VC HAIP profiles SD-JWT-VC + mdoc.
- Selective disclosure — dismissed as “not needed” in §2.4 — is in fact relevant to property data minimisation and is native to SD-JWT-VC/mdoc but not to plain Data Integrity.
Two layers to decide separately:
- Identity ingestion (GOV.UK-verified identity →
Person.identityBinding, and the cross-party reuse in 03 §10.5): PDTF must be able to verifymdocvia OpenID4VP (plus One Login OIDC) regardless — this is forced by the wallet. - PDTF’s own property credentials (Property, Title, SellerCapacity, …): free choice. Leading candidate is SD-JWT-VC (JSON, native selective disclosure, EU-aligned, HAIP-profiled), keeping mdoc confined to the identity boundary.
Provisional decision (adopted in this draft). §2.4/§9/§10 now document SD-JWT-VC as the primary securing mechanism + mdoc at the GOV.UK identity boundary (dual-format, mirroring HAIP/eIDAS); Data Integrity / JSON-LD is demoted to a superseded fallback and an optional semantic overlay. AI-agent legibility is not a discriminator here — agents read the composed entity graph over the API, not raw credentials.
Still open (why this remains a question):
- Confirm SD-JWT-VC against the GOV.UK Wallet’s current published format list, and ratify via consultation Q20 before locking in.
- Revocation: SD-JWT-VC uses the IETF Token Status List (
statusclaim), not W3C BitstringStatusList — sub-spec 14 (revocation) must be updated to match. - Example migration: the ~15 inline credential examples in this spec still show the superseded JSON-LD +
DataIntegrityProofserialisation and need reissuing as SD-JWT-VC (claims model unchanged). - Ripple: references in the reference docs (credential-types, schemas, urn-scheme) and specs 03/04 that assume Data Integrity /
@contextneed reconciling. - Decide whether PDTF keeps the optional JSON-LD
@contextoverlay for linked-data / ontology tooling, or drops it entirely.
- GOV.UK Wallet mandates
14. Implementation Notes
Section titled “14. Implementation Notes”14.1 Reference Libraries
Section titled “14.1 Reference Libraries”The following reference implementations support this spec:
| Library | Purpose | Status |
|---|---|---|
@pdtf/vc-builder | Create and sign PDTF VCs | Planned |
@pdtf/vc-validator | Validate VC structure, proof, issuer, revocation | Planned |
@pdtf/did-resolver | Resolve did:key and did:web to public keys | Planned |
@pdtf/state-assembler | MERGE + prune state from VCs | Planned |
@pdtf/context | PDTF v2 JSON-LD context document | Planned |
14.2 VC Builder Flow
Section titled “14.2 VC Builder Flow”Input: entity data (sparse object), entity ID, issuer DID, evidence, termsOfUse → Construct credentialSubject from entity data + ID → Determine credential type from entity type → Set @context, type, issuer, validFrom → Attach evidence, termsOfUse → Allocate status list index from issuer's current status list → Attach credentialStatus → JCS-canonicalise the document → Sign with issuer's Ed25519 private key → Attach proof → Output: complete signed VC14.3 VC Validator Flow
Section titled “14.3 VC Validator Flow”Input: VC document → Validate JSON structure (required fields, types) → Validate @context includes VC v2 + PDTF v2 → Validate type includes VerifiableCredential + PDTF type → Validate credentialSubject.id format matches credential type → Extract issuer DID → Resolve DID → DID document → public key → Verify Data Integrity proof (JCS canonical, Ed25519 verify) → Check credentialStatus: → Fetch BitstringStatusList credential (with caching) → Verify status list credential's own proof → Check bit at statusListIndex → Check validFrom ≤ now ≤ validUntil (if present) → Optionally: resolve issuer trust chain to the Trust Anchor and check Trust Mark delegation claim for authorised paths → Output: { valid, revoked, expired, issuerTrust, errors[] }14.4 Credential Sizing
Section titled “14.4 Credential Sizing”Estimated credential sizes (JSON, uncompressed):
| Credential Type | Typical Size | Notes |
|---|---|---|
| PropertyCredential (EPC) | 1.5–2 KB | Certificate + recommendations |
| PropertyCredential (seller form section) | 0.5–3 KB | Varies by section |
| PropertyCredential (full seller form) | 8–15 KB | All BASPI sections combined |
| TitleCredential | 2–5 KB | Register extract + ownership type |
| SellerCapacityCredential | 0.8–1 KB | Thin — smallest credential type |
| RepresentationCredential | 0.8–1 KB | Thin — similar to SellerCapacity |
| TransactionRoleCredential | 0.7–0.9 KB | Thin — three claims |
| GiftCredential | 1–1.5 KB | Includes gift terms |
| OfferCredential | 1–2 KB | Amount, conditions, buyer circumstances |
| TransactionCredential | 1.5–3 KB | Status, milestones, sale context |
A typical transaction might have 20–40 credentials totalling 30–80 KB of VC data.
14.5 Credential Lifecycle
Section titled “14.5 Credential Lifecycle” ┌───────────┐ │ Created │ │ (issued + │ │ signed) │ └─────┬─────┘ │ ┌─────▼─────┐ │ Active │◄──── Verifiers check proof + │ (valid, │ status list. Bit = 0. │ not │ │ revoked) │ └─────┬─────┘ │ ┌───────────┼───────────┐ │ │ │ ┌─────▼─────┐ ┌──▼──┐ ┌─────▼─────┐ │ Superseded │ │Revoked│ │ Expired │ │ (new VC │ │(bit=1│ │ (validUntil│ │ replaces) │ │in SL)│ │ passed) │ └───────────┘ └──────┘ └───────────┘- Superseded: A new credential covers the same data. The old credential is revoked (bit flipped) and the new one takes its place.
- Revoked: Explicitly invalidated (e.g. mandate withdrawn, error found). Bit flipped in status list.
- Expired:
validUntilhas passed. No bit flip needed — verifiers check the date.
14.6 Relationship to Other Sub-Specs
Section titled “14.6 Relationship to Other Sub-Specs”| Sub-spec | Relationship |
|---|---|
| 01 — Entity Graph | Defines the entity schemas that shape credentialSubject. This spec wraps those shapes in VCs. |
| 03 — DID Methods | Defines how issuer DIDs and verificationMethod DIDs resolve. This spec references them. |
| 04 — OpenID Federation | Defines which issuers are trusted for which entity:path combos, via Trust Mark delegation claims. This spec’s credentials are validated against those Trust Marks. |
| 07 — State Assembly | Implements the MERGE + prune semantics defined in §5 of this spec. |
| 08 — DE Migration | Maps DE evaluation paths to credential credentialSubject paths. |
| 14 — Credential Revocation | Details the hosting, caching, and operational aspects of BitstringStatusList referenced in §8. |
14.7 Security Considerations
Section titled “14.7 Security Considerations”-
Proof verification is mandatory. Never trust a credential without verifying its proof. Even internal systems must verify — defence in depth.
-
Status list freshness. A cached status list may not reflect recent revocations. For security-critical checks (e.g. verifying a representation credential before granting data access), use short cache TTL or force-refresh.
-
Issuer impersonation.
did:webresolution depends on DNS. An attacker who compromises a domain could issue fraudulent credentials. Mitigation: OpenID Federation trust resolution provides a second check — the issuer must hold a valid Trust Mark whosedelegation.authorised_pathscovers the relevant paths, and the Subordinate Entity Statement from the Trust Anchor pins the expected JWKS. Monitor DID document changes. -
Credential correlation. Credentials with
idfields create correlation vectors — the same credential ID appearing in different contexts links those contexts. For privacy-sensitive credentials, omit theidfield. -
Key compromise. If an issuer’s private key is compromised, all credentials signed with that key are suspect. The issuer must: rotate keys in the DID document, revoke all credentials signed with the compromised key, re-issue with the new key. See 06 — Key Management for rotation procedures.
-
PII in evidence. Evidence fields like
sourcemay contain a person’s DID, which could be PII. ThetermsOfUse.piiflag should be set accordingly, and evidence data must be handled with the same access controls as the credential subject data.
Appendix A: Architectural Decisions Referenced
Section titled “Appendix A: Architectural Decisions Referenced”| # | Decision | Status | Relevance to This Spec |
|---|---|---|---|
| D3 | Representation to Organisations, not Persons | ⚪ Superseded by D33 | §3.5 — the representative is the instructed party (Person or Organisation); the firm is on the Transaction roster |
| D32 | Role lives only on the relationship credential; every credential references the Transaction | ✅ Confirmed | §3.4–3.7a, §7.4 — no role on the roster; nothing nests |
| D33 | One Representation per (representative, represented party) pair | ✅ Confirmed | §3.5 |
| D4 | Property-level VCs, not first-class entity VCs | ✅ Confirmed | §3.2 — EPC is a PropertyCredential, not EPCCredential |
| D5 | Sparse objects + dependency pruning | 🟡 Needs consensus | §5 — Claims representation model |
| D6 | Simpler evidence model | ✅ Confirmed | §6 — Four evidence types replacing OIDC-derived schema |
| D7 | did:key for users, did:web for transactions/adapters | ✅ Confirmed | §9 — verificationMethod DID formats |
| D14 | Digital ID wallet binding (future, custodial for now) | ✅ Confirmed | §3.2 note on custodial signing |
| D16 | Ed25519 key algorithm | ✅ Confirmed | §9 — eddsa-jcs-2022 cryptosuite |
| D18 | Bitstring Status List revocation mandatory | ✅ Confirmed | §8 — credentialStatus required on all VCs |
| D28 | SellerCapacity credential is thin (claim-vs-evidence separation) | ✅ Confirmed | §3.4 — SellerCapacityCredential design |
Appendix B: Credential Type Quick Reference
Section titled “Appendix B: Credential Type Quick Reference”PropertyCredential subject: urn:pdtf:uprn:{uprn} claims: property facts (EPC, flood, heating, fixtures, searches, ...) issuer: trusted proxy / root issuer / platform (for user attestations)
TitleCredential subject: urn:pdtf:titleNumber:{n} | urn:pdtf:unregisteredTitle:{id} claims: register extract, ownership type, leasehold terms issuer: HMLR proxy / HMLR root issuer
SellerCapacityCredential ⇒ Seller subject: urn:pdtf:capacity:{id} claims: seller, transaction, title?, sellersCapacity, dateBecameOwnerOrAuthority issuer: account provider (platform) NOTE: thin — no title details, just the capacity; one per seller
OfferCredential ⇒ Buyer subject: urn:pdtf:offer:{id} claims: buyer, transaction, offerId, amount, currency, status, conditions, buyerCircumstances issuer: platform (on behalf of buyer)
GiftCredential ⇒ Giftor subject: urn:pdtf:gift:{id} claims: donor, transaction, offerId, giftDetails issuer: platform (on behalf of giftor)
RepresentationCredential role: which kind subject: urn:pdtf:representation:{id} claims: representative, representedParty, role, transaction issuer: platform (on behalf of the instructing party) NOTE: one per (representative, represented party) pair
TransactionRoleCredential role: which role subject: urn:pdtf:role:{id} claims: participant, role, transaction issuer: platform
TransactionCredential subject: did:web:{host}:transactions:{id} claims: status, milestones, saleContext, offers, property, titlesToBeSold, participants (roster — no roles) issuer: platform