Fix #4: verify pipeline dispatches legacy / type-named proofs#5
Conversation
The verify path previously dispatched a proof to a cryptosuite only when it carried a `cryptosuite` member AND `type == "DataIntegrityProof"`. Legacy Linked-Data-Signature proofs (Ed25519Signature2020, EcdsaSecp256r1Signature2019, ...) name their algorithm by `type` and carry no `cryptosuite`, so they could never be dispatched — violating the FR-4 "registering a new suite requires no pipeline changes" contract and producing a create→verify inconsistency (a custom suite could emit a legacy-shaped proof the same pipeline then refused to verify). Option A: a suite declares the proof type(s) it verifies via the new `ICryptosuite.SupportedProofTypes` default interface member (defaults to the single `DataIntegrityProof` type, so existing suites are unchanged and still dispatched by `cryptosuite` name). `CryptosuiteRegistry` maintains a secondary `type → suite` index for non-default types only (the JCS suites stay unambiguous, name-dispatched) exposed via `GetByProofType`. The verify pipeline dispatches by `cryptosuite` name first, falling back to proof `type` for legacy proofs. Dispatch only routes; each suite still re-validates type/cryptosuite/key/encoding/ signature itself. The library still creates only conformant 2022/2019 proofs — this is verification-only and non-breaking. Adversarial review found no acceptance bypass (default type excluded from the type index → no JCS dispatch by type; gate re-confirms the live suite claims the type; per-suite re-validation unchanged). Tests: LegacyProofTypeVerificationTests (round-trip, wire shape, fail-closed, unregistered-type rejection, JCS coexistence, resolver-path controller auth) and 6 added CryptosuiteRegistryTests. Bumps version to 0.1.0-preview.2. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review — looks solid ✅Reviewed the dispatch change on this security-critical verify path ( Independently verified
Security analysis of the dispatch (traced each path)The new
Two redundant layers (default-type exclusion from the index + the gate re-confirming the live suite claims the type), and each suite still re-validates Tests, docs (FR-4 + XML), CHANGELOG, version bump, and PublicAPI tracking are all in order. Scope is tight and the commit message is clear. Minor, non-blocking (optional)
Nice work — the adversarial pass, fail-closed layering, and coexistence tests are exactly what this path needed. 🤖 Reviewed with Claude Code |
…ll window Two minor, non-blocking notes from the PR review: 1. Same-type collision across two different suite names is silently last-registration-wins. Documented this on the CryptosuiteRegistry class remarks and GetByProofType (benign — each suite re-validates in VerifyProof); added Register_SameTypeUnderDifferentNames_IsLastRegistrationWins and Register_ReplacingASuite_DoesNotStealAnotherNamesTypeEntry. 2. Transient registration window where GetByProofType could briefly return null for a type the suite still claims. Reordered Register to publish the new suite's type entries BEFORE pruning the previous suite's, using a set difference so only types the new suite no longer claims are removed (value-matched, leaving other names' entries intact and re-registration idempotent). Added Register_SameInstanceTwice_IsIdempotent. Full suite: 739 passed, 0 failed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Closes #4.
Problem
DataIntegrityProofPipeline.VerifySingleProofAsyncdispatched a proof to a registered cryptosuite only when the proof carried acryptosuitemember andtype == "DataIntegrityProof". Legacy / pre–Data-Integrity Linked-Data-Signature proofs (Ed25519Signature2020,EcdsaSecp256r1Signature2019,Ed25519Signature2018,JsonWebSignature2020) identify their algorithm bytypeand carry nocryptosuitemember, so they could never be dispatched.This violated the FR-4 extensibility contract ("registering a new suite requires no pipeline changes") and produced a create→verify inconsistency: a custom suite could emit a legacy-shaped proof through
AddProofAsyncthat the same pipeline then refused to verify.Fix (Option A from the issue)
A suite declares the proof
type(s) it verifies; the verify path dispatches bycryptosuitename first (current behavior, always wins), then falls back totypefor legacy proofs.ICryptosuite.SupportedProofTypes— new default interface member (defaults to the singleDataIntegrityProoftype, backed by a shared static array). Non-breaking: existing suites compile unchanged and are still dispatched bycryptosuitename.CryptosuiteRegistry— secondarytype → suiteindex for non-default types only (the JCS suites all shareDataIntegrityProofand stay unambiguous, name-dispatched). NewGetByProofType(string?). Maintained under a registration lock with replace-aware, value-matched cleanup; reads stay lock-free.VerifySingleProofAsync—GetByName(proof.Cryptosuite) ?? GetByProofType(proof.Type), gated on the suite actually claiming the type.Dispatch only routes; each suite still re-validates
type/cryptosuite/key/encoding/signature inside its ownVerifyProof. The library still creates only conformant 2022/2019 proofs — this is verification-only.Security
Default type is excluded from the type index, so no new acceptance path opens for
DataIntegrityProofproofs with a missing/unknowncryptosuite(still rejected exactly as before). An adversarial subagent review attempted type-confusion, theDataIntegrityProof+no-cryptosuite regression, gate relaxation, stale/mis-pointed registry entries, missing-type and gate-evasion, and null/empty edges — verdict: no exploitable acceptance bypass; every path fails closed, enforced redundantly. Its one non-exploitable hardening note (null-tolerant gate) was applied.Tests & verification
dotnet build -c Release— 0 warnings / 0 errors (PublicApiAnalyzer + warnings-as-errors clean).dotnet test -c Release— 736 passed, 0 failed across all six test projects.LegacyProofTypeVerificationTests(8 — round-trip raw-key, wire shape carries nocryptosuite, tamper/wrong-key fail closed, unregistered-type rejected, JCS coexistence, resolver-path authorized + controller-authorization enforced); +6CryptosuiteRegistryTests.Acceptance criteria
Ed25519Signature2020suite (nocryptosuite) round-trips create→verify.eddsa-jcs-2022/ecdsa-jcs-2019round-trips unchanged.PROOF_VERIFICATION_ERROR.VerifyAsyncstill enforces controller authorization for legacy-type proofs.Version bumped to
0.1.0-preview.2.🤖 Generated with Claude Code