trust: parc — selective disclosure via Merkle inclusion proofs (stacked on #63)#66
Draft
Sharathvc23 wants to merge 2 commits into
Draft
Conversation
…ssion gate Reputation in NEST dies with the run: no plugin exports it, carries it, or verifies it in another trust domain. This adds the parc trust plugin (extending the merged agent_receipts receipt maths), the parc_migration two-domain scenario, and six adversarial validators. - Export: an agent's receipt ledger becomes a W3C-VC-shaped credential (behavioral_merkle_root + nanda-rep/0.2 scores as fixed 6-decimal strings), signed through the ed25519_rotating identity layer with key_id pinned in proof.verificationMethod (vendored did:key encoder, stdlib-only). - Admission: the gate recomputes the root and scores from the carried receipts instead of trusting signed claims — an issuer-inflated, genuinely re-signed score is rejected (score_mismatch). A valid signature is not admission. - Cross-layer: proofs are checked against the issuer's key-rotation window as-of the credential's validFrom tick (stale_key), using genuine rotation records replayed onto the gate. - Ring severance at the border: an inline single-subject ledger is a star graph and cannot show an N-party ring, so the gate re-runs whole-graph collusion severance over the originating domain's published ledger (severed_below_threshold). - Differential proof: with task.config.naive_gate (signed claims trusted) the inflation + ring validators FAIL; under trust: score_average no credentials exist and every validator fails. Registered in _BUILTINS and as a real nest.plugins.trust entry point. 41 new tests (hostile paths per typed reason, Hypothesis properties, 5-seed e2e, byte-identical determinism). make ci-local green.
A holder can now reveal a chosen subset of receipts instead of the whole ledger: inclusion_proof/verify_inclusion build and check authentication paths over the exact tree merkle_root computes (lexicographically sorted hex leaves, hex-concat parents, odd-node duplication), so proofs verify against the behavioral_merkle_root a credential already signs. ParcTrust.build_presentation packages the credential plus per-receipt proofs; ParcTrust.verify_presentation checks the credential's Ed25519 proof through the same as-of key-window path as admit(), each disclosed receipt's membership under the signed root, and each proof's leaf_count against the signed receipt_count — the bound on the undisclosed remainder. Typed reasons throughout (bad_credential_proof, not_included, count_mismatch, malformed_proof, issuer_mismatch, malformed_presentation). Scores are deliberately never recomputed from a disclosed subset: the aggregate is a whole-graph property, so the signed value stands and full-ledger recomputation remains admit()'s job.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this adds
Selective disclosure for
trust: parccredentials: a holder reveals only chosen receipts from their ledger, each with a Merkle inclusion proof against the credential's signedbehavioral_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 treemerkle_rootalready 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 asadmit()), each inclusion proof, and that every proof'sleaf_countequals the signedreceipt_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_presentationdeliberately never reads the embedded list — it verifies from root + signed count only — so such a variant (a smallbuild_credentialoption, 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-localgreen: 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:Doc:
docs/layers/trust.md§parc gains a "Selective disclosure" subsection.