Skip to content

perf: adopt ring-go perf/hash-cache off-chain signing optimization - #49

Merged
oten91 merged 5 commits into
mainfrom
perf/ring-go-hash-cache
Jul 2, 2026
Merged

oten91 merged 5 commits into
mainfrom
perf/ring-go-hash-cache

Conversation

@oten91

@oten91 oten91 commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

What

Adopts ring-go's perf/hash-cache off-chain signing optimization as an opt-in, named path alongside the existing deterministic Sign — mirroring ring-go's own Sign vs SignWithContext split.

Supersedes the perf/signer-context-cache approach (which overloaded Sign with off-chain-only behavior).

Changes

  • signer.go
    • Sign — unchanged behavior: deterministic, consensus-safe (Ring.Sign). Existing callers keep identical semantics.
    • SignOffChain / SignOffChainWithRing — new opt-in off-chain fast path (Ring.SignWithContext). Caches one secret-free SignerContext per ring (sync.Map), reused across signs; SkipSelfCheck enabled (safe — off-chain, always signed with the scalar the context was built from). Off-chain only.
    • ClearSignerContextCache for session rollover; private-key scalar decoded once at construction.
  • signer_test.go — round-trip verify for both paths, tampered-message reject, cache reuse + clear, asserts Sign never touches the cache.
  • deps — ring-go → perf/hash-cache (0830a5c); go-dleq now direct; go-ethereum → v1.17.4 (CVEs, cgo-only); x/crypto/net bumps.
  • docs — docs/migration-ring-go-hash-cache.md for PATH.

Downstream (PATH etc.)

  • On signer-context-cache, Sign was the cached path. Here Sign is deterministic again — swap Sign→SignOffChain, SignWithRing→SignOffChainWithRing to keep the speedup. Leaving Sign as-is stays correct, just slower. See the migration doc.
  • Requires the Go 1.26 toolchain (propagates from the SDK).

Verification

Build ✅ · vet ✅ · golangci-lint 0 issues ✅ · tests pass (both paths verify) ✅

⚠️ Draft — do not merge yet

ring-go is pinned to a branch commit, not a release tag. Keep off main until go-ring promotes hash-cache to master/tags it, and until the gateway reports off-chain before/after numbers.

🤖 Generated with Claude Code

oten91 and others added 4 commits July 2, 2026 14:20
Adds ring-go's hash-cache off-chain fast path to the SDK as an opt-in,
named alongside the existing deterministic default (mirrors ring-go's own
Sign vs SignWithContext split).

Dependency:
- ring-go -> perf/hash-cache (0830a5c); go-dleq now a direct dep
  (types.Scalar used for the cached signer scalar).
- pulls go-ethereum v1.16.9 -> v1.17.4 (CVE fixes; only active with the
  ethereum_secp256k1 cgo backend) and x/crypto/net/etc.

signer.go:
- Sign: unchanged behavior — deterministic, consensus-safe (Ring.Sign).
  Remains the default; existing callers keep identical semantics.
- SignOffChain / SignOffChainWithRing: NEW opt-in off-chain fast path
  (Ring.SignWithContext). Caches one secret-free SignerContext per ring
  (sync.Map) and reuses it across signs; SkipSelfCheck enabled (safe:
  off-chain, always signed with the scalar the context was built from).
  OFF-CHAIN ONLY — never use on consensus-critical / gas-metered paths.
- ClearSignerContextCache for session rollover; private-key scalar decoded
  once at construction (removes the per-call DecodeToScalar TODO).

signer_test.go:
- Sign: round-trip verify, tampered-message reject, asserts cache untouched.
- SignOffChain: round-trip verify + cache populated.
- Off-chain cache reuse across signs + ClearSignerContextCache.

Both paths produce signatures that verify identically; Verify is unchanged.
Build, vet, golangci-lint (0 issues), and tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Maps signer-context-cache call sites to the new Sign (deterministic) vs
SignOffChain/SignOffChainWithRing (opt-in off-chain) split, plus the
Go 1.26 toolchain requirement.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
golangci-lint v2.6.0 is built with go1.25.3 and refuses to lint a module
targeting go 1.26.4 ("Go language version used to build golangci-lint is
lower than the targeted Go version"). v2.11.3 is built with go1.26.1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lover

Per PATH canary feedback: signerContextCache is keyed by ring pointer and
grows one entry per session with no automatic eviction, so consumers must
call ClearSignerContextCache() on session rollover or memory leaks slowly.

Adds a prominent warning to the migration guide (TL;DR + step 3) and
strengthens the ClearSignerContextCache doc comment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@oten91

oten91 commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

✅ Canary validation (PATH, draft PR #517)

PATH adopted this branch and validated on canary (beta-4, node-matched, 82 min, rc=0, live HTTP + WebSocket + hedge traffic):

  • Adoption: 1-line swap SignWithRing → SignOffChainWithRing; SDK-side SkipSelfCheck worked transparently. Matched PATH's existing per-session ring caching.
  • Micro-benchmark: hash-cache vs precompute — −17.2% allocs / −16.2% bytes at ring 32, scaling to −12% at ring 2. In the predicted ~15–18% window.
  • Canary: PATH is signing-CPU-bound (~54% of pod CPU in SignWithContext → signInternal; verification only ~4.5%). Signing CPU materially down — secp256k1 FieldVal.SquareVal flat 31.9% → 23.6%; signing CPU share dropped and deepened as the context cache warmed (precompute amortization confirmed). No regressions, signatures verify at the relay miner, rc=0 throughout.
  • Backend: pure-Go decred secp256k1 (no CGO) as expected — win is fewer field ops.

Follow-up applied

  • Documented the unbounded signerContextCache: consumers MUST call ClearSignerContextCache() on session rollover or it leaks slowly (437b446). Decision: keep eviction consumer-managed (doc-only), not an internal LRU/TTL — PATH already wires rollover eviction.

Still gated on

  • ⛔ ring-go must cut a tag for hash-cache. Then: repin SDK branch-commit → tag, un-draft, merge, tag SDK release, PATH re-pins. This PR stays draft until then.

go-ring cut the hash-cache release tag. Move from the branch-commit
pseudo-version to v0.2.0. API unchanged (SignWithContext takes the private
key per call). Build, vet, tests, and golangci-lint all pass.

This removes the "pinned to a branch commit" merge blocker for PR #49.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@oten91
oten91 marked this pull request as ready for review July 2, 2026 16:00
@oten91
oten91 merged commit c2af500 into main Jul 2, 2026
6 checks passed
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