Skip to content

feat: DataProofsDotnet.Legacy — Ed25519Signature2020 / EcdsaSecp256r1Signature2019 (#7)#8

Merged
moisesja merged 2 commits into
mainfrom
feat/issue-7-legacy-ld-signature-suites
Jun 15, 2026
Merged

feat: DataProofsDotnet.Legacy — Ed25519Signature2020 / EcdsaSecp256r1Signature2019 (#7)#8
moisesja merged 2 commits into
mainfrom
feat/issue-7-legacy-ld-signature-suites

Conversation

@moisesja

Copy link
Copy Markdown
Owner

Summary

Closes #7. Ships the pre–Data-Integrity Linked-Data-Signature cryptosuites as a new opt-in DataProofsDotnet.Legacy package, building on the #4 verify-dispatch hook (0.1.0-preview.2).

  • Ed25519Signature2020Cryptosuite (Ed25519) and EcdsaSecp256r1Signature2019Cryptosuite (P-256), each implementing ICryptosuite.
  • Two canonicalization variants per suite (chosen at construction; JCS is the back-compat default):
    • JCS — signing input = JCS(document with the proof nested under "proof", minus proofValue/cryptosuite), fed directly to the signer (the 2020-era convention — not the 2022 separate-config + hash-concat).
    • RDFC-1.0SHA-256(RDFC(proofOptions)) ‖ SHA-256(RDFC(document)).
  • Create→verify round-trips through DataIntegrityProofPipeline: create dispatches by cryptosuite (the suite's Name); verify dispatches by proof type via GetByProofType. Emitted proof carries type:"<suite>", no cryptosuite, base58-btc proofValue.
  • No DataProofsDotnet.Core changes; the suites are not in CryptosuiteRegistry.CreateDefault() — register them explicitly.
  • Unblocks zcap-dotnet's proof-pipeline delegation (zcap#108 Stage C) and legacy-VC verification in credentials-dotnet.

Byte-identity with zcap-dotnet (interop-critical)

The wire convention and signing bytes were reproduced from zcap's reference (ProofSigningPayloadBuilder + CryptoSuite) and are locked by a cross-stack golden vector: a zcap-issued Ed25519Signature2020 proof verifies through the pipeline, and re-signing the same input with the same seed reproduces zcap's exact proofValue.

Adversarial review — findings fixed

A multi-agent review (spec → implement → tests → adversarial verify) surfaced and these were resolved:

  • JCS proof-chain fail-open (HIGH): JCS unconditionally clobbered a pipeline-injected previousProof chain. Now fails closed when the unsecured document already carries a proof member (create throws; verify does not pass). RDFC binds chains natively. → LegacyChainBindingTests
  • Null-member byte divergence (MEDIUM): JCS now strips null object members (preserving null array elements), matching zcap's StripNullMembers. → JcsNullMemberByteIdentityTests
  • RDFC + undefined terms (CRIT/HIGH, documented): RDFC binds only @context-defined terms (members it does not define are dropped by RDF expansion — inherent to JSON-LD/RDFC, shared with the conformant rdfc-* suites). Documented on the suite/enum/CHANGELOG/PRD and pinned by characterization tests contrasting JCS (binds verbatim) vs RDFC. → RdfcExtensionBindingCharacterizationTests
  • P-256 malleability (LOW, documented): low-s not enforced (matches zcap + modern suites; enforcing it would break byte-compat). proofValues are malleable and must not be used as unique identifiers.

Acceptance criteria

  • Both suites, JCS + RDFC variants, in DataProofsDotnet.Legacy (not in CreateDefault).
  • Create→verify round-trips through DataIntegrityProofPipeline; type:"<suite>", no cryptosuite, base58-btc proofValue.
  • Golden vector proving byte-identity with a zcap-issued proof (verify + recreate).
  • Extension fields (capabilityChain) participate in the JCS signed bytes and round-trip; tamper fails closed.
  • Docs: legacy = interop only; prefer 2022/2019 for new proofs (README, CHANGELOG, PRD).

Verification

  • dotnet build DataProofsDotnet.sln0 warnings, 0 errors (warnings-as-errors).
  • dotnet test DataProofsDotnet.sln778 passed, 0 failed (Legacy 39; no regressions in Core/Rdfc/Jose/Cose). AC-6 dependency-hygiene gate green (dotNetRDF reaches Legacy only transitively via the Rdfc project reference).

🤖 Generated with Claude Code

Ship the pre–Data-Integrity Linked-Data-Signature cryptosuites as a new opt-in
`DataProofsDotnet.Legacy` package: `Ed25519Signature2020` and
`EcdsaSecp256r1Signature2019`, each with a JCS variant (default) and an RDFC-1.0
variant, implementing `ICryptosuite`. They create and verify through
`DataIntegrityProofPipeline` — create dispatches by `cryptosuite` naming the suite,
verify dispatches by proof `type` via `GetByProofType` — emitting `type:"<suite>"`,
no `cryptosuite`, and a base58-btc `proofValue`. The wire convention and signing
bytes are byte-identical to zcap-dotnet, locked by a cross-stack golden vector.

No changes to `DataProofsDotnet.Core`; the suites are not in `CreateDefault()`.

Adversarial-review fixes:
- JCS fails closed on a document already carrying a `proof` member (cannot bind a
  W3C proof chain); RDFC binds chains natively.
- JCS strips null object members before canonicalizing (zcap byte-identity).
- Documented limitations: RDFC binds only @context-defined terms (inherent to
  JSON-LD/RDFC); EcdsaSecp256r1Signature2019 does not enforce low-s (malleable
  proofValue, not a unique id).

Full solution: 778 tests pass, 0 warnings.

Closes #7

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@moisesja moisesja self-assigned this Jun 15, 2026
@moisesja moisesja added this to the 1.0.0 GA milestone Jun 15, 2026

@moisesja moisesja left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review — DataProofsDotnet.Legacy (Ed25519Signature2020 / EcdsaSecp256r1Signature2019)

The implementation is well-structured. The base-class split (LegacyCryptosuiteBase → concrete suites), the fail-closed JCS chain guard, null-member stripping for byte identity with zcap, and the documented limitations (RDFC term-binding, ECDSA malleability) are all solid. The adversarial-review summary in the description is exactly the right level of transparency.


CI — 4 failures, breakdown

Job Root cause Introduced by this PR?
build + test (windows-latest) 66 RdfCanonicalizationConformanceTests fail: expected length 87 vs actual 86 at index 85 — CRLF vs LF line ending in test fixtures on Windows No — pre-existing RDFC issue
ac-5 didcomm JWS/JWE behavior parity Parity tool: commit e98570cd… not found in .parity-src/didcomm-dotnet No — pre-existing fetch/SHA issue
ac-10 pack, install, and smoke Smoke script hardcodes version="0.1.0" but packages ship as 0.1.0-preview.2 No — pre-existing version mismatch
ac-9 samples coverage gate Two uncovered public members Yes — needs to be fixed before merge

Blocking: ac-9 — missing sample coverage for two new public APIs

The gate reports:

UNCOVERED: DataProofsDotnet.DataIntegrity.CryptosuiteRegistry.GetByProofType
UNCOVERED: DataProofsDotnet.DataIntegrity.ICryptosuite.SupportedProofTypes

Both members were introduced (or promoted to required public surface) by this PR to enable legacy proof-type dispatch. A DataProofsDotnet.Samples.DataIntegrityLegacy sample project should be added that demonstrates:

  1. Registering Ed25519Signature2020Cryptosuite / EcdsaSecp256r1Signature2019Cryptosuite explicitly (not via CreateDefault).
  2. A create→verify round-trip that exercises the type-based dispatch path (GetByProofType), so the coverage scanner sees the member referenced.
  3. SupportedProofTypes surfaced — either called directly or observed via the dispatch path.

This is exactly what AC-9/FR-21 checks: every public member visible to the surface scan must appear in at least one sample.


Non-blocking observations

  • LegacyCryptosuiteBase is internal abstract — good; consumers only touch the two concrete public types.
  • Verify fallback order (JCS → RDFC): A short comment noting that a failed JCS verify incurs a second RDFC canonicalization pass on the same document would help future readers reason about performance on hot verification paths.
  • RdfcDocumentCanonicalizer always allocated even for JCS-only usage (LegacyCryptosuiteBase.cs:457). The existing comment explains the rationale (immutability/thread-safety). Valid trade-off — just worth noting if allocation cost becomes a concern for high-throughput callers.

Fix the ac-9 gate (add a Legacy sample covering the two uncovered members) and this is ready to merge. The three pre-existing CI failures are out of scope for this change.


Generated by Claude Code

PR #8 review: the `ac-9` samples-coverage gate failed — `CryptosuiteRegistry
.GetByProofType` and `ICryptosuite.SupportedProofTypes` (the legacy dispatch
hooks) were referenced by no sample.

- Add `DataProofsDotnet.Samples.DataIntegrityLegacy`: registers the legacy
  suites explicitly, create→verify round-trips both suites (JCS) plus the RDFC
  variant, exercises `GetByProofType` and `SupportedProofTypes`, asserts
  type-only wire shape (no cryptosuite), and a tamper negative. Exit 0 on success.
- Close the root-cause gap: the coverage gate only scanned the original five
  package assemblies, so the new `DataProofsDotnet.Legacy` public surface escaped
  AC-9. Add it to the gate (assemblies array + project reference) and the
  five→six wording. Gate now: 528 members across 6 packages, 0 uncovered.
- Wire the sample into the solution and samples/README.
- Non-blocking review note: document the JCS→RDFC verify fallback's second
  canonicalization pass in LegacyCryptosuiteBase.

Full solution: 778 tests pass, build 0/0, sample exits 0, coverage gate green.
The three pre-existing CI failures (windows CRLF/LF, ac-5 parity SHA, ac-10
smoke version) are out of scope per the review.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@moisesja

Copy link
Copy Markdown
Owner Author

Addressed the blocking ac-9 finding in 863a4f5:

  • Added DataProofsDotnet.Samples.DataIntegrityLegacy — registers both legacy suites explicitly (not via CreateDefault), create→verify round-trips Ed25519Signature2020 and EcdsaSecp256r1Signature2019 (JCS) plus the RDFC variant, exercises GetByProofType and SupportedProofTypes, asserts the type-only wire shape (no cryptosuite), and includes a tamper negative. Exits 0.
  • Closed the root-cause gap: the coverage gate only scanned the original five package assemblies, so the new Legacy public surface escaped AC-9 entirely. Added Legacy to the gate (assemblies array + project reference). Gate now reports 528 members across 6 packages, 0 uncovered, 0 allowlisted (no allowlist entries added — genuinely covered).
  • Wired the sample into the solution + samples/README.md.
  • Non-blocking [BLOCKER] Publish DataProofsDotnet.Core to NuGet with a frozen public API surface #1: documented the JCS→RDFC verify fallback's second canonicalization pass in LegacyCryptosuiteBase. Non-blocking Confirm consumers own did:key URL parsing (library accepts resolved PublicKeyMaterial) #2 (always-allocated RdfcDocumentCanonicalizer) left as-is per your note.

Local: build 0/0, full solution 778 tests pass, sample exits 0, coverage gate green.

The three pre-existing CI failures (windows CRLF/LF in RDFC fixtures, ac-5 parity commit SHA, ac-10 smoke version) remain out of scope for this PR, as flagged.

@moisesja
moisesja merged commit 6773c1f into main Jun 15, 2026
9 of 12 checks passed
@moisesja
moisesja deleted the feat/issue-7-legacy-ld-signature-suites branch June 15, 2026 02:14
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.

Add legacy LD-Signature cryptosuites (Ed25519Signature2020 / EcdsaSecp256r1Signature2019) — DataProofsDotnet.Legacy

1 participant