Skip to content

fix(crypto): complete input-bound hardening - #28

Merged
piotr-roslaniec merged 3 commits into
devfrom
codex/input-hardening-followups
Sep 29, 2026
Merged

piotr-roslaniec merged 3 commits into
devfrom
codex/input-hardening-followups

Conversation

@piotr-roslaniec

Copy link
Copy Markdown

Summary

Focused follow-up to the review of #12 (agent-docs/pr-integration/12.md), addressing the four validated gaps that were explicitly called out as non-blocking follow-ups in that review (not the separate broad fuzz-harness PR).

  • 12-F1: ecdsa/keygen/messages.go KGRound3Message.UnmarshalProofInts indexed proofBzs[0..12] with no arity guard; a direct exported-decoder call panicked on fewer than 13 parts. Now validates exact arity (paillier.ProofIters = 13) via common.NonEmptyMultiBytes and returns an error.
  • 12-F2: KGRound2Message1.UnmarshalFactorProof / UnmarshalFactorProofTilde dereferenced a nil Facproof/FacproofTilde submessage; a direct call panicked. Both now return a descriptive error on a nil submessage.
  • 12-F3 (P2): Incomplete backport of upstream e65fb36 ("crypto: bound the declared length before reserving it, in gob decode and ModProof.Verify"). Adds verifyMaxModulusBitLen = 65536 to crypto/paillier, checked in ModProof.ModVerify before common.IsUsableUnknownOrderModulus (i.e. before the O(bitLen) primality check and the session-tagged sampler sampleYModN), so the exported API can no longer allocate/hash proportional to an arbitrarily large caller-supplied modulus. Placement and derivation mirror the upstream commit's crypto/modproof/proof.go hunk (256-bit sampler blocks, one-byte-equivalent block tag, boundary at bitLen <= 65536). The shipped keygen wire path already pins moduli to 2048 bits, so this closes the gap only in the exported API surface.
  • 12-F4: RangeProofAlice.Bytes/ValidateBasic lacked the nil/ValidateBasic fail-loud symmetry that sibling ProofBob/ProofBobWC already have. ValidateBasic now short-circuits on a nil receiver, and Bytes() panics with a descriptive "RangeProofAlice.Bytes: invalid receiver" error instead of segfaulting inside a nil *big.Int field.

Regression tests (intrinsic to each fix, deterministic & fast)

  • ecdsa/keygen/messages_test.go: TestKGRound3UnmarshalProofIntsArity (exact-13, short 0..12, extra 14th part, empty part — all rejected without panicking); TestKGRound2UnmarshalFactorProofNil (nil submessage rejected on both decoders, valid submessage round-trips).
  • crypto/paillier/mod_proof_test.go: TestModProofVerifyAcceptsCeilingBoundary (a modulus at exactly 65536 bits passes the ceiling, rejected only at the later oddness check — guards the off-by-one); TestModProofVerifyRejectsOversizedModulus (a 65537-bit odd composite is rejected with the ceiling error and allocates under 256 KiB, measured via runtime.MemStats).
  • crypto/mta/range_proof_test.go: TestRangeProofAliceBytesInvalidReceiver (nil receiver and nil-field receiver both fail loud with the descriptive message instead of a raw nil-pointer panic).

Notes

  • Minimal production changes; no API shims, no unrelated changes. Error styles match the surrounding decoders/validators.
  • CI/build/lint were intentionally not run locally per the delivery contract for this change; the orchestrator verifies.

This PR addresses the four follow-up findings from the review of PR #12
(agent-docs/pr-integration/12.md):

12-F1: KGRound3Message.UnmarshalProofInts now validates exact arity
(paillier.ProofIters = 13 parts) and returns an error instead of
panicking on short/long/empty slices.

12-F2: KGRound2Message1.UnmarshalFactorProof / UnmarshalFactorProofTilde
now guard against nil sub-message pointers and return a descriptive error
instead of dereferencing nil.

12-F3 (P2): Backports upstream e65fb36's verifyMaxModulusBitLen=65536
ceiling to crypto/paillier ModProof/ModVerify. An exported API caller can
no longer supply an arbitrarily large modulus that triggers the O(bitLen)
session sampler. Regression tests pin the boundary at 65536 (passes
ceiling, rejected at oddness) and 65537 (rejected at ceiling with
allocation < 256 KiB).

12-F4: RangeProofAlice.Bytes and ValidateBasic now fail-loud on nil or
partially-nil receivers, consistent with the sibling ProofBob/ProofBobWC
encoders.

All tests are deterministic, fast, and fail before/pass after the fixes.
…ording/allocation

Review follow-ups on the input-hardening-followups PR:

1. crypto/paillier/paillier.go: verifyMaxModulusBitLen's comment claimed
   sampleYModN tags blocks with a single byte and wraps at 257 blocks.
   This fork's sampler actually tags blocks with a 4-byte uint32 index
   (does not wrap until 2^32 blocks), so that rationale was factually
   wrong here. The comment now documents the ceiling honestly as an
   upstream-compatible allocation/operational cap, not a sampler
   correctness bound, and says so explicitly.

2. crypto/paillier/mod_proof.go: extracted the ceiling comparison into an
   unexported exceedsModulusBitLenCeiling(N) predicate used by ModVerify.
   mod_proof_test.go now pins the exact boundary (65536 bit-length passes,
   65537 fails) by calling this predicate directly, and separately
   preserves an end-to-end ModVerify behavior check at the same widths --
   neither depends on ModVerify's error wording or on measuring
   allocation. Dropped the runtime.MemStats allocation assertion and the
   strings.Contains wording assertions along with their imports.

3. crypto/mta/range_proof_test.go: TestRangeProofAliceBytesInvalidReceiver
   no longer pins panic message text. It now asserts ValidateBasic()
   returns false on the invalid receiver directly, then recovers the
   Bytes() panic and checks the recovered value is an error that is NOT a
   runtime.Error -- which fails before the fix (nil-pointer dereference
   panics are runtime.Error) and passes after (the guard panics with a
   plain fmt.Errorf). Removed the inaccurate round-trip claim from the
   doc comment since this test does not exercise a round-trip.

4. ecdsa/keygen/messages_test.go: TestKGRound2UnmarshalFactorProofNil now
   also exercises the symmetric successful FacproofTilde decode once it
   is populated, matching the existing Facproof positive-path coverage.

gofmt run on all touched files; no build/test/lint executed per the
delivery contract.
@piotr-roslaniec
piotr-roslaniec merged commit d4528e5 into dev Sep 29, 2026
4 checks passed
@piotr-roslaniec

Copy link
Copy Markdown
Author

Integration verification completed before merge into dev at d4528e5e33d9bcca3537c328b37a43d36d6bc527.

  • Decoder guards cover exact Paillier proof arity and nil factor-proof submessages, with all production callers migrated to return errors instead of panicking.
  • ModVerify rejects moduli wider than 65536 bits before primality and modulus-sized work. The final documentation correctly treats 65536 as an upstream-compatible resource ceiling; this fork uses a uint32 sampler block index and does not share upstream's one-byte wrap limit.
  • RangeProofAlice.ValidateBasic is nil-safe and Bytes now fails with a diagnostic panic rather than an unguarded nil-pointer panic.
  • Independent review found no P0-P2 defects. Review cleanup removed process-wide allocation and error-wording assertions and added symmetric positive decoder coverage.
  • Local whole-tree compile (go test -run ^$ ./...) passed. Focused regressions passed 20 consecutive runs.
  • GitHub Actions run 36608731347 passed vet/format, keygen units, and the full suite.

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