Vote for your favorite SkillMD. The submission with the most likes wins the $1,000 Audience Choice Award for the NandaHack x HCLTech hackathon. Voting is open through September 25.Vote now →
mergedhumanPR #66Trust

trust: parc — selective disclosure via Merkle inclusion proofs (stacked on #63)

Selective disclosure for trust: parc credentials: a holder reveals only chosen receipts from their ledger, each with a Merkle inclusion proof against the credential's signed behavioral_merkle_root — a verifier confirms every revealed…

Author

Sharathvc23 avatar

@Sharathvc23

github profile →
Status
Merged
Merged on
Jul 8
Branch
hackathon/stellarminds-ai-parc-selective-disclosure

Description

The pitch.

> **Stacked on #63 — review the top commit only** (`123c8d9`). The base commit here is #63's `trust: parc` unchanged; this PR will be rebased to a single commit once #63 merges.

## What this adds

Selective disclosure for `trust: parc` credentials: a holder reveals only chosen receipts from their ledger, each with a **Merkle inclusion proof** against the credential's signed `behavioral_merkle_root` — a verifier confirms every revealed receipt is genuinely part of the committed history **without seeing the rest of the ledger**.

- `inclusion_proof(receipts, *, receipt)` / `verify_inclusion(receipt, proof, root)` — proofs over the exact tree `merkle_root` already builds (lexicographically sorted leaf digests, odd-node duplication). Stdlib-only, no new dependencies.
- `ParcTrust.build_presentation(credential, receipts, *, disclose)` — `{credential, disclosed: [{receipt, proof}]}`, byte-deterministic.
- `ParcTrust.verify_presentation(presentation, *, identity, expected_issuer=None)` — verifies the credential's Ed25519 proof (same as-of key-window path as `admit()`), each inclusion proof, and that every proof's `leaf_count` equals the signed `receipt_count` (the credential's own bound on undisclosed scope). Typed failure reasons: `malformed_presentation`, `issuer_mismatch`, `bad_credential_proof`, `malformed_proof`, `count_mismatch`, `not_included`.

## What it deliberately does NOT do

Disclosure proves *which receipts happened*. It does **not** recompute reputation scores — score aggregation needs the whole receipt graph (collusion-ring severance requires every edge), so the signed aggregate from issuance stands. The two layers stay separate, matching the recomputing-gate philosophy of #63: membership is provable piecewise; standing is not.

## Scope note / roadmap

The #63 credential embeds the full receipts list in the signed document (the recomputing `admit()` gate consumes it), so presentations are not receipts-private until a receipts-free credential variant exists. `verify_presentation` deliberately never reads the embedded list — it verifies from root + signed count only — so such a variant (a small `build_credential` option, left out here to avoid touching #63's signed format) will verify unchanged. Happy to send that as a follow-up if there's interest.

## Tests

`make ci-local` green: **777 → 797** (+20: tree/proof round-trips incl. a Hypothesis property over random ledgers, tamper/forgery/wrong-ledger/count-lie adversarial cases, byte-determinism). Demo:

```
ledger receipts:        20
disclosed:              ['r03', 'r07', 'r11']
verify (genuine):       ok=True reasons=()
verify (tampered):      ok=False reasons=('not_included',)
```

Doc: `docs/layers/trust.md` §parc gains a "Selective disclosure" subsection.

Try it

Open PR on GitHubView diff

Checkout locally

git fetch origin pull/66/head:pr-66
git checkout pr-66