Skip to content

feat(#163 CC-13 inc-2-live): real gossip BLS quorum co-signer (default-off)#179

Merged
jhfnetboy merged 5 commits into
masterfrom
feat/audit-inc2-live-cosigner
Jul 7, 2026
Merged

feat(#163 CC-13 inc-2-live): real gossip BLS quorum co-signer (default-off)#179
jhfnetboy merged 5 commits into
masterfrom
feat/audit-inc2-live-cosigner

Conversation

@jhfnetboy

Copy link
Copy Markdown
Member

CC-13 inc-2-live — real gossip BLS quorum co-signer

SP completed the on-chain prerequisites (Seeder CC-13): applyBLSAggregator + addValidator×3 + registerBLSPublicKey×3, slots dvt1=1 / dvt2=2 / dvt3=3 on BLSAggregator 0xF51c…8B13 / DVTValidator 0x568b1486…. This replaces the PendingSlotCoSigner stub (which threw) with the real gossip-based BLS quorum co-signer that drives on-chain GToken slashing — the last piece of 目标2.

Still default-off (AUDIT_EXECUTE_SLASH). The stub remains the wiring default when disarmed.

The safety model (this executes real slashes)

A node NEVER blindly signs a hash a peer sends. Each armed node independently:

  1. re-derives the messageHash from the structured CoSignRequest (never trusts the requester's hash),
  2. independently re-runs the credit-over-limit audit at the deterministic epoch block and reconstructs the proofHash, and for the execute step requires proofHash === evidenceHash (a substituted/innocent operator yields a different proofHash → refusal),
  3. resolves its own on-chain slot, then signs — fail-closed on any doubt (not-armed / not-watchlisted / hash-mismatch / unconfirmed / no-slot / RPC error → refuse).

The requester validates every response before counting it: recompute+match hash, cryptographic BLS verify, on-chain slot→key binding (uncompressed EIP-2537 compare + non-zero validator), dedup by slot. Strict threshold (WARNING 2/3, MINOR/MAJOR 3/3) — under-threshold → throw, never submitting a proof that would revert.

signerMask convention — PINNED

Verified against BLSAggregator.sol:772 (for slot 1..MAX: if ((signerMask >> (slot-1)) & 1)): slot s → bit s-1. buildSignerMask([1,2,3]) = 7n. One tested helper.

Files

  • NEW gossip-quorum-cosigner.ts (+ spec, 20 tests) — responder verifyAndSign + requester coSign.
  • NEW gossip-cosign.spec.ts (8 transport tests).
  • slash-consensus.ts — structured CoSignRequest seam, buildSignerMask/recomputeMessageHash, MAX_VALIDATORS=13, QUORUM_COSIGNER token.
  • gossip.{interfaces,service}.tscosign-request/response (point-to-point, ttl 0, requestId correlation, fresh messageId); transport only, zero slash logic.
  • blockchain.service.tsgetValidatorAtSlot / getBlsPublicKeyAtSlot / getSlotForValidator.
  • audit.service.ts — structured call sites + verifyViolationForCoSign responder verifier; audit.module.ts factory provider (armed→real, disarmed→stub); configuration.ts co-sign knobs.

Tests: 291/291 (+28). type-check / build / lint clean.

Confirmable ONLY against the 3 live deployed nodes

  • The produced signerMask + 256B sigG2 accepted by the real BLSAggregator.verifyAndExecute pairing (EIP-2537 precompiles) + DVTValidator.queue/executeWithProof.
  • Real 3-node gossip agreement/latency; each node reconstructing the identical messageHash/proofHash/epoch.
  • getBLSPublicKey live ABI tuple shape; getBlsPublicKeyAtSlot uncompressed equality vs live registered keys.

Refs #163, CC-13.

…e stub)

SP completed the on-chain prereqs (applyBLSAggregator + addValidator×3 +
registerBLSPublicKey×3, slots dvt1=1/dvt2=2/dvt3=3). This replaces the
PendingSlotCoSigner stub with the real gossip-based BLS quorum co-signer that
drives on-chain GToken slashing.

- slash-consensus.ts: widen the seam to a structured CoSignRequest (so peers verify
  from first principles, never trust a raw hash); pure buildSignerMask (slot s → bit
  s-1, PINNED against BLSAggregator.sol:772 — [1,2,3]=7n) + recomputeMessageHash;
  MAX_VALIDATORS=13; QUORUM_COSIGNER DI token. PendingSlotCoSigner still throws.
- gossip-quorum-cosigner.ts (NEW): responder verifyAndSign (fail-closed on not-armed /
  not-watchlisted / hash-mismatch / independent-violation-unconfirmed / execute
  proofHash≠evidenceHash / no-slot) + requester coSign (self-contribute, per-response
  cryptographic BLS verify + on-chain slot→key binding, STRICT threshold → throw on
  under-threshold, never submit a reverting proof). Aggregate via
  aggregateSignaturesOnly→encodeToEIP2537.
- gossip: cosign-request/response message types (point-to-point ttl=0, requestId
  correlation, fresh messageId), registerCoSignHandler + requestCoSignatures collector.
  Transport only — zero slash logic in GossipService.
- blockchain.service.ts: getValidatorAtSlot / getBlsPublicKeyAtSlot / getSlotForValidator
  (on-chain authoritative slot source).
- audit.service.ts: call sites pass CoSignRequest; verifyViolationForCoSign responder
  verifier + shared isCreditOverLimit; arm co-signer at bootstrap.
- audit.module.ts: factory provider (armed→GossipQuorumCoSigner, disarmed→stub).
- configuration.ts: auditCoSignTimeoutMs(15s) / auditSlashThresholds(2/3/3) /
  auditMaxSlots(13) / auditSlotMap.

Still default-off (AUDIT_EXECUTE_SLASH). All inc-2 invariants preserved. 291/291 tests
(+28), type-check/build/lint clean.

LIVE-ONLY (needs dvt1/2/3 deployed): real signerMask/sigG2 accepted by verifyAndExecute
pairing + real 3-node gossip agreement.

Claude-Session: https://claude.ai/code/session_01JrdmiUk4A258FmhDSKn9aj
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

jhfnetboy added 2 commits July 7, 2026 01:28
…Low)

Codex found no Critical (the core 'a lying node cannot force an unjust slash' holds),
but 6 hardening issues blocking live-arming. All fixed:

- HIGH: clamp AUDIT_SLASH_THRESHOLDS UP to a pinned floor (WARNING>=2, MINOR>=3,
  MAJOR>=3) so a misconfig can't let one signature pass as quorum.
- HIGH: static-call preflight before queueSlashWithProof/executeSlashWithProof —
  a staticCall revert throws (→ degrade to file+archive) BEFORE spending gas, so any
  signerMask/sigG2/DST/threshold/slot mismatch is caught free instead of a paid revert.
- MED: gossip requestCoSignatures now counts only VALIDATED responses (validate+dedupKey
  callbacks) toward threshold — a bad peer can't crowd out honest signers (liveness DoS).
- MED: queue co-sign now also requires evidenceHash present AND == re-derived proofHash
  (was execute-only) — no bogus-evidence queue quorum.
- MED: ProposalCreated parse requires log.address==dvtValidator AND operator/level match.
- LOW: widen ProofIdentity (availableCredit, slashLevel, violationBlockHash, registry/
  superPaymaster/dvtValidator/apntsToken) so proofHash is fully content-addressed; add
  getBlockHash; responder re-reads epoch blockHash fail-closed.

Still default-off. All inc-2/inc-2-live invariants preserved. 313/313 tests (+21),
type-check/build/lint clean.

Claude-Session: https://claude.ai/code/session_01JrdmiUk4A258FmhDSKn9aj
…+ strict threshold parse

Codex R2: no Critical/High (all R1 blockers resolved). Two live-arming liveness
residuals (both already fail-closed), fixed:

- MED: bound co-sign response validation work. A compromised peer that knows the
  requestId could flood responses, each hitting async validate (BLS verify + on-chain
  reads) before dedup. Now: EXACT resends (signer|slot|sig) are dropped BEFORE validate,
  and total validations per request are capped (maxValidations, default 64). Keyed on
  the FULL response — never on slot alone — so an early bogus slot claim can NOT poison
  the honest signer's real response for that slot (post-validate slot dedup still decides
  quorum). +2 tests (exact-dedup+cap, no-poisoning).
- LOW: strict AUDIT_SLASH_THRESHOLDS numeric parse — '4oops' was read as 4 by parseInt;
  now ^[0-9]+$ + Number.isSafeInteger, malformed → ignored → safe floor kept. +2 tests.

Still default-off. 317/317 tests (+4). type-check/build/lint clean. Codex verdict:
default-off merge sound; these were the last live-arming liveness gaps.

Claude-Session: https://claude.ai/code/session_01JrdmiUk4A258FmhDSKn9aj

@clestons clestons left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

PK Review — YetAnotherAA-Validator#179 (gossip BLS quorum co-signer)

Verdict: REQUEST CHANGES — 2 confirmed findings + 1 PK-added liveness concern. Safety design is solid (fail-closed at every gate, BLS crypto verified end-to-end, threshold strictly enforced). The issues below are pre-production blockers before AUDIT_EXECUTE_SLASH=true is enabled on live DVT nodes.


[Confirmed] Medium — resolveOwnSlot O(maxSlots) sequential RPC on every call

File: src/modules/audit/gossip-quorum-cosigner.ts:1205

Every coSign AND every verifyAndSign (which is also called inside coSign for self-contribution) runs a serial loop:

for (let slot = 1; slot <= this.maxSlots; slot++) {
  const onchain = await this.blockchain.getBlsPublicKeyAtSlot(this.blsAggregatorAddress, slot);
  ...
}

With AUDIT_MAX_SLOTS=13 (default) and RPC latency of 200–500ms per call, this scan alone consumes 2–6s of the 15s co-sign timeout window — before a single peer response has been requested. Worse, it runs twice in one coSign call (once as self-contribution inside verifyAndSign, once at the start of coSign).

Fix: Cache own slot after first successful resolution. The slot only changes if the operator re-registers a BLS key (a rare, deliberate action), so a process-lifetime cache with a stale flag is sufficient:

private ownSlotCache: number | null | undefined = undefined;

private async resolveOwnSlot(compressedPubKey: string): Promise<number | null> {
  if (this.ownSlotCache !== undefined) return this.ownSlotCache;
  // ... existing scan logic ...
  this.ownSlotCache = result;
  return result;
}

[PK-added] Medium — Validation budget exhaustion by compromised peer

File: src/modules/gossip/gossip.service.ts:690 (added in this PR)

The handleCoSignResponse path deduplicates by signerNodeId|slot|signatureCompact and caps total validation work at maxValidations=64. A compromised connected peer who knows the requestId (gossip-broadcast) can inject 64 responses with distinct invalid signatureCompact values — each passes seenExact (different sig), each increments validationsStarted. After 64 entries, line 690 silently drops ALL subsequent responses, including legitimate ones from honest validators.

Impact: Liveness — the timeout expires, coSign throws "only N valid unique-slot signatures", the slash is deferred. Safety is preserved (no wrong slash). With N=3 DVT nodes, a single compromised peer can reliably block all slashing operations.

Fix: Rate-limit per signerNodeId instead of (or in addition to) global budget, or reduce maxValidations to maxSlots * 2 (26 for N=13) so honest nodes (≤13 slots × 1–2 retries) comfortably fit while a budget-exhaustion attack requires a much tighter injection window.


[Confirmed] Low — Double RPC per peer response in validateResponse

File: src/modules/audit/gossip-quorum-cosigner.ts:1181–1192

validateResponse calls both getBlsPublicKeyAtSlot AND getValidatorAtSlot for each peer response. A non-null BLS public key already implies a live validator at that slot — getValidatorAtSlot is redundant and doubles RPC cost on the already-tight validation path.

Fix: Remove the getValidatorAtSlot call; rely solely on getBlsPublicKeyAtSlot returning non-null as proof of slot occupancy.


Rejected

  • F4 (parseSlashThresholds no floor-clamping): Fixed in commit 2 via SLASH_THRESHOLD_FLOOR. Final state correctly prevents MINOR:1 from passing. No action needed.
  • F2 (arm() double registration): registerCoSignHandler is single-field assignment (this.coSignHandler = fn) — repeated arm() calls overwrite, not accumulate. Finding was incorrect.

PK Summary

Round Challenger Result
1 Codex F1 CONFIRM · F2 CHALLENGE (correct) · F3 CONFIRM · F4 CONFIRM · MISSED: budget exhaustion

Two-round cap not needed — no critical misses requiring second round.

Self-assessment: Security design is strong — the structured CoSignRequest, independent violation re-confirmation, slot↔key on-chain binding, and STRICT threshold enforcement are all correct. The three findings are performance/liveness concerns on the hot path, not safety holes. Fix F1 (cache) and address the budget exhaustion before enabling on live DVT nodes.

… + per-conn rate limit)

Copilot REQUEST CHANGES on #179 — 3 liveness/perf issues (no safety gate touched):

- own-slot cache: resolveOwnSlot memoizes the node's stable on-chain slot (was up to 13
  serial getValidatorAtSlot RPCs per co-sign); null (unregistered) is NOT cached so a later
  registration is picked up; refreshOwnSlot() forces re-scan. getSlotForValidator now scans
  slots in parallel (Promise.all), lowest match.
- validateResponse: drop the redundant getValidatorAtSlot (getBlsPublicKeyAtSlot non-null
  already implies non-zero + active validator); add a per-coSign keyCache shared by the
  collector validate callback + the post-collection loop so same-slot reads hit RPC once.
- per-connection rate limit: the global maxValidations=64 could be exhausted by ONE
  compromised peer flooding distinct bogus responses (signerNodeId is forgeable). Now rate-
  limit by the CONNECTION (ws, the un-forgeable identity): each connection gets at most
  perConnCap (default 4) validations, so a flooder is bounded and honest signers on other
  connections still reach quorum. Exact-dedup + global cap retained as backstops.

Still default-off. Safety gates unchanged (Codex-confirmed sound). 331/331 tests,
type-check/build/lint clean.

Claude-Session: https://claude.ai/code/session_01JrdmiUk4A258FmhDSKn9aj

@clestons clestons left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

PK Review (incremental) — YetAnotherAA-Validator#179 v2

Verdict: REQUEST CHANGES — Previous 3 findings fully addressed. 3 new issues found in the incremental commits, one MEDIUM (coordinated-upgrade constraint) requiring documentation.


Previous findings — all fixed ✓

Finding Status
F1 — resolveOwnSlot O(maxSlots) serial RPC, no cache Fixed: ownSlot/ownSlotResolved permanent cache; null NOT cached; refreshOwnSlot() for forced re-scan
F3 — validateResponse double RPC (getValidatorAtSlot redundant) Fixed: getValidatorAtSlot removed; non-null getBlsPublicKeyAtSlot already implies active validator; per-call keyCache added
PK-added — maxValidations=64 exhaustible by one peer (distinct-sig flood, liveness) Fixed: per-connection cap perConnCap=4 (WebSocket identity, un-forgeable); flooder on one connection capped before honest peers on other connections are crowded out

Bonus security hardening verified:

  • HIGH 1: slash threshold floor clamp (SLASH_THRESHOLD_FLOOR = 2/3/3) — MINOR:1 misconfiguration can no longer bypass 3-of-3 quorum
  • HIGH 2: static-call preflight on queueSlashWithProof / executeWithProof — deterministic reverts surface before gas is spent
  • MEDIUM 1: collector now counts only validated + deduped-by-slot responses toward threshold (bogus responses cannot crowd out honest signers or force premature resolution)
  • MEDIUM 2: evidenceHash binding extended to queue step (was execute-only) — malicious requester cannot form a real queue quorum while attaching bogus evidence
  • MEDIUM 3: ProposalCreated log gated to the calling DVTValidator address + matching operator/level — wrong-contract log cannot steal another proposal's id

[New] Medium — Coordinated upgrade required (ProofIdentity schema break)

Files: src/modules/audit/proof-archive.ts, audit.service.ts, gossip-quorum-cosigner.ts

ProofIdentity expanded from ~4 fields to 9 (availableCredit, violationBlockHash, slashLevel, registry, superPaymaster, dvtValidator, apntsToken added). The CoSignRequest type carries no schema version field.

Consequence: a node running old code re-derives the old-schema proofHash; a proposer on new code sends the new-schema evidenceHash → permanent mismatch → co-sign refused. All three DVT nodes must upgrade simultaneously, or co-signing is broken during the window when nodes run mixed versions.

Suggested fix: Document in DEPLOYMENT.md or the upgrade runbook:

⚠️ This commit changes ProofIdentity's hash input schema. Upgrade all DVT nodes simultaneously; do NOT rolling-upgrade. A split-version cluster will refuse all co-sign requests until all nodes are on the new schema.

Optionally add a schemaVersion: 2 field to CoSignRequest so responders can detect and reject stale requests with an explicit error rather than a silent mismatch.


[New] Low — getBlockHash on non-archive node → permanent liveness failure

File: src/modules/blockchain/blockchain.service.ts:971–980, audit.service.ts:663

verifyEvidence now calls getBlockHash(blockTag) and fails closed (return NO) if the result is null. getBlockHash returns null on any provider exception OR if getBlock(number) returns null (block pruned).

Non-archive RPC nodes (default geth/reth retain ~128 blocks). If a DVT node's provider is non-archive, any violation whose epoch block is older than ~128 blocks will have its block hash unresolvable → verifyEvidence returns NO → legitimate slash cannot co-sign.

Suggested fix: Require archive-capable RPC for DVT nodes and document this in the deployment guide. Optionally emit a warning log on null to distinguish "block pruned" from "transient RPC failure":

if (!violationBlockHash) {
  this.logger.warn(`getBlockHash(${blockTag}) returned null — RPC may not be archive-capable`);
  return NO;
}

[New] Low — getSlotForValidator no early exit (always maxSlots RPCs)

File: src/modules/blockchain/blockchain.service.ts:994–1002

Previous serial for-loop stopped at the first matching slot (O(slot_index) expected). The new Promise.all scan always issues maxSlots (13) concurrent RPC calls — even when the match is at slot 1. The result is correct; this is a mild efficiency regression (13 parallel reads vs average ~2–3 serial reads when most operators are near the front of the list).

Acceptable trade-off for the wall-clock speedup, but worth noting: with 3 registered nodes each making this call on co-sign events, it's 39 RPCs vs ~9 before.


PK Summary

Finding PK Verdict
F-new-1 — ProofIdentity schema break, coordinated upgrade required [CONFIRM] Codex
F-new-2 — getBlockHash prune → permanent liveness failure [CONFIRM] Codex
F-new-3 — getSlotForValidator full-scan (no early exit) [CONFIRM] Codex

Self-assessment: Excellent security hardening round. All 3 original findings and 5 additional security issues (HIGH 1/2, MEDIUM 1/2/3) addressed with comprehensive tests. The new MEDIUM is a deployment coordination concern rather than a logic bug — the code is correct, but the upgrade runbook needs an explicit "all-at-once upgrade" warning. F-new-2 and F-new-3 are acceptable tradeoffs for the correctness and liveness improvements they enable.

@clestons clestons left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

PK Review (full re-review) — YetAnotherAA-Validator#179

Verdict: REQUEST CHANGES — Core gossip BLS quorum co-sign architecture is sound and fail-closed. 3 deployment/operational issues remain open; the two HIGH and three MEDIUM security items were correctly addressed across the 4 commits.


Security model — verified correct

The core invariant — "a lying node cannot force an unjust slash" — holds through the PR:

  • Responder (verifyAndSign): 9 fail-closed checks: armed, watchlisted, messageHash recompute (never trusts requester's copy), independent violation re-confirmation at epoch block, evidenceHash == re-derived proofHash for BOTH queue and execute steps, own on-chain slot resolved, BLS sign locally-recomputed hash.
  • Requester (coSign): self-contributes through the same responder gate, crypto+on-chain-binds every peer response (validateResponse: sig verify + getBlsPublicKeyAtSlot slot binding), strict per-severity threshold with post-collection dedup by slot, never aggregates under-threshold.
  • Static-call preflight on queueSlashWithProof / executeSlashWithProof catches any signerMask/sigG2/DST/threshold mismatch before spending gas.
  • ProposalCreated log bound to the calling DVTValidator address + matching operator + level (prevents wrong-proposal-id binding).
  • Slash threshold floor (WARNING≥2, MINOR≥3, MAJOR≥3) — misconfiguration can never weaken below the 3-of-3 pinned policy.
  • Collector validation (MEDIUM 1): only validated, unique-slot peer responses count toward threshold; per-connection cap (perConnCap=4) plus global maxValidations prevent one flooding peer from exhausting the budget before honest signers arrive.
  • resolveOwnSlot cache (finding-1): permanent positive cache, null not cached, refreshOwnSlot() available.
  • validateResponse keyCache (finding-2): one RPC per unique slot per coSign call, shared between collector and post-collection loop.

[Open] Medium — Coordinated upgrade required: ProofIdentity schema break

Files: src/modules/audit/proof-archive.ts:154–178, audit.service.ts, gossip-quorum-cosigner.ts:150

ProofIdentity expanded from 4 to 9 fields (availableCredit, violationBlockHash, slashLevel, registry, superPaymaster, dvtValidator, apntsToken). CoSignRequest / CoSignRequestPayload carry no schema version field.

A responder running old code re-derives a 4-field proofHash; a proposer running new code sends a 9-field evidenceHash → permanent mismatch → co-sign refused for every violation until ALL nodes are upgraded.

Required fix: Document an explicit "simultaneous upgrade" constraint. Optionally add schemaVersion: 2 to CoSignRequest so responders emit a distinguishable error ("schema mismatch") rather than a silent evidenceHash failure:

// CoSignRequest
schemaVersion?: number; // must equal current PROOF_IDENTITY_SCHEMA_VERSION

[Open] Low — getBlockHash on non-archive RPC → permanent liveness failure

Files: src/modules/audit/audit.service.ts:663–668, blockchain.service.ts:493

verifyViolationForCoSign calls getBlockHash(blockTag) and returns NO (fail-closed) on null. getBlockHash returns null on any provider error or pruned block. Non-archive RPC nodes retain only ~128 recent blocks by default (geth/reth/erigon in pruned mode).

If any DVT node's RPC is non-archive and the violation epoch block has been pruned, getBlockHash returns null → that responder permanently refuses the violation — real slashes cannot co-sign even if the violation is genuine.

Suggested fix: Document the archive-node requirement in deployment/DEPLOYMENT.md:

DVT nodes MUST connect to an archive-capable RPC (one that retains full block history). Non-archive providers that prune old blocks will cause verifyViolationForCoSign to fail-closed for old violations.

Optionally log a diagnostic warning:

if (!violationBlockHash) {
  this.logger.warn(`getBlockHash(${blockTag}) null — RPC may not be archive-capable`);
  return NO;
}

[Open] Low — getSlotForValidator always scans all 13 slots (no early exit)

File: src/modules/blockchain/blockchain.service.ts:711

Changed from serial for-loop (exits at first match, O(avg slot_index)) to Promise.all(1..maxSlots) — always issues 13 concurrent RPC calls even when the match is at slot 1. Functionally correct, but with 3 DVT nodes calling this on each co-sign event, it's 39 RPCs per round-trip instead of ~9.

Acceptable wall-clock trade-off for most deployments; noting it for awareness.


PK Summary

Finding PK Verdict
F1 — ProofIdentity schema break, coordinated upgrade required [CONFIRM] Codex
F2 — getBlockHash non-archive prune → permanent liveness failure [CONFIRM] Codex
F3 — getSlotForValidator full scan, no early exit [CONFIRM] Codex

Self-assessment: Comprehensive security-hardening PR. The slash safety invariant is correctly implemented and adversarially tested with real BLS keys. The 3 remaining issues are operational/deployment concerns; the most actionable is documenting the simultaneous-upgrade requirement and archive-node dependency before live-arming in production.

…rsion + non-archive liveness + O(1) slot)

Codex re-review re-confirmed the security model correct (9 fail-closed checks, static-call
preflight, collector counts only validated) and 3 open findings. All resolved:

- FINDING 3 (perf): own-slot lookup is now O(1) — getRegisteredSlot reads
  BLSAggregator.getBLSPublicKey(operator).slot directly (one RPC), replacing the 1..13
  getValidatorAtSlot scan. Cached; requester still binds every returned slot→key on-chain.
- FINDING 2 (non-archive liveness): removed violationBlockHash from the proofHash /
  ProofIdentity and the responder's getBlockHash(epoch) re-read — reorg-safety already comes
  from the finality-pinned violationBlock, so a non-archive node no longer permanently
  fail-closes on an old block header. violationBlockHash stays in the archived evidence for
  forensics. getBlockHash removed (no remaining caller).
- FINDING 1 (rolling-upgrade break): PROOF_SCHEMA_VERSION=2 bound into proofHash + carried in
  CoSignRequest; verifyAndSign REFUSES with a clear log on version mismatch (explicit +
  diagnosable, not a silent hash mismatch → lost quorum). docs/INTERFACES.md §3: deploy the
  DVT fleet atomically, never a rolling partial upgrade.

On-chain slash preimages (buildQueue/buildExecuteMessageHash/encodeProof) BYTE-IDENTICAL —
ProofIdentity/proofHash is a separate off-chain evidence hash. Still default-off. 345/345
tests, type-check/build/lint clean.

Claude-Session: https://claude.ai/code/session_01JrdmiUk4A258FmhDSKn9aj

@clestons clestons left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

PK Review (incremental v4) — YetAnotherAA-Validator#179

Verdict: APPROVE — All 3 previously open findings are fully addressed in this increment. Security model remains fail-closed and correct.


Previously open findings — all fixed ✓

Finding Status
F-new-1 — ProofIdentity schema break, coordinated upgrade required (MEDIUM) Fixed: PROOF_SCHEMA_VERSION = 2 added; proofSchemaVersion field in ProofIdentity + CoSignRequest; responder explicitly refuses version-mismatched requests with WARNING log and return null; docs/INTERFACES.md documents atomic upgrade requirement
F-new-2 — getBlockHash on non-archive RPC → permanent liveness failure (LOW) Fixed: violationBlockHash removed from ProofIdentity entirely; getBlockHash() removed from BlockchainService; getBlockHash call removed from verifyViolationForCoSign — reorg-safety now comes only from the finalized epoch block, no historical block header read required; non-archive nodes fully supported
F-new-3 — resolveOwnSlot O(N) scan (LOW) Fixed: resolveOwnSlot() now calls getRegisteredSlot(blsAggregatorAddress, operatorEoa) — one getBLSPublicKey(operatorEoa) RPC call returning (publicKey, slot, isActive) directly; no 1..maxSlots scan; O(1) own-slot resolution

New increment quality notes

  • proofSchemaVersion binding is correct: version field is first in ProofIdentity, bound into every computeProofHash call via stableStringify; version changes produce a different hash, making schema drift immediately detectable.
  • violationBlockHash removal is safe: forensic use retained in Evidence interface; reorg-safety argument is sound (finalized epoch block by definition cannot reorg); no test or contract needs a block header hash for proof identity.
  • getRegisteredSlot security model holds: requester's validateResponse still independently binds each responder's returned slot to its on-chain BLS key (getBlsPublicKeyAtSlot), so a misconfigured slot returned by resolveOwnSlot is rejected at validation — safety does not depend on this lookup matching the signing key.
  • Test coverage: new proof-archive.spec.ts tests PROOF_SCHEMA_VERSION binding; updated gossip-quorum-cosigner.spec.ts covers version-mismatch refusal and version-match signing; new getRegisteredSlot describe block in blockchain.service.spec.ts covers O(1), null-for-inactive, null-for-slot-0, null-on-revert, null-for-malformed-address.

PK Summary

Finding PK Verdict
F-new-1 — ProofIdentity schema break (MEDIUM) [CONFIRM fixed] Codex
F-new-2 — getBlockHash non-archive liveness (LOW) [CONFIRM fixed] Codex
F-new-3 — resolveOwnSlot O(N) scan (LOW) [CONFIRM fixed] Codex

Self-assessment: This increment cleanly closes all 3 open findings with targeted, well-tested changes. The proof schema versioning mechanism is production-ready and the deployment documentation is updated. APPROVE.

@jhfnetboy jhfnetboy merged commit 9cad236 into master Jul 7, 2026
11 checks passed
@jhfnetboy jhfnetboy deleted the feat/audit-inc2-live-cosigner branch July 7, 2026 07:02
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 7, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants