Skip to content

feat(domain): key passport signatures by disclosure set - #70

Merged
LKSNDRTMLKV merged 1 commit into
mainfrom
feat/disclosure-keyed-signatures
Jul 29, 2026
Merged

feat(domain): key passport signatures by disclosure set#70
LKSNDRTMLKV merged 1 commit into
mainfrom
feat/disclosure-keyed-signatures

Conversation

@LKSNDRTMLKV

Copy link
Copy Markdown
Member

Adds the Passport field and the naming vocabulary that let each audience receive a proof over its own view. Consumed by dpp-engine's audience-scoped read (dpp-engine#63); no behaviour changes in this repo on its own.

Do not publish yet. dpp-engine builds against this via the local [patch.crates-io] override for now.

The defect this exists to fix

A passport carries two proofs: jwsSignature over the full payload (Conformity), and publicJwsSignature over the public view. Cross that with Audience::may_see and a non-public reader has nothing that covers what they receive:

Audience Body served Proof it had
Public public view publicJwsSignature
LegitimateInterest public + restricted + individual publicJwsSignature — covers less
Authority public + restricted + conformity jwsSignature — covers more

Neither non-public case is "unsigned". Both were served a signature that fails against the bytes it arrived with, which to anyone actually checking is indistinguishable from tampering. A repairer or recycler making a safety or resale call on the data is exactly the reader who needs to verify it.

What lands here

  • Passport::disclosure_signatures: BTreeMap<String, String> — a proof per non-public disclosure set, frozen at publish beside its two siblings. BTreeMap so serialisation is key-ordered and the signed bytes are reproducible; skip_serializing_if empty, so drafts and existing rows are unaffected.
  • disclosure_key(&[Disclosure]) -> String and Audience::disclosure_key() / disclosure_set() — canonical set naming, tokens in Annex XIII order joined with +.
  • Disclosure::token() — a stable wire token, deliberately not Serialize-derived, because these strings are baked into stored artefact keys and must not move if a serde attribute changes.
public
public+restricted+individual
public+restricted+conformity

Why the key is the disclosure set and never the audience name

ESPR's actor vocabulary is ~14 classes (repairers, refurbishers, recyclers, customs, civil society, …), not battery Art. 77(2)'s three audiences, and the Art. 9(2)(f) delegated act mapping actors to data does not exist for any of our sectors. An artefact keyed "legitimateInterest" would need every passport re-signed the day that mapping lands. One keyed by the classes it actually covers keeps meaning exactly what it always meant, and a new actor taxonomy becomes a new mapping onto the same keys.

The tests assert this literally rather than by round-trip — a_disclosure_key_names_classes_never_an_audience fails if an audience name ever leaks into a key.

Tests

Four new in domain::identity: key contents, canonical ordering regardless of input order, disclosure_set agreeing with may_see for every audience/class pair, and the no-audience-names property. just check green.

Release note

This is a breaking change for anyone constructing Passport with a struct literal, so it wants 0.12.0 (lockstep) whenever it is published. Not bumped here.

@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 13 complexity · 6 duplication

Metric Results
Complexity 13
Duplication 6

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant