-
-
Save denniswon/28aa29640e8e9eb6677a6e175a013834 to your computer and use it in GitHub Desktop.
| Here's the full picture: | |
| --- | |
| Newton Privacy Layer — Known Limitations & Next Steps | |
| ★ Insight ───────────────────────────────────── | |
| The limitations fall into three buckets: (1) security gaps where the system doesn't enforce what it should, (2) trust assumptions | |
| where centralization remains, and (3) missing infrastructure that blocks future phases. Each has a clear resolution path — none are | |
| architectural dead ends. | |
| ───────────────────────────────────────────────── | |
| Known Limitations | |
| 1. Challenger Cannot Verify Privacy Tasks (NEWT-664) — SECURITY GAP | |
| Problem: evaluate_and_prove() in crates/challenger/src/lib.rs:171 replays task responses to detect misbehavior, but it has no access | |
| to decrypted privacy data. The Rego evaluation runs without data.identity.*, data.privacy.*, or data.ephemeral.* — so the challenger's | |
| result will differ from the operator's result even when the operator was honest. | |
| Impact: Operators can return arbitrary results for privacy-enabled tasks without risk of slashing. This is the single largest security | |
| gap in the shipped system. | |
| Solution (two-stage): | |
| - Phase 1.5 (short-term): Detect privacy-enabled tasks and skip challenge evaluation + emit challenger_privacy_tasks_skipped metric at | |
| WARN level. Prevents false-positive slashing. | |
| - Phase 2 (long-term): Challenger holds a FROST key share from the DKG ceremony, requests partial decryptions from operators, combines | |
| t-of-n shares, and replays with full privacy context. | |
| Linear: NEWT-664, High priority, Backlog. | |
| --- | |
| 2. Gateway Reconstructs Plaintext (Trust Assumption) | |
| Problem: Even with threshold DKG, the gateway is the entity that combines the partial DH outputs via Lagrange interpolation. It sees | |
| the reconstructed shared secret and decrypts the HPKE ciphertext. This is true for both centralized and threshold modes — the | |
| difference is whether the gateway holds the full key (centralized) or reconstructs it per-task from operator contributions | |
| (threshold). | |
| Impact: A compromised gateway can read all ephemeral privacy data. Identity and confidential data are safer — operators decrypt those | |
| locally. | |
| Solution: Phase 3 MPC — operators evaluate the Rego policy directly on encrypted data without any party seeing plaintext. This | |
| requires: | |
| - NEWT-173 (privacy-preserving policy evaluation with MPC/ZK) | |
| - NEWT-168 (MPC framework — canceled, needs rescoping) | |
| - Possibly FHE or garbled circuits depending on benchmarking (NEWT-631) | |
| Timeline: Phase 3, no concrete date. | |
| --- | |
| 3. No Epoch-Based Key Rotation (Forward Secrecy Gap) | |
| Problem: The FROST DKG ceremony produces a single set of key shares that remain valid indefinitely. If an attacker compromises t | |
| shares across any time window, they can decrypt all data ever encrypted to that key. | |
| Impact: No forward secrecy. A single key compromise has unlimited blast radius. | |
| Solution: PSS (Proactive Secret Sharing) with epoch rotation: | |
| - NEWT-628 (canonical spec, due 2026-05-30): epoch-based rotation, MSK destruction, grace periods | |
| - NEWT-639: EpochRegistry Solidity contract for on-chain epoch lifecycle | |
| - Old shares zeroed via zeroize crate after 2-epoch grace period | |
| - MPK stays constant across epochs (no client re-encryption needed) | |
| Timeline: Phase 2C, target Q2 2026. | |
| --- | |
| 4. No Privacy-Specific Slashing | |
| Problem: Operators who leak plaintext, submit invalid partial decryptions, or miss DKG ceremony rounds face no economic penalty beyond | |
| the general task misbehavior slashing. | |
| Impact: No economic deterrent for privacy-specific misbehavior. | |
| Solution: | |
| - NEWT-629 (slashing conditions, blocked by NEWT-628 epoch context) | |
| - NEWT-640 (PrivacySlasher contract) | |
| - NEWT-641 (DleqVerifier Solidity library for on-chain DLEQ proof verification) | |
| - Slashing conditions: missed partial decryption, invalid DLEQ proof, plaintext leakage attestation | |
| Timeline: Phase 2D, after epoch rotation ships. | |
| --- | |
| 5. No On-Chain Auditability for Threshold Operations | |
| Problem: Threshold decryption happens entirely off-chain. There's no on-chain record of which operators participated, which DLEQ | |
| proofs were submitted, or which epoch's key was used. | |
| Impact: No verifiable audit trail for privacy operations. Disputes rely on off-chain logs. | |
| Solution: NEWT-630 (on-chain aggregation commitments) — post a commitment hash of the threshold operation metadata on-chain after each | |
| decryption. | |
| Timeline: Phase 2C, alongside epoch rotation. | |
| --- | |
| 6. Plaintext in Gateway Memory Not Wrapped with secrecy Crate | |
| Problem: Decrypted ephemeral privacy data exists as plain Vec<u8> / String in gateway memory during the task lifecycle. If the process | |
| dumps core or a debugging tool attaches, plaintext is readable. | |
| Impact: Low — requires host-level access to exploit. But defense-in-depth says wrap it. | |
| Solution: Wrap with secrecy::Secret<Vec<u8>> and zeroize-on-drop. The keystore already uses zeroize::Zeroizing (confirmed in | |
| crates/core/src/dkg/keystore.rs:28). Extend this pattern to ephemeral decrypted data in inline_privacy.rs and sync.rs. | |
| Timeline: Quick win, can be done anytime. | |
| --- | |
| 7. Private Data Storage Still on PostgreSQL | |
| Problem: Operators currently access encrypted data blobs from the gateway's PostgreSQL database. External operators (not co-located | |
| with the gateway) need direct DB access — a security and operational risk. | |
| Impact: Limits operator decentralization. External operators can't participate in identity/confidential data flows without DB | |
| credentials. | |
| Solution: Private Data Storage Layer (decided 2026-03-19 team meeting): | |
| - Phase 1 (done): Postgres stopgap with operator DB access | |
| - Phase 2: HPKE migration (done) | |
| - Phase 3: redb (pure Rust) + Jellyfish Merkle Tree, NATS JetStream sync | |
| - Linear: NEWT-799 through NEWT-813 | |
| Timeline: Separate project track, Q3 2026. | |
| --- | |
| Prioritized Next Steps | |
| ┌──────────┬─────────────────────────────────────────────┬──────────────┬──────────┬────────────────────────────────────────────┐ | |
| │ Priority │ What │ Linear │ Effort │ Why First │ | |
| ├──────────┼─────────────────────────────────────────────┼──────────────┼──────────┼────────────────────────────────────────────┤ | |
| │ 1 │ Challenger privacy skip (NEWT-664 Phase │ NEWT-664 │ 1-2 days │ Security gap — false-positive slashing │ | |
| │ │ 1.5) │ │ │ risk │ | |
| ├──────────┼─────────────────────────────────────────────┼──────────────┼──────────┼────────────────────────────────────────────┤ | |
| │ 2 │ secrecy crate for ephemeral plaintext │ (no ticket) │ 1 day │ Quick defense-in-depth win │ | |
| ├──────────┼─────────────────────────────────────────────┼──────────────┼──────────┼────────────────────────────────────────────┤ | |
| │ 3 │ PSS epoch rotation (NEWT-628) │ NEWT-628 │ 3-4 │ Forward secrecy — longest-pole Phase 2 │ | |
| │ │ │ │ weeks │ item │ | |
| ├──────────┼─────────────────────────────────────────────┼──────────────┼──────────┼────────────────────────────────────────────┤ | |
| │ 4 │ EpochRegistry contract (NEWT-639) │ NEWT-639 │ 1-2 │ On-chain epoch lifecycle, blocks slashing │ | |
| │ │ │ │ weeks │ │ | |
| ├──────────┼─────────────────────────────────────────────┼──────────────┼──────────┼────────────────────────────────────────────┤ | |
| │ 5 │ On-chain aggregation commitments (NEWT-630) │ NEWT-630 │ 1 week │ Auditability for threshold ops │ | |
| ├──────────┼─────────────────────────────────────────────┼──────────────┼──────────┼────────────────────────────────────────────┤ | |
| │ 6 │ PrivacySlasher + DleqVerifier │ NEWT-640, │ 2 weeks │ Economic security for privacy misbehavior │ | |
| │ │ (NEWT-640/641) │ 641 │ │ │ | |
| ├──────────┼─────────────────────────────────────────────┼──────────────┼──────────┼────────────────────────────────────────────┤ | |
| │ 7 │ Privacy slashing conditions (NEWT-629) │ NEWT-629 │ 1 week │ Ties slasher to specific violations │ | |
| └──────────┴─────────────────────────────────────────────┴──────────────┴──────────┴────────────────────────────────────────────┘ | |
| Items 1-2 are quick wins. Items 3-7 form the Phase 2C/2D roadmap and should be executed in order (628 → 639 → 630 → 640/641 → 629). | |
| --- | |
| ★ Insight ───────────────────────────────────── | |
| The system is in a solid position for a pre-mainnet AVS — all three privacy data paths work end-to-end, threshold DKG is operational, | |
| and KMS is fully removed. The remaining gaps are about hardening (forward secrecy, slashing, auditability) rather than fundamental | |
| capability. NEWT-664 is the only gap that affects correctness of the current system — everything else is about reducing trust | |
| assumptions or adding economic security. The Phase 2C/2D work is pure Solidity + coordination protocol — the Rust-side crypto is done. | |
| ───────────────────────────────────────────────── | |
| Want me to create a Linear ticket for the secrecy crate wrapping, or start working on NEWT-664 (challenger privacy skip)? |
analyze the importance and implication of phase 2D. What is our current state in terms of newton privacy layer, and what does phase 2D
bring and why important?
⏺ ★ Insight ─────────────────────────────────────
This is a critical architectural question. Phase 2D is the economic enforcement layer — without it, the privacy system relies entirely on
operator honesty. Let me analyze what we have, what's missing, and why it matters.
─────────────────────────────────────────────────
Current State: Privacy Layer without Economic Enforcement
What we have (Phases 1→2C, all merged):
- HPKE encryption (X25519/ChaCha20Poly1305) for all three privacy paths
- Threshold DKG (FROST on Ristretto255) — operators hold key shares, MSK never assembled
- PSS epoch rotation — shares refresh every 24h, MPK constant, forward secrecy
- Gateway-never-decrypts — operators always decrypt locally
- Multi-domain resolution — identity, confidential, ephemeral all supported simultaneously
- EpochRegistry on-chain — epoch commitments, MPK immutability, grace periods
- Challenger support for privacy task replay
What's missing (Phase 2D):
- No economic penalty for operators who misbehave during threshold decryption
- No on-chain verification of partial decryption correctness
- No slashing for operators who refuse to participate
What Phase 2D Brings
NEWT-629: Slashing Conditions (spec)
Defines three provable violation types:
┌─────────────────────┬──────────────────────────────────────────────┬──────────────────────────────────────────────────┬───────────┐
│ Violation │ Detection │ Proof Type │ Severity │
├─────────────────────┼──────────────────────────────────────────────┼──────────────────────────────────────────────────┼───────────┤
│ Wrong-keyshare │ Operator submits an invalid partial DH │ DLEQ proof failure (deterministic, │ 10% slash │
│ │ output │ cryptographic) │ │
├─────────────────────┼──────────────────────────────────────────────┼──────────────────────────────────────────────────┼───────────┤
│ No-keyshare │ Operator absent during threshold decryption │ Quorum attestation (t-of-n operators attest to │ 1-2% │
│ │ window │ absence) │ slash │
├─────────────────────┼──────────────────────────────────────────────┼──────────────────────────────────────────────────┼───────────┤
│ Premature │ Operator participates in unauthorized task │ On-chain authorization check against │ 10% slash │
│ decryption │ │ EpochRegistry │ │
└─────────────────────┴──────────────────────────────────────────────┴──────────────────────────────────────────────────┴───────────┘
NEWT-641: DleqVerifier Library
Solidity library using EIP-196 precompiles (bn128 alt_bn128) to verify DLEQ proofs on-chain:
Prove: log_G(PK_i) == log_{enc}(D_i)
This is the cryptographic primitive that makes "wrong-keyshare" provable on-chain. Without it, you can only detect wrong shares off-chain
(which is what the Rust DLEQ code already does), but you can't slash for it.
NEWT-640: PrivacySlasher Contract
Calls EigenLayer's InstantSlasher.fulfillSlashingRequest() when a violation is proven. References EpochRegistry.isEpochActive() and
getEpoch() to validate that the slashing claim is against a valid epoch.
Why Phase 2D Matters
Without Phase 2D, the privacy system is trust-based, not trust-minimized.
Right now, operators can:
- Submit garbage partial decryptions — the gateway detects this via DLEQ verification and excludes the operator, but the operator faces
zero economic consequences. They can keep doing it. - Refuse to participate — a malicious operator can selectively drop privacy tasks (censorship) with no penalty. As long as t-of-n others
participate, the task succeeds, but the attack is free. - Participate in unauthorized decryptions — if an operator's key share is compromised, there's no on-chain mechanism to prove misuse.
The practical impact:
- For users: Privacy data is protected by operator honesty, not economics. An operator could collude to reconstruct the master secret
(needs t operators) with no slashing risk. - For the protocol: Can't credibly claim "restaked security" for privacy operations. EigenLayer's value proposition is economic security
— without slashing, privacy is outside that model. - For auditors: A security audit would flag "no slashing for privacy violations" as a critical finding.
How Critical is Phase 2D?
For testnet/staging: Not blocking. The current system works correctly — DLEQ proofs verify off-chain, bad operators are excluded from
aggregation, and the permissioned operator set provides practical security.
For mainnet with real money: Phase 2D is a prerequisite. You can't launch privacy features with restaked ETH security claims unless
misbehavior has economic consequences. The DLEQ proof is the key differentiator from other threshold encryption systems — it makes
wrong-keyshare provable on-chain, not just detectable.
For the DAO transition: The PrivacySlasher is permissionless (anyone can submit a slashing proof). This aligns with decentralization —
you don't need a trusted entity to police operators.
Recommendation
Phase 2D is necessary for production but not urgent for the current testnet milestone. The natural order:
- Deploy EpochRegistry to stagef/testnet (just merged)
- Run PSS epoch rotation in production for a few cycles to validate stability
- Implement Phase 2D (NEWT-629 → NEWT-641 → NEWT-640) before mainnet privacy launch
- Security audit should cover both the Rust crypto (PSS, DLEQ) and the Solidity slashing contracts together
The DLEQ verification is the hard part — it requires mapping the Ristretto255 DLEQ proof to bn128 pairing-friendly operations on-chain,
which involves non-trivial curve arithmetic. That's why NEWT-641 exists as a separate library.
✻ Sautéed for 52s
❯ How does privacy slashing work in had with the current slashing logic we have? For example, if policy evaluation was correct, but
privacy decryption was incorrect, who get's slashed? is it just privacy decyrption operated operators?
⏺ ★ Insight ─────────────────────────────────────
This is a critical trust model question. Newton currently has two independent slashing mechanisms, and Phase 2D adds a third.
Understanding how they compose is essential for the spec.
─────────────────────────────────────────────────
Current Slashing Architecture
Let me check how the existing slashing works to give a precise answer:
Searched for 2 patterns (ctrl+o to expand)
⏺ Now I have the full picture. Here's how the two slashing systems interact:
Current Slashing: Policy Evaluation Correctness
The existing system slashes operators for incorrect policy evaluation results:
- raiseAndResolveChallenge — A challenger re-evaluates the policy independently (including generating a ZK proof via SP1). If the
operator's evaluationResult was wrong, ChallengeLib.slashSigningOperators() slashes all operators who BLS-signed the incorrect response
at 10% (WADS_TO_SLASH = 100000000000000000). - challengeDirectlyVerifiedAttestation — Same mechanism for the direct attestation path.
- slashForCrossChainChallenge — Cross-chain relay of a challenge from a destination chain.
Who gets slashed: Every operator whose BLS signature is in the aggregated response. The BLS aggregation means all signers are
collectively liable — you can't distinguish which individual operator produced a wrong sub-result.
Phase 2D: Privacy Decryption Correctness
Privacy slashing is fundamentally different from policy slashing because it targets a different failure mode:
┌─────────────────┬───────────────────────────────────────┬─────────────────────────────────────────────────┐
│ Aspect │ Policy Slashing (current) │ Privacy Slashing (Phase 2D) │
├─────────────────┼───────────────────────────────────────┼─────────────────────────────────────────────────┤
│ What failed │ Policy evaluation result was wrong │ Threshold decryption share was wrong or missing │
├─────────────────┼───────────────────────────────────────┼─────────────────────────────────────────────────┤
│ Who is liable │ All BLS signers (collective) │ Individual operator (targeted) │
├─────────────────┼───────────────────────────────────────┼─────────────────────────────────────────────────┤
│ Proof type │ ZK proof of correct evaluation (SP1) │ DLEQ proof of correct partial DH │
├─────────────────┼───────────────────────────────────────┼─────────────────────────────────────────────────┤
│ Detection │ Challenger re-evaluates independently │ Gateway verifies each operator's DLEQ proof │
├─────────────────┼───────────────────────────────────────┼─────────────────────────────────────────────────┤
│ When it happens │ After respondToTask on-chain │ During Prepare phase (off-chain, before BLS) │
└─────────────────┴───────────────────────────────────────┴─────────────────────────────────────────────────┘
The Key Interaction: Your Scenario
"Policy evaluation correct, but privacy decryption incorrect"
This is the exact scenario Phase 2D addresses. Here's the timeline:
- Prepare phase: Gateway asks operators for partial DH outputs. Operator A submits a wrong partial decryption with an invalid DLEQ
proof. - Gateway detects it: The Rust DLEQ verification (crates/core/src/dkg/dleq.rs) rejects Operator A's contribution. Gateway excludes
Operator A and proceeds with the remaining t-1 operators (if enough remain). - Commit phase: Policy evaluation happens with the correct decrypted data (from honest operators). BLS aggregation succeeds. The policy
result is correct. - On-chain: respondToTask lands. No policy challenge is raised (the result was correct).
Without Phase 2D: Operator A submitted a bad share, was excluded, and faces zero consequences. They can keep doing this — degrading
liveness (fewer operators available for threshold) without any economic risk.
With Phase 2D: The gateway submits Operator A's invalid DLEQ proof to the PrivacySlasher contract. The DleqVerifier library verifies the
proof is indeed invalid on-chain. Operator A gets slashed 10%. Only Operator A — not the other honest operators.
Separation of Concerns
The two slashing systems are independent and composable:
Policy slashing: BLS signers of wrong result → 10% slash (collective)
Privacy slashing: Individual bad decryptor → 10% slash (targeted)
An operator could be slashed by both systems in theory:
- They submit a wrong partial decryption AND sign a wrong policy result
- Privacy slash hits them individually for the bad share
- Policy slash hits all signers (including them) for the bad result
But in practice, the bad decryption is caught in the Prepare phase (before BLS signing), so the operator is excluded before they can sign
a wrong result. The two failure modes are temporally separated.
What About "No-Keyshare" (Liveness)?
The 1-2% slash for absence is lighter because:
- The operator might be legitimately down (network issues)
- It only matters if their absence drops below threshold (n-absent < t)
- Detection is via quorum attestation (t operators attest "Operator A didn't respond"), not cryptographic proof
This is similar to how EigenLayer handles general liveness failures, just scoped to the privacy decryption window.
✻ Crunched for 1m 21s
❯ But in this model, Gateway is being trusted that it will honestly submit onchain to slash the bad operators. Isn't it? is there a way
for the current challenger to do so?
⏺ You've identified the critical trust assumption. Yes — in the current Phase 2D design, the gateway is the only entity that sees the raw
DLEQ proofs during the Prepare phase. If the gateway is compromised or colluding with a bad operator, it can simply not submit the
slashing proof.
The Trust Gap
┌───────────────────────────────┬─────────────────────────┬──────────────────┬─────────────────────────────────────────────────────┐
│ Slashing Type │ Who Detects │ Who Submits │ Trust Assumption │
│ │ │ Proof │ │
├───────────────────────────────┼─────────────────────────┼──────────────────┼─────────────────────────────────────────────────────┤
│ Policy (current) │ Challenger │ Challenger │ Trustless — anyone can re-evaluate and challenge │
│ │ (independent) │ │ │
├───────────────────────────────┼─────────────────────────┼──────────────────┼─────────────────────────────────────────────────────┤
│ Privacy (Phase 2D as │ Gateway │ Gateway │ Trusted — gateway must be honest about DLEQ │
│ designed) │ │ │ failures │
└───────────────────────────────┴─────────────────────────┴──────────────────┴─────────────────────────────────────────────────────┘
The current challenger cannot detect privacy decryption failures because:
- The challenger doesn't participate in the Prepare phase — it only sees the final respondToTask on-chain
- The DLEQ proofs are exchanged off-chain between operators and the gateway
- By the time the task response is on-chain, the bad operator was already excluded and the result is correct
Could the Challenger Do It?
For the challenger to independently verify privacy decryption correctness, it would need:
Option A: On-chain DLEQ commitments (NEWT-630)
This is actually what NEWT-630 (on-chain aggregation commitments) is designed for. If the gateway publishes a commitment of all partial
decryptions + DLEQ proofs on-chain (or to EigenDA), then:
- Gateway posts commitmentRoot = keccak256(operator_id, D_i, pi_i) per task
- Challenger can request the individual proofs from EigenDA/gateway
- Challenger verifies each DLEQ proof independently
- If a proof is invalid, challenger submits slashing proof to PrivacySlasher
This makes privacy slashing trustless — same model as policy slashing. The gateway can't hide a bad operator because the commitment is
on-chain.
Option B: Operators report each other
In the Prepare phase, each operator receives partial decryptions from peers (in a broadcast model). If Operator B sees Operator A's
invalid DLEQ proof, Operator B could submit the slashing proof directly. But currently the gateway mediates all communication — operators
don't see each other's proofs.
Option C: Broadcast partial decryptions + proofs publicly
Post all DLEQ proofs to a public data availability layer. Anyone can verify. But this leaks partial decryption data which has privacy
implications.
The Right Architecture
The combination of NEWT-630 (commitments) + NEWT-640 (PrivacySlasher) makes it trustless:
Prepare phase:
Gateway collects partials + DLEQ proofs from operators
Gateway verifies DLEQ proofs (excludes bad operators)
Gateway posts commitment root on-chain (NEWT-630)
Challenge window:
Challenger fetches individual proofs via EigenDA or gateway
Challenger verifies each DLEQ proof independently
If invalid: submits to PrivacySlasher (NEWT-640)
This is why NEWT-630 exists alongside NEWT-640/641 — without the commitments, the slashing is gateway-trusted. With commitments, any
external party can verify and slash, matching the existing challenger model for policy evaluation.
Practical Recommendation
For the implementation order, I'd suggest:
- NEWT-641 (DleqVerifier library) — pure cryptographic library, no trust assumptions
- NEWT-630 (on-chain commitments) — makes DLEQ proofs publicly verifiable
- NEWT-640 (PrivacySlasher) — references both DleqVerifier and commitments
- NEWT-629 (spec update) — refine the spec to require commitment-based detection, not gateway-based
This way the slashing system is trustless from day one, matching Newton's existing challenger model. Want me to update the Linear tickets
to reflect this dependency ordering and the commitment requirement?
⏺ All 4 tickets updated. Here's the final dependency graph:
NEWT-628 (PSS epoch rotation) ──── DONE
└── NEWT-639 (EpochRegistry) ──── DONE
NEWT-641 (DleqVerifier library) ──── Backlog
└── blocks NEWT-640
NEWT-630 (On-chain commitments) ──── Backlog
├── blocks NEWT-629
└── blocks NEWT-640
NEWT-629 (Slashing spec) ──── Backlog, blocked by NEWT-628 (done) + NEWT-630
├── NEWT-640 (PrivacySlasher) ──── Backlog, blocked by NEWT-641 + NEWT-630
└── NEWT-641 (DleqVerifier) ──── Backlog
Implementation order:
- NEWT-641 — DleqVerifier library (no blockers, pure crypto)
- NEWT-630 — On-chain commitments (no blockers, extends TaskResponseMetadata)
- NEWT-640 — PrivacySlasher (blocked by both above)
- NEWT-629 — Spec finalization (refine based on implementation)
Key updates made:
- NEWT-629: Rewrote to emphasize trustless detection model, removed Tier 3 plaintext leakage (deferred to TEE), added policy-vs-privacy
slashing comparison table - NEWT-640: Updated with permissionless design, explicit DleqVerifier + commitments dependency, slashing flow for both violation types
- NEWT-641: Added blocks: NEWT-640 relation
- NEWT-630: Added blocks: NEWT-640 relation
❯ one more question. is PrivacySlasher purely for Prepare phase?
⏺ No — it covers both phases, but different violation types map to different phases:
┌──────────────────────────┬───────────┬─────────────────────────────────────────────────────────────────────────────────────────────┐
│ Violation │ Phase │ Why │
├──────────────────────────┼───────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ Wrong-keyshare (DLEQ │ Prepare │ Operators submit partial DH outputs during Prepare. The DLEQ proof accompanies each │
│ failure) │ phase │ partial. │
├──────────────────────────┼───────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ No-keyshare (liveness) │ Prepare │ Operator fails to respond during the Prepare phase timeout. │
│ │ phase │ │
├──────────────────────────┼───────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ Wrong BLS signature on │ Commit │ Operator BLS-signs a policy result that was computed from incorrect decrypted data. But │
│ privacy result │ phase │ this is already covered by the existing policy challenger (ZK proof) — not PrivacySlasher's │
│ │ │ scope. │
└──────────────────────────┴───────────┴─────────────────────────────────────────────────────────────────────────────────────────────┘
So practically, the PrivacySlasher targets Prepare phase misbehavior — that's where threshold decryption happens. The Commit phase
(policy evaluation + BLS signing) is already covered by the existing ChallengeVerifier.
There's one edge case worth noting: in the gateway-never-decrypts model, operators also decrypt locally during the Commit phase (they
receive partial DH outputs from Prepare and complete HPKE decryption). But that local decryption is deterministic given correct partials
— if the partials were valid (verified via DLEQ in Prepare), the Commit-phase decryption can't be wrong. So there's no separate
Commit-phase privacy violation to slash for.
The flow:
Prepare phase: partials + DLEQ proofs → PrivacySlasher can slash
Commit phase: policy eval + BLS sign → ChallengeVerifier can slash (existing)
Two independent slashing systems, one per phase. No overlap, no gaps.
Let's proceed to next steps
⏺ The natural next step is NEWT-641 (DleqVerifier library) — it's the first in the dependency chain, has no blockers, and is pure
cryptographic library code.
But before diving into implementation, there's a significant design challenge worth discussing: the curve mismatch. Our DLEQ proofs are
generated on Ristretto255 (Rust side), but EVM precompiles (EIP-196) operate on alt_bn128 (BN254). These are fundamentally different
curves — you can't directly verify a Ristretto255 DLEQ proof using BN254 precompiles.
This needs a /grill-me before implementation. The ticket mentions two approaches:
- Proof translation — operator generates on Ristretto255, translates for BN254 verification
- Dual-curve commitment — operator commits public key share on both curves during DKG
There may be a third option worth exploring: verifying the DLEQ proof in a ZK circuit (SP1) instead of via EVM precompiles — we already
have the SP1 infrastructure for policy challenges.
Want to start with /grill-me on the DleqVerifier curve strategy, or would you prefer to tackle something else first?
✻ Crunched for 31s
❯ yes /grill-me on the DleqVerifier curve strategy
⏺ Skill(grill-me)
⎿ Successfully loaded skill
⏺ Let me first understand the exact DLEQ proof structure we're working with on the Rust side.
Read 1 file (ctrl+o to expand)
⏺ Good — now I have the exact DLEQ structure. The proof operates on Ed25519 (Curve25519 in Edwards form), not Ristretto255 as I initially
said. The proof is:
- c = SHA-256("newton-dleq-v1" || G || A || H || B || R1 || R2) mod order
- z = k - c * secret
- Verification: zG + cA == R1 and zH + cB == R2
This is standard Schnorr-style DLEQ on Ed25519 (group order ~2^252). EVM precompiles operate on BN254 (alt_bn128, group order ~2^254).
These are different curves with different group orders, base points, and coordinate systems.
Question 1 of N: Which verification approach should we use?
Three options with fundamentally different trade-offs:
A) Native Solidity verification on Ed25519
Implement Ed25519 point arithmetic in pure Solidity — scalar multiplication via double-and-add, point addition, SHA-256 hash. No
precompiles needed (SHA-256 is available as a precompile at address 0x02). But Ed25519 scalar mul in Solidity is extremely expensive —
~1M+ gas per verification (no native precompile for Curve25519).
B) SP1 ZK circuit verification
The DLEQ verification runs inside an SP1 zkVM circuit (we already have SP1 infrastructure for sp1-rego policy challenges). The circuit
verifies the DLEQ proof off-chain and produces a succinct proof. On-chain, only the SP1 proof is verified (~300k gas via the SP1 verifier
contract). The operator's DLEQ proof correctness is reduced to "verify this ZK proof."
C) Dual-curve scheme
During DKG/PSS, operators commit their public key share on both Ed25519 (for HPKE threshold decryption) and BN254 (for on-chain DLEQ
verification). Operators generate a second DLEQ proof on BN254 alongside the Ed25519 one. The BN254 proof can be verified on-chain using
EIP-196/197 precompiles (~40k gas). But this doubles the DKG key material and requires operators to maintain two key shares.
My recommendation: Option B (SP1 ZK circuit). Reasons:
- We already have SP1 infrastructure (circuits/sp1-rego/) and the SP1 verifier deployed on-chain
- No changes to the DKG/PSS ceremony or key material
- The DLEQ verification is simple enough for an SP1 circuit (~100 lines)
- Gas cost is moderate (~300k) but acceptable for a slashing path (not hot path)
- The Ed25519 arithmetic runs natively inside SP1's RISC-V environment (no field translation)
- Aligns with the existing challenger pattern: challenger generates ZK proof, submits on-chain
A, B, or C?
✻ Churned for 51s
❯ How complex is C? "But this doubles the DKG key material and requires operators to maintain two key shares." --> what does this entail
in terms of complexity, error-prone-ness, reliability, efficiency?
⏺ Good question. Let me break down exactly what dual-curve entails:
What "Two Key Shares" Means Concretely
Currently each operator has:
- One secret scalar s_i (a 32-byte number)
- One public share pk_i = s_i * G_ed25519 (Ed25519 point)
With dual-curve, each operator would have:
- Same secret scalar s_i (it's just a number — curve-agnostic)
- Ed25519 public share: pk_i_ed = s_i * G_ed25519
- BN254 public share: pk_i_bn = s_i * G_bn254
The secret scalar is the same — you don't need two separate secrets. You just compute the public share on both curves. The scalar s_i is
~252 bits; both Ed25519 (order ~2^252) and BN254 (order ~2^254) can accommodate it.
Complexity Assessment
DKG ceremony changes:
- frost.rs round3: after computing s_i, also compute s_i * G_bn254 and store it
- keystore.rs: store both public shares in the encrypted keystore
- types.rs: add bn254_public_share: [u8; 64] to KeyShare (BN254 points are 64 bytes uncompressed)
- FROST package broadcast: include the BN254 public share in round3 output so all parties know it
~50 lines of code change in the DKG crate. Not trivial but not massive.
PSS refresh changes:
- refresh.rs: Feldman commitments are currently Ed25519 only. Would need BN254 commitments too (or derive from the same polynomial, since
coefficients are scalars) - Actually — the polynomial coefficients are scalars, and Feldman verification is share * G == sum(C_j * x^j). If we compute Feldman
commitments on both curves, verification works on both independently - ~30 lines additional in refresh.rs
Operator runtime changes:
- During Prepare phase: operator generates DLEQ proof on BN254 instead of (or in addition to) Ed25519
- Requires a BN254 scalar multiplication library in Rust (e.g., ark-bn254 or alloy's BN254 support)
- New dependency in the operator crate
On-chain changes:
- EpochRegistry or a companion contract stores BN254 public shares per operator per epoch
- Additional ~21k gas per operator per epoch for storage
- DleqVerifier uses EIP-196 ecMul + ecAdd: ~12k gas for verification (very cheap)
Error-Proneness
The risky part: the scalar s_i must be valid in both group orders. Ed25519 order is 2^252 + 27742... and BN254 order is 21888...
(~2^254). Since the BN254 order is larger, any valid Ed25519 scalar is also valid for BN254. This works safely — no modular reduction
needed.
But: if we ever change the DKG to generate scalars mod BN254's order (larger), they might not be valid Ed25519 scalars. This is a latent
footgun. The rule would be: always generate scalars mod Ed25519 order (the smaller one).
FROST interaction: FROST DKG on Ristretto255 generates scalars mod the Ristretto255 group order (same as Ed25519). These are guaranteed
valid for BN254. No issue with the current FROST implementation.
Reliability
- One more thing that can go wrong during DKG: BN254 key derivation failure
- One more field to serialize/deserialize in keystore
- One more commitment to verify during PSS refresh
- But all of these are deterministic — if the scalar is valid (which it is by construction), the BN254 operations can't fail
Efficiency
┌───────────────────────┬──────────────────────────────┬────────────────────────────────────────────────────┐
│ Aspect │ Current (Ed25519 only) │ Dual-Curve │
├───────────────────────┼──────────────────────────────┼────────────────────────────────────────────────────┤
│ DKG ceremony │ 3 rounds │ 3 rounds (same, +1 point computation per operator) │
├───────────────────────┼──────────────────────────────┼────────────────────────────────────────────────────┤
│ Keystore size │ ~200 bytes │ ~264 bytes (+64 bytes for BN254 point) │
├───────────────────────┼──────────────────────────────┼────────────────────────────────────────────────────┤
│ PSS refresh │ 1 set of Feldman commitments │ 2 sets (but can share polynomial) │
├───────────────────────┼──────────────────────────────┼────────────────────────────────────────────────────┤
│ Prepare phase │ 1 DLEQ proof (Ed25519) │ 1 DLEQ proof (BN254) — can drop Ed25519 proof │
├───────────────────────┼──────────────────────────────┼────────────────────────────────────────────────────┤
│ On-chain verification │ N/A │ ~12k gas (ecMul precompile) │
├───────────────────────┼──────────────────────────────┼────────────────────────────────────────────────────┤
│ On-chain storage │ 0 │ ~21k gas per operator per epoch │
└───────────────────────┴──────────────────────────────┴────────────────────────────────────────────────────┘
Compared to SP1 (Option B)
┌───────────────────────────┬───────────────────────────┬───────────────────────────────────┐
│ Aspect │ SP1 ZK Circuit │ Dual-Curve │
├───────────────────────────┼───────────────────────────┼───────────────────────────────────┤
│ On-chain verification gas │ ~300k (SP1 verifier) │ ~12k (ecMul precompile) │
├───────────────────────────┼───────────────────────────┼───────────────────────────────────┤
│ Proof generation time │ ~10-30s (SP1 prover) │ ~0ms (BN254 point mul is instant) │
├───────────────────────────┼───────────────────────────┼───────────────────────────────────┤
│ New dependencies │ None (SP1 already exists) │ ark-bn254 or similar in operator │
├───────────────────────────┼───────────────────────────┼───────────────────────────────────┤
│ DKG changes │ None │ ~80 lines │
├───────────────────────────┼───────────────────────────┼───────────────────────────────────┤
│ Slashing latency │ Slow (proof generation) │ Instant (precompile verification) │
├───────────────────────────┼───────────────────────────┼───────────────────────────────────┤
│ Complexity locus │ Circuit code │ DKG + keystore + refresh │
└───────────────────────────┴───────────────────────────┴───────────────────────────────────┘
My Updated Assessment
Dual-curve is more complex upfront (~80 lines across DKG/PSS/keystore) but dramatically cheaper and faster at verification time (12k gas
vs 300k, instant vs 10-30s proof generation). The complexity is contained in well-tested crypto code (scalar multiplication is
deterministic), not in a new ZK circuit.
SP1 is simpler to implement (no DKG changes) but slower and more expensive when actually slashing.
Revised recommendation: If slashing speed matters (a bad operator should be slashable immediately, not after waiting for SP1 proof
generation), dual-curve is better. If implementation speed matters and slashing is rare, SP1 is better.
What's your priority — fast implementation or fast slashing?
✻ Cooked for 45s
❯ reliability and least error-prone (technically and operationally). Least execution risk.
⏺ With that criteria, Option B (SP1 ZK circuit) is the clear winner.
Here's why:
Dual-curve (C) execution risks:
- Modifying the DKG ceremony is high-stakes — FROST is the most complex crypto in the codebase. Any bug in round3 BN254 key derivation
could produce invalid key material across all operators, requiring a fresh DKG ceremony to recover - PSS refresh with dual Feldman commitments doubles the verification surface — a bug means operators silently accumulate invalid BN254
shares across epochs - The "scalar valid in both group orders" invariant is a latent footgun that every future contributor must know about
- New ark-bn254 dependency in the operator crate — different serialization format, different point representation, new attack surface
- Every operator must upgrade simultaneously to support BN254 — partial rollout breaks the ceremony
SP1 (B) execution risks:
- The DLEQ verification logic is ~30 lines of Rust (same code as dleq.rs::verify(), compiled to RISC-V)
- No changes to DKG, PSS, keystore, or operator runtime
- No new dependencies — curve25519-dalek already works in SP1's RISC-V target
- The SP1 verifier contract is already deployed and battle-tested on-chain
- Slashing is rare (dispute path, not hot path) — 10-30s proof generation is acceptable
- If the SP1 circuit has a bug, fix and redeploy the circuit — no operator coordination needed
The decisive factor: with dual-curve, a bug corrupts live key material across all operators. With SP1, a bug means a slashing proof fails
to verify — fixable without touching the privacy system.
Let me continue the grill.
Question 2 of N: SP1 circuit scope — verify a single DLEQ proof, or verify the full commitment?
Two circuit designs:
A) Single DLEQ verification: The circuit takes one operator's DLEQ proof + public inputs (G, pk_i, H, D_i) and outputs pass/fail. The
challenger generates one SP1 proof per operator they want to slash.
B) Commitment batch verification: The circuit takes the full commitment from NEWT-630 (all operator partials + DLEQ proofs for a task)
and verifies all of them, outputting a bitmap of which operators' proofs are invalid. One SP1 proof covers the entire task.
My recommendation: Option A (single DLEQ). Simpler circuit, faster proof generation (one operator's proof vs all), and the PrivacySlasher
only needs to slash one operator at a time. Batch can be added later if needed.
A or B?
✻ Brewed for 30s
❯ B) Commitment batch verification: The circuit takes the full commitment from NEWT-630 (all operator partials + DLEQ proofs for a task)
and verifies all of them, outputting a bitmap of which operators' proofs are invalid. One SP1 proof covers the entire task. -->
similar to how we slashing in ChallengeVerifier
⏺ Exactly — matches the existing pattern in ChallengeLib.slashSigningOperators() which slashes all bad signers in one batch. Consistent
architecture.
Question 3 of N: What are the public inputs and outputs of the SP1 circuit?
The circuit needs to:
- Take in all DLEQ proofs for a task
- Verify each against the operator's committed public share
- Output which operators failed
Public inputs (committed on-chain, verifiable by anyone):
- decryptionCommitment (bytes32) — from NEWT-630, the on-chain commitment hash
- epochId (uint64) — which epoch's key shares to verify against
- taskId (bytes32) — which task's partial decryptions
Private inputs (provided by the challenger, not on-chain):
- Per-operator: (operator_index, pk_share, enc_point, partial_decryption, dleq_proof)
- These are fetched from EigenDA or the gateway's data availability layer
Public outputs (written to the proof, verified on-chain):
- invalidOperatorBitmap (uint256) — bitmask of which operators submitted invalid DLEQ proofs
- commitmentHash (bytes32) — re-computed from the private inputs, must match the on-chain decryptionCommitment
The on-chain PrivacySlasher then:
- Verifies the SP1 proof
- Checks commitmentHash == stored decryptionCommitment (binding)
- Slashes each operator flagged in invalidOperatorBitmap
My recommendation: This structure directly. The commitmentHash binding prevents the challenger from fabricating inputs — they must use
the same data the gateway committed to.
Does this input/output structure look right, or do you want to adjust?
✻ Cogitated for 36s
❯ looks right
⏺ Question 4 of N: Where does the challenger get the raw DLEQ proofs (private inputs)?
The on-chain commitment (NEWT-630) stores only a hash. The challenger needs the actual per-operator data to feed into the SP1 circuit.
Three options:
▐▛███▜▌ Claude Code v2.1.108
▝▜█████▛▘ Opus 4.6 (1M context) with max effort · API Usage Billing
▘▘ ▝▝ ~/projects/newton-prover-avs/contracts
⚠ Large .claude/rules/lessons.md will impact performance (48.3k chars > 40.0k) · /memory to edit
⏺ That's a strong argument for keeping it on-chain. When admin transitions to a DAO, on-chain transactions are the only way the DAO can
interact — there's no "call the gateway RPC" option for a multisig or governance contract. The current design is forward-compatible with
that.
Want me to add that context as a reply on Albert's thread? Something like: "admin will be a DAO in the future — on-chain tx is the only
interface available to governance contracts. keeping it on-chain now avoids a migration later."
❯ update our comment
⏺ Bash(gh api graphql -f query='
mutation {…)
⎿ ok
⏺ Posted. The thread now has the full reasoning: Safe multisig access pattern, block time latency, auditability, and DAO
forward-compatibility.
✻ Churned for 1m 10s
❯ also another engineer asks "that also means an ethereum halt (not often anymore) could compromise the platform for the length of a halt
right?"
⏺ That's a valid concern but the impact is limited. Let me think through it:
An Ethereum halt means no new blocks → the chain watcher can't detect the EmergencyRotationTriggered event → the gateway doesn't trigger
an immediate PSS refresh. But:
- The emergency signal is a speed-up, not a gate. The EpochManager already rotates on a timer (default 24h). An Ethereum halt delays the
emergency rotation, not all rotation. Scheduled rotations are purely off-chain (operator RPC calls) and don't depend on Ethereum being
live. - The halt also blocks the attacker. If Ethereum is halted, the compromised key share can't be exploited on-chain either — no
transactions can land. The privacy data is only useful in the context of on-chain task submissions, which are also blocked. - The actual blast radius is: time between the admin detecting the compromise and the next scheduled refresh, which is at most
epoch_duration_seconds (24h default). The emergency signal would reduce that to ~seconds, but during a halt it falls back to the timer.
Should I post this reasoning as a reply? The short version: "Ethereum halt delays the emergency signal, but scheduled rotations are
off-chain and unaffected. The halt also blocks the attacker from exploiting on-chain. Blast radius is bounded by epoch_duration_seconds,
not the halt duration."
❯ what are pro vs cons of the two approaches?
⏺ On-chain emergency signal (current approach)
Pros:
- DAO-compatible — governance contracts can only interact via on-chain tx
- Auditable — when admin requested rotation vs when it completed is visible on-chain
- No network access needed — admin (Safe multisig) doesn't need gateway RPC endpoint
- Immutable record — can't be disputed or lost
Cons:
- Latency tied to block time (2-24s depending on chain)
- Publicly broadcasts that an emergency occurred (operational info leak)
- Ethereum halt delays the signal (falls back to scheduled rotation timer)
- Gas cost for the tx (minimal, ~27k)
Off-chain emergency signal (admin RPC endpoint on gateway)
Pros:
- Instant — no block time latency, sub-second
- Private — no public broadcast of the emergency
- Works during chain halt
- Zero gas cost
Cons:
- Not DAO-compatible — governance contracts can't call an HTTP endpoint
- Requires admin to have network access to the gateway (VPN, internal network)
- No immutable audit trail — log-based only, can be tampered
- Requires separate auth mechanism for the admin endpoint (API key, mTLS)
- Gateway being down/unreachable blocks the emergency signal entirely
The key differentiator is the DAO trajectory. If admin stays as an EOA/multisig with gateway access, off-chain is simpler. Once admin
becomes a DAO, on-chain is the only option. Since we're planning the DAO transition, on-chain is the forward-compatible choice.
Want me to post a summary of this on the PR thread?
✻ Cooked for 31s
❯ this PR has been merged. Let's update Linear project task status to reflect the current progress
⏺ Let me check the current status of NEWT-639 and update it:
Called linear-server (ctrl+o to expand)
⏺ NEWT-639 is already marked Done (auto-closed by PR #544 merge). The PR attachment is linked. Let me check the remaining tickets to see if
any need status updates now that NEWT-639 is complete:
Called linear-server (ctrl+o to expand)
⏺ Linear is already up to date. Here's the current state:
Done (auto-closed by PR merges):
- NEWT-639 (EpochRegistry) — Done, PR #544 attached
- NEWT-628 (PSS epoch rotation) — Done
- All Phase 1→2C tickets — Done
Remaining Backlog (Phase 2D Solidity, unchanged):
┌──────────┬────────────────────────────────────┬─────────┐
│ Ticket │ Title │ Status │
├──────────┼────────────────────────────────────┼─────────┤
│ NEWT-629 │ Privacy slashing conditions (spec) │ Backlog │
├──────────┼────────────────────────────────────┼─────────┤
│ NEWT-640 │ PrivacySlasher contract │ Backlog │
├──────────┼────────────────────────────────────┼─────────┤
│ NEWT-641 │ DleqVerifier library │ Backlog │
├──────────┼────────────────────────────────────┼─────────┤
│ NEWT-630 │ On-chain aggregation commitments │ Backlog │
└──────────┴────────────────────────────────────┴─────────┘
Far future (Phase 3/4): MPC/ZK, mainnet deployment, ecosystem — all Backlog.
No manual status updates needed — the GitHub integration auto-marked NEWT-639 as Done when PR #544 merged. The remaining backlog tickets
are correctly in Backlog status, unblocked and ready for when we pick them up.
✻ Cooked for 1m 6s
❯ analyze the importance and implication of phase 2D. What is our current state in terms of newton privacy layer, and what does phase 2D
bring and why important?
⏺ ★ Insight ─────────────────────────────────────
This is a critical architectural question. Phase 2D is the economic enforcement layer — without it, the privacy system relies entirely on
operator honesty. Let me analyze what we have, what's missing, and why it matters.
─────────────────────────────────────────────────
Current State: Privacy Layer without Economic Enforcement
What we have (Phases 1→2C, all merged):
- HPKE encryption (X25519/ChaCha20Poly1305) for all three privacy paths
- Threshold DKG (FROST on Ristretto255) — operators hold key shares, MSK never assembled
- PSS epoch rotation — shares refresh every 24h, MPK constant, forward secrecy
- Gateway-never-decrypts — operators always decrypt locally
- Multi-domain resolution — identity, confidential, ephemeral all supported simultaneously
- EpochRegistry on-chain — epoch commitments, MPK immutability, grace periods
- Challenger support for privacy task replay
What's missing (Phase 2D):
- No economic penalty for operators who misbehave during threshold decryption
- No on-chain verification of partial decryption correctness
- No slashing for operators who refuse to participate
What Phase 2D Brings
NEWT-629: Slashing Conditions (spec)
Defines three provable violation types:
┌─────────────────────┬──────────────────────────────────────────────┬──────────────────────────────────────────────────┬───────────┐
│ Violation │ Detection │ Proof Type │ Severity │
├─────────────────────┼──────────────────────────────────────────────┼──────────────────────────────────────────────────┼───────────┤
│ Wrong-keyshare │ Operator submits an invalid partial DH │ DLEQ proof failure (deterministic, │ 10% slash │
│ │ output │ cryptographic) │ │
├─────────────────────┼──────────────────────────────────────────────┼──────────────────────────────────────────────────┼───────────┤
│ No-keyshare │ Operator absent during threshold decryption │ Quorum attestation (t-of-n operators attest to │ 1-2% │
│ │ window │ absence) │ slash │
├─────────────────────┼──────────────────────────────────────────────┼──────────────────────────────────────────────────┼───────────┤
│ Premature │ Operator participates in unauthorized task │ On-chain authorization check against │ 10% slash │
│ decryption │ │ EpochRegistry │ │
└─────────────────────┴──────────────────────────────────────────────┴──────────────────────────────────────────────────┴───────────┘
NEWT-641: DleqVerifier Library
Solidity library using EIP-196 precompiles (bn128 alt_bn128) to verify DLEQ proofs on-chain:
Prove: log_G(PK_i) == log_{enc}(D_i)
This is the cryptographic primitive that makes "wrong-keyshare" provable on-chain. Without it, you can only detect wrong shares off-chain
(which is what the Rust DLEQ code already does), but you can't slash for it.
NEWT-640: PrivacySlasher Contract
Calls EigenLayer's InstantSlasher.fulfillSlashingRequest() when a violation is proven. References EpochRegistry.isEpochActive() and
getEpoch() to validate that the slashing claim is against a valid epoch.
Why Phase 2D Matters
Without Phase 2D, the privacy system is trust-based, not trust-minimized.
Right now, operators can:
- Submit garbage partial decryptions — the gateway detects this via DLEQ verification and excludes the operator, but the operator faces
zero economic consequences. They can keep doing it. - Refuse to participate — a malicious operator can selectively drop privacy tasks (censorship) with no penalty. As long as t-of-n others
participate, the task succeeds, but the attack is free. - Participate in unauthorized decryptions — if an operator's key share is compromised, there's no on-chain mechanism to prove misuse.
The practical impact:
- For users: Privacy data is protected by operator honesty, not economics. An operator could collude to reconstruct the master secret
(needs t operators) with no slashing risk. - For the protocol: Can't credibly claim "restaked security" for privacy operations. EigenLayer's value proposition is economic security
— without slashing, privacy is outside that model. - For auditors: A security audit would flag "no slashing for privacy violations" as a critical finding.
How Critical is Phase 2D?
For testnet/staging: Not blocking. The current system works correctly — DLEQ proofs verify off-chain, bad operators are excluded from
aggregation, and the permissioned operator set provides practical security.
For mainnet with real money: Phase 2D is a prerequisite. You can't launch privacy features with restaked ETH security claims unless
misbehavior has economic consequences. The DLEQ proof is the key differentiator from other threshold encryption systems — it makes
wrong-keyshare provable on-chain, not just detectable.
For the DAO transition: The PrivacySlasher is permissionless (anyone can submit a slashing proof). This aligns with decentralization —
you don't need a trusted entity to police operators.
Recommendation
Phase 2D is necessary for production but not urgent for the current testnet milestone. The natural order:
- Deploy EpochRegistry to stagef/testnet (just merged)
- Run PSS epoch rotation in production for a few cycles to validate stability
- Implement Phase 2D (NEWT-629 → NEWT-641 → NEWT-640) before mainnet privacy launch
- Security audit should cover both the Rust crypto (PSS, DLEQ) and the Solidity slashing contracts together
The DLEQ verification is the hard part — it requires mapping the Ristretto255 DLEQ proof to bn128 pairing-friendly operations on-chain,
which involves non-trivial curve arithmetic. That's why NEWT-641 exists as a separate library.
✻ Sautéed for 52s
❯ How does privacy slashing work in had with the current slashing logic we have? For example, if policy evaluation was correct, but
privacy decryption was incorrect, who get's slashed? is it just privacy decyrption operated operators?
⏺ ★ Insight ─────────────────────────────────────
This is a critical trust model question. Newton currently has two independent slashing mechanisms, and Phase 2D adds a third.
Understanding how they compose is essential for the spec.
─────────────────────────────────────────────────
Current Slashing Architecture
Let me check how the existing slashing works to give a precise answer:
Searched for 2 patterns (ctrl+o to expand)
⏺ Now I have the full picture. Here's how the two slashing systems interact:
Current Slashing: Policy Evaluation Correctness
The existing system slashes operators for incorrect policy evaluation results:
- raiseAndResolveChallenge — A challenger re-evaluates the policy independently (including generating a ZK proof via SP1). If the
operator's evaluationResult was wrong, ChallengeLib.slashSigningOperators() slashes all operators who BLS-signed the incorrect response
at 10% (WADS_TO_SLASH = 100000000000000000). - challengeDirectlyVerifiedAttestation — Same mechanism for the direct attestation path.
- slashForCrossChainChallenge — Cross-chain relay of a challenge from a destination chain.
Who gets slashed: Every operator whose BLS signature is in the aggregated response. The BLS aggregation means all signers are
collectively liable — you can't distinguish which individual operator produced a wrong sub-result.
Phase 2D: Privacy Decryption Correctness
Privacy slashing is fundamentally different from policy slashing because it targets a different failure mode:
┌─────────────────┬───────────────────────────────────────┬─────────────────────────────────────────────────┐
│ Aspect │ Policy Slashing (current) │ Privacy Slashing (Phase 2D) │
├─────────────────┼───────────────────────────────────────┼─────────────────────────────────────────────────┤
│ What failed │ Policy evaluation result was wrong │ Threshold decryption share was wrong or missing │
├─────────────────┼───────────────────────────────────────┼─────────────────────────────────────────────────┤
│ Who is liable │ All BLS signers (collective) │ Individual operator (targeted) │
├─────────────────┼───────────────────────────────────────┼─────────────────────────────────────────────────┤
│ Proof type │ ZK proof of correct evaluation (SP1) │ DLEQ proof of correct partial DH │
├─────────────────┼───────────────────────────────────────┼─────────────────────────────────────────────────┤
│ Detection │ Challenger re-evaluates independently │ Gateway verifies each operator's DLEQ proof │
├─────────────────┼───────────────────────────────────────┼─────────────────────────────────────────────────┤
│ When it happens │ After respondToTask on-chain │ During Prepare phase (off-chain, before BLS) │
└─────────────────┴───────────────────────────────────────┴─────────────────────────────────────────────────┘
The Key Interaction: Your Scenario
"Policy evaluation correct, but privacy decryption incorrect"
This is the exact scenario Phase 2D addresses. Here's the timeline:
- Prepare phase: Gateway asks operators for partial DH outputs. Operator A submits a wrong partial decryption with an invalid DLEQ
proof. - Gateway detects it: The Rust DLEQ verification (crates/core/src/dkg/dleq.rs) rejects Operator A's contribution. Gateway excludes
Operator A and proceeds with the remaining t-1 operators (if enough remain). - Commit phase: Policy evaluation happens with the correct decrypted data (from honest operators). BLS aggregation succeeds. The policy
result is correct. - On-chain: respondToTask lands. No policy challenge is raised (the result was correct).
Without Phase 2D: Operator A submitted a bad share, was excluded, and faces zero consequences. They can keep doing this — degrading
liveness (fewer operators available for threshold) without any economic risk.
With Phase 2D: The gateway submits Operator A's invalid DLEQ proof to the PrivacySlasher contract. The DleqVerifier library verifies the
proof is indeed invalid on-chain. Operator A gets slashed 10%. Only Operator A — not the other honest operators.
Separation of Concerns
The two slashing systems are independent and composable:
Policy slashing: BLS signers of wrong result → 10% slash (collective)
Privacy slashing: Individual bad decryptor → 10% slash (targeted)
An operator could be slashed by both systems in theory:
- They submit a wrong partial decryption AND sign a wrong policy result
- Privacy slash hits them individually for the bad share
- Policy slash hits all signers (including them) for the bad result
But in practice, the bad decryption is caught in the Prepare phase (before BLS signing), so the operator is excluded before they can sign
a wrong result. The two failure modes are temporally separated.
What About "No-Keyshare" (Liveness)?
The 1-2% slash for absence is lighter because:
- The operator might be legitimately down (network issues)
- It only matters if their absence drops below threshold (n-absent < t)
- Detection is via quorum attestation (t operators attest "Operator A didn't respond"), not cryptographic proof
This is similar to how EigenLayer handles general liveness failures, just scoped to the privacy decryption window.
✻ Crunched for 1m 21s
❯ But in this model, Gateway is being trusted that it will honestly submit onchain to slash the bad operators. Isn't it? is there a way
for the current challenger to do so?
⏺ You've identified the critical trust assumption. Yes — in the current Phase 2D design, the gateway is the only entity that sees the raw
DLEQ proofs during the Prepare phase. If the gateway is compromised or colluding with a bad operator, it can simply not submit the
slashing proof.
The Trust Gap
┌───────────────────────────────┬─────────────────────────┬──────────────────┬─────────────────────────────────────────────────────┐
│ Slashing Type │ Who Detects │ Who Submits │ Trust Assumption │
│ │ │ Proof │ │
├───────────────────────────────┼─────────────────────────┼──────────────────┼─────────────────────────────────────────────────────┤
│ Policy (current) │ Challenger │ Challenger │ Trustless — anyone can re-evaluate and challenge │
│ │ (independent) │ │ │
├───────────────────────────────┼─────────────────────────┼──────────────────┼─────────────────────────────────────────────────────┤
│ Privacy (Phase 2D as │ Gateway │ Gateway │ Trusted — gateway must be honest about DLEQ │
│ designed) │ │ │ failures │
└───────────────────────────────┴─────────────────────────┴──────────────────┴─────────────────────────────────────────────────────┘
The current challenger cannot detect privacy decryption failures because:
- The challenger doesn't participate in the Prepare phase — it only sees the final respondToTask on-chain
- The DLEQ proofs are exchanged off-chain between operators and the gateway
- By the time the task response is on-chain, the bad operator was already excluded and the result is correct
Could the Challenger Do It?
For the challenger to independently verify privacy decryption correctness, it would need:
Option A: On-chain DLEQ commitments (NEWT-630)
This is actually what NEWT-630 (on-chain aggregation commitments) is designed for. If the gateway publishes a commitment of all partial
decryptions + DLEQ proofs on-chain (or to EigenDA), then:
- Gateway posts commitmentRoot = keccak256(operator_id, D_i, pi_i) per task
- Challenger can request the individual proofs from EigenDA/gateway
- Challenger verifies each DLEQ proof independently
- If a proof is invalid, challenger submits slashing proof to PrivacySlasher
This makes privacy slashing trustless — same model as policy slashing. The gateway can't hide a bad operator because the commitment is
on-chain.
Option B: Operators report each other
In the Prepare phase, each operator receives partial decryptions from peers (in a broadcast model). If Operator B sees Operator A's
invalid DLEQ proof, Operator B could submit the slashing proof directly. But currently the gateway mediates all communication — operators
don't see each other's proofs.
Option C: Broadcast partial decryptions + proofs publicly
Post all DLEQ proofs to a public data availability layer. Anyone can verify. But this leaks partial decryption data which has privacy
implications.
The Right Architecture
The combination of NEWT-630 (commitments) + NEWT-640 (PrivacySlasher) makes it trustless:
Prepare phase:
Gateway collects partials + DLEQ proofs from operators
Gateway verifies DLEQ proofs (excludes bad operators)
Gateway posts commitment root on-chain (NEWT-630)
Challenge window:
Challenger fetches individual proofs via EigenDA or gateway
Challenger verifies each DLEQ proof independently
If invalid: submits to PrivacySlasher (NEWT-640)
This is why NEWT-630 exists alongside NEWT-640/641 — without the commitments, the slashing is gateway-trusted. With commitments, any
external party can verify and slash, matching the existing challenger model for policy evaluation.
Practical Recommendation
For the implementation order, I'd suggest:
- NEWT-641 (DleqVerifier library) — pure cryptographic library, no trust assumptions
- NEWT-630 (on-chain commitments) — makes DLEQ proofs publicly verifiable
- NEWT-640 (PrivacySlasher) — references both DleqVerifier and commitments
- NEWT-629 (spec update) — refine the spec to require commitment-based detection, not gateway-based
This way the slashing system is trustless from day one, matching Newton's existing challenger model. Want me to update the Linear tickets
to reflect this dependency ordering and the commitment requirement?
✻ Brewed for 31s
❯ yes
Called linear-server (ctrl+o to expand)
⏺ Good — NEWT-630 already has the commitment architecture described in detail, and it already blocks NEWT-629. But the description needs
updating to reflect the DLEQ-based trustless slashing model we just discussed. Let me also check NEWT-629's current relations:
Called linear-server (ctrl+o to expand)
⏺ Good — the dependency graph already has NEWT-630 blocking NEWT-629, and NEWT-628 blocking NEWT-629 (done). Now let me update the tickets
to reflect the trustless slashing model and correct implementation order:
Called linear-server (ctrl+o to expand)
⏺ Now add the blocking relation — NEWT-641 should block NEWT-640 (PrivacySlasher needs DleqVerifier):
Called linear-server (ctrl+o to expand)
⏺ Now add NEWT-630 blocking NEWT-640 (PrivacySlasher needs commitments for trustless detection):
Called linear-server (ctrl+o to expand)
⏺ Now update NEWT-640 (PrivacySlasher) to reflect that it depends on both NEWT-641 and NEWT-630:
Called linear-server (ctrl+o to expand)
⏺ All 4 tickets updated. Here's the final dependency graph:
NEWT-628 (PSS epoch rotation) ──── DONE
└── NEWT-639 (EpochRegistry) ──── DONE
NEWT-641 (DleqVerifier library) ──── Backlog
└── blocks NEWT-640
NEWT-630 (On-chain commitments) ──── Backlog
├── blocks NEWT-629
└── blocks NEWT-640
NEWT-629 (Slashing spec) ──── Backlog, blocked by NEWT-628 (done) + NEWT-630
├── NEWT-640 (PrivacySlasher) ──── Backlog, blocked by NEWT-641 + NEWT-630
└── NEWT-641 (DleqVerifier) ──── Backlog
Implementation order:
- NEWT-641 — DleqVerifier library (no blockers, pure crypto)
- NEWT-630 — On-chain commitments (no blockers, extends TaskResponseMetadata)
- NEWT-640 — PrivacySlasher (blocked by both above)
- NEWT-629 — Spec finalization (refine based on implementation)
Key updates made:
- NEWT-629: Rewrote to emphasize trustless detection model, removed Tier 3 plaintext leakage (deferred to TEE), added policy-vs-privacy
slashing comparison table - NEWT-640: Updated with permissionless design, explicit DleqVerifier + commitments dependency, slashing flow for both violation types
- NEWT-641: Added blocks: NEWT-640 relation
- NEWT-630: Added blocks: NEWT-640 relation
✻ Sautéed for 2m 9s
❯ one more question. is PrivacySlasher purely for Prepare phase?
⏺ No — it covers both phases, but different violation types map to different phases:
┌──────────────────────────┬───────────┬─────────────────────────────────────────────────────────────────────────────────────────────┐
│ Violation │ Phase │ Why │
├──────────────────────────┼───────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ Wrong-keyshare (DLEQ │ Prepare │ Operators submit partial DH outputs during Prepare. The DLEQ proof accompanies each │
│ failure) │ phase │ partial. │
├──────────────────────────┼───────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ No-keyshare (liveness) │ Prepare │ Operator fails to respond during the Prepare phase timeout. │
│ │ phase │ │
├──────────────────────────┼───────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ Wrong BLS signature on │ Commit │ Operator BLS-signs a policy result that was computed from incorrect decrypted data. But │
│ privacy result │ phase │ this is already covered by the existing policy challenger (ZK proof) — not PrivacySlasher's │
│ │ │ scope. │
└──────────────────────────┴───────────┴─────────────────────────────────────────────────────────────────────────────────────────────┘
So practically, the PrivacySlasher targets Prepare phase misbehavior — that's where threshold decryption happens. The Commit phase
(policy evaluation + BLS signing) is already covered by the existing ChallengeVerifier.
There's one edge case worth noting: in the gateway-never-decrypts model, operators also decrypt locally during the Commit phase (they
receive partial DH outputs from Prepare and complete HPKE decryption). But that local decryption is deterministic given correct partials
— if the partials were valid (verified via DLEQ in Prepare), the Commit-phase decryption can't be wrong. So there's no separate
Commit-phase privacy violation to slash for.
The flow:
Prepare phase: partials + DLEQ proofs → PrivacySlasher can slash
Commit phase: policy eval + BLS sign → ChallengeVerifier can slash (existing)
Two independent slashing systems, one per phase. No overlap, no gaps.
✻ Cogitated for 30s
❯ Let's proceed to next steps
⏺ The natural next step is NEWT-641 (DleqVerifier library) — it's the first in the dependency chain, has no blockers, and is pure
cryptographic library code.
But before diving into implementation, there's a significant design challenge worth discussing: the curve mismatch. Our DLEQ proofs are
generated on Ristretto255 (Rust side), but EVM precompiles (EIP-196) operate on alt_bn128 (BN254). These are fundamentally different
curves — you can't directly verify a Ristretto255 DLEQ proof using BN254 precompiles.
This needs a /grill-me before implementation. The ticket mentions two approaches:
- Proof translation — operator generates on Ristretto255, translates for BN254 verification
- Dual-curve commitment — operator commits public key share on both curves during DKG
There may be a third option worth exploring: verifying the DLEQ proof in a ZK circuit (SP1) instead of via EVM precompiles — we already
have the SP1 infrastructure for policy challenges.
Want to start with /grill-me on the DleqVerifier curve strategy, or would you prefer to tackle something else first?
✻ Crunched for 31s
❯ yes /grill-me on the DleqVerifier curve strategy
⏺ Skill(grill-me)
⎿ Successfully loaded skill
⏺ Let me first understand the exact DLEQ proof structure we're working with on the Rust side.
Read 1 file (ctrl+o to expand)
⏺ Good — now I have the exact DLEQ structure. The proof operates on Ed25519 (Curve25519 in Edwards form), not Ristretto255 as I initially
said. The proof is:
- c = SHA-256("newton-dleq-v1" || G || A || H || B || R1 || R2) mod order
- z = k - c * secret
- Verification: zG + cA == R1 and zH + cB == R2
This is standard Schnorr-style DLEQ on Ed25519 (group order ~2^252). EVM precompiles operate on BN254 (alt_bn128, group order ~2^254).
These are different curves with different group orders, base points, and coordinate systems.
Question 1 of N: Which verification approach should we use?
Three options with fundamentally different trade-offs:
A) Native Solidity verification on Ed25519
Implement Ed25519 point arithmetic in pure Solidity — scalar multiplication via double-and-add, point addition, SHA-256 hash. No
precompiles needed (SHA-256 is available as a precompile at address 0x02). But Ed25519 scalar mul in Solidity is extremely expensive —
~1M+ gas per verification (no native precompile for Curve25519).
B) SP1 ZK circuit verification
The DLEQ verification runs inside an SP1 zkVM circuit (we already have SP1 infrastructure for sp1-rego policy challenges). The circuit
verifies the DLEQ proof off-chain and produces a succinct proof. On-chain, only the SP1 proof is verified (~300k gas via the SP1 verifier
contract). The operator's DLEQ proof correctness is reduced to "verify this ZK proof."
C) Dual-curve scheme
During DKG/PSS, operators commit their public key share on both Ed25519 (for HPKE threshold decryption) and BN254 (for on-chain DLEQ
verification). Operators generate a second DLEQ proof on BN254 alongside the Ed25519 one. The BN254 proof can be verified on-chain using
EIP-196/197 precompiles (~40k gas). But this doubles the DKG key material and requires operators to maintain two key shares.
My recommendation: Option B (SP1 ZK circuit). Reasons:
- We already have SP1 infrastructure (circuits/sp1-rego/) and the SP1 verifier deployed on-chain
- No changes to the DKG/PSS ceremony or key material
- The DLEQ verification is simple enough for an SP1 circuit (~100 lines)
- Gas cost is moderate (~300k) but acceptable for a slashing path (not hot path)
- The Ed25519 arithmetic runs natively inside SP1's RISC-V environment (no field translation)
- Aligns with the existing challenger pattern: challenger generates ZK proof, submits on-chain
A, B, or C?
✻ Churned for 51s
❯ How complex is C? "But this doubles the DKG key material and requires operators to maintain two key shares." --> what does this entail
in terms of complexity, error-prone-ness, reliability, efficiency?
⏺ Good question. Let me break down exactly what dual-curve entails:
What "Two Key Shares" Means Concretely
Currently each operator has:
- One secret scalar s_i (a 32-byte number)
- One public share pk_i = s_i * G_ed25519 (Ed25519 point)
With dual-curve, each operator would have:
- Same secret scalar s_i (it's just a number — curve-agnostic)
- Ed25519 public share: pk_i_ed = s_i * G_ed25519
- BN254 public share: pk_i_bn = s_i * G_bn254
The secret scalar is the same — you don't need two separate secrets. You just compute the public share on both curves. The scalar s_i is
~252 bits; both Ed25519 (order ~2^252) and BN254 (order ~2^254) can accommodate it.
Complexity Assessment
DKG ceremony changes:
- frost.rs round3: after computing s_i, also compute s_i * G_bn254 and store it
- keystore.rs: store both public shares in the encrypted keystore
- types.rs: add bn254_public_share: [u8; 64] to KeyShare (BN254 points are 64 bytes uncompressed)
- FROST package broadcast: include the BN254 public share in round3 output so all parties know it
~50 lines of code change in the DKG crate. Not trivial but not massive.
PSS refresh changes:
- refresh.rs: Feldman commitments are currently Ed25519 only. Would need BN254 commitments too (or derive from the same polynomial, since
coefficients are scalars) - Actually — the polynomial coefficients are scalars, and Feldman verification is share * G == sum(C_j * x^j). If we compute Feldman
commitments on both curves, verification works on both independently - ~30 lines additional in refresh.rs
Operator runtime changes:
- During Prepare phase: operator generates DLEQ proof on BN254 instead of (or in addition to) Ed25519
- Requires a BN254 scalar multiplication library in Rust (e.g., ark-bn254 or alloy's BN254 support)
- New dependency in the operator crate
On-chain changes:
- EpochRegistry or a companion contract stores BN254 public shares per operator per epoch
- Additional ~21k gas per operator per epoch for storage
- DleqVerifier uses EIP-196 ecMul + ecAdd: ~12k gas for verification (very cheap)
Error-Proneness
The risky part: the scalar s_i must be valid in both group orders. Ed25519 order is 2^252 + 27742... and BN254 order is 21888...
(~2^254). Since the BN254 order is larger, any valid Ed25519 scalar is also valid for BN254. This works safely — no modular reduction
needed.
But: if we ever change the DKG to generate scalars mod BN254's order (larger), they might not be valid Ed25519 scalars. This is a latent
footgun. The rule would be: always generate scalars mod Ed25519 order (the smaller one).
FROST interaction: FROST DKG on Ristretto255 generates scalars mod the Ristretto255 group order (same as Ed25519). These are guaranteed
valid for BN254. No issue with the current FROST implementation.
Reliability
- One more thing that can go wrong during DKG: BN254 key derivation failure
- One more field to serialize/deserialize in keystore
- One more commitment to verify during PSS refresh
- But all of these are deterministic — if the scalar is valid (which it is by construction), the BN254 operations can't fail
Efficiency
┌───────────────────────┬──────────────────────────────┬────────────────────────────────────────────────────┐
│ Aspect │ Current (Ed25519 only) │ Dual-Curve │
├───────────────────────┼──────────────────────────────┼────────────────────────────────────────────────────┤
│ DKG ceremony │ 3 rounds │ 3 rounds (same, +1 point computation per operator) │
├───────────────────────┼──────────────────────────────┼────────────────────────────────────────────────────┤
│ Keystore size │ ~200 bytes │ ~264 bytes (+64 bytes for BN254 point) │
├───────────────────────┼──────────────────────────────┼────────────────────────────────────────────────────┤
│ PSS refresh │ 1 set of Feldman commitments │ 2 sets (but can share polynomial) │
├───────────────────────┼──────────────────────────────┼────────────────────────────────────────────────────┤
│ Prepare phase │ 1 DLEQ proof (Ed25519) │ 1 DLEQ proof (BN254) — can drop Ed25519 proof │
├───────────────────────┼──────────────────────────────┼────────────────────────────────────────────────────┤
│ On-chain verification │ N/A │ ~12k gas (ecMul precompile) │
├───────────────────────┼──────────────────────────────┼────────────────────────────────────────────────────┤
│ On-chain storage │ 0 │ ~21k gas per operator per epoch │
└───────────────────────┴──────────────────────────────┴────────────────────────────────────────────────────┘
Compared to SP1 (Option B)
┌───────────────────────────┬───────────────────────────┬───────────────────────────────────┐
│ Aspect │ SP1 ZK Circuit │ Dual-Curve │
├───────────────────────────┼───────────────────────────┼───────────────────────────────────┤
│ On-chain verification gas │ ~300k (SP1 verifier) │ ~12k (ecMul precompile) │
├───────────────────────────┼───────────────────────────┼───────────────────────────────────┤
│ Proof generation time │ ~10-30s (SP1 prover) │ ~0ms (BN254 point mul is instant) │
├───────────────────────────┼───────────────────────────┼───────────────────────────────────┤
│ New dependencies │ None (SP1 already exists) │ ark-bn254 or similar in operator │
├───────────────────────────┼───────────────────────────┼───────────────────────────────────┤
│ DKG changes │ None │ ~80 lines │
├───────────────────────────┼───────────────────────────┼───────────────────────────────────┤
│ Slashing latency │ Slow (proof generation) │ Instant (precompile verification) │
├───────────────────────────┼───────────────────────────┼───────────────────────────────────┤
│ Complexity locus │ Circuit code │ DKG + keystore + refresh │
└───────────────────────────┴───────────────────────────┴───────────────────────────────────┘
My Updated Assessment
Dual-curve is more complex upfront (~80 lines across DKG/PSS/keystore) but dramatically cheaper and faster at verification time (12k gas
vs 300k, instant vs 10-30s proof generation). The complexity is contained in well-tested crypto code (scalar multiplication is
deterministic), not in a new ZK circuit.
SP1 is simpler to implement (no DKG changes) but slower and more expensive when actually slashing.
Revised recommendation: If slashing speed matters (a bad operator should be slashable immediately, not after waiting for SP1 proof
generation), dual-curve is better. If implementation speed matters and slashing is rare, SP1 is better.
What's your priority — fast implementation or fast slashing?
✻ Cooked for 45s
❯ reliability and least error-prone (technically and operationally). Least execution risk.
⏺ With that criteria, Option B (SP1 ZK circuit) is the clear winner.
Here's why:
Dual-curve (C) execution risks:
- Modifying the DKG ceremony is high-stakes — FROST is the most complex crypto in the codebase. Any bug in round3 BN254 key derivation
could produce invalid key material across all operators, requiring a fresh DKG ceremony to recover - PSS refresh with dual Feldman commitments doubles the verification surface — a bug means operators silently accumulate invalid BN254
shares across epochs - The "scalar valid in both group orders" invariant is a latent footgun that every future contributor must know about
- New ark-bn254 dependency in the operator crate — different serialization format, different point representation, new attack surface
- Every operator must upgrade simultaneously to support BN254 — partial rollout breaks the ceremony
SP1 (B) execution risks:
- The DLEQ verification logic is ~30 lines of Rust (same code as dleq.rs::verify(), compiled to RISC-V)
- No changes to DKG, PSS, keystore, or operator runtime
- No new dependencies — curve25519-dalek already works in SP1's RISC-V target
- The SP1 verifier contract is already deployed and battle-tested on-chain
- Slashing is rare (dispute path, not hot path) — 10-30s proof generation is acceptable
- If the SP1 circuit has a bug, fix and redeploy the circuit — no operator coordination needed
The decisive factor: with dual-curve, a bug corrupts live key material across all operators. With SP1, a bug means a slashing proof fails
to verify — fixable without touching the privacy system.
Let me continue the grill.
Question 2 of N: SP1 circuit scope — verify a single DLEQ proof, or verify the full commitment?
Two circuit designs:
A) Single DLEQ verification: The circuit takes one operator's DLEQ proof + public inputs (G, pk_i, H, D_i) and outputs pass/fail. The
challenger generates one SP1 proof per operator they want to slash.
B) Commitment batch verification: The circuit takes the full commitment from NEWT-630 (all operator partials + DLEQ proofs for a task)
and verifies all of them, outputting a bitmap of which operators' proofs are invalid. One SP1 proof covers the entire task.
My recommendation: Option A (single DLEQ). Simpler circuit, faster proof generation (one operator's proof vs all), and the PrivacySlasher
only needs to slash one operator at a time. Batch can be added later if needed.
A or B?
✻ Brewed for 30s
❯ B) Commitment batch verification: The circuit takes the full commitment from NEWT-630 (all operator partials + DLEQ proofs for a task)
and verifies all of them, outputting a bitmap of which operators' proofs are invalid. One SP1 proof covers the entire task. -->
similar to how we slashing in ChallengeVerifier
⏺ Exactly — matches the existing pattern in ChallengeLib.slashSigningOperators() which slashes all bad signers in one batch. Consistent
architecture.
Question 3 of N: What are the public inputs and outputs of the SP1 circuit?
The circuit needs to:
- Take in all DLEQ proofs for a task
- Verify each against the operator's committed public share
- Output which operators failed
Public inputs (committed on-chain, verifiable by anyone):
- decryptionCommitment (bytes32) — from NEWT-630, the on-chain commitment hash
- epochId (uint64) — which epoch's key shares to verify against
- taskId (bytes32) — which task's partial decryptions
Private inputs (provided by the challenger, not on-chain):
- Per-operator: (operator_index, pk_share, enc_point, partial_decryption, dleq_proof)
- These are fetched from EigenDA or the gateway's data availability layer
Public outputs (written to the proof, verified on-chain):
- invalidOperatorBitmap (uint256) — bitmask of which operators submitted invalid DLEQ proofs
- commitmentHash (bytes32) — re-computed from the private inputs, must match the on-chain decryptionCommitment
The on-chain PrivacySlasher then:
- Verifies the SP1 proof
- Checks commitmentHash == stored decryptionCommitment (binding)
- Slashes each operator flagged in invalidOperatorBitmap
My recommendation: This structure directly. The commitmentHash binding prevents the challenger from fabricating inputs — they must use
the same data the gateway committed to.
Does this input/output structure look right, or do you want to adjust?
✻ Cogitated for 36s
❯ looks right
⏺ Question 4 of N: Where does the challenger get the raw DLEQ proofs (private inputs)?
The on-chain commitment (NEWT-630) stores only a hash. The challenger needs the actual per-operator data to feed into the SP1 circuit.
Three options:
A) EigenDA: Gateway posts the raw DLEQ data to EigenDA alongside the on-chain commitment hash. EigenDA provides data availability
guarantees — the data is retrievable by anyone for the dispute window. Cost: ~$0.01 per blob. This is how EigenLayer-native systems
typically handle DA.
B) Gateway API endpoint: Gateway exposes a newt_getDecryptionProofs(taskId) RPC endpoint that returns the raw data. Simpler but relies on
the gateway being online and honest about serving the data. A compromised gateway could refuse to serve proofs for tasks where it
colluded.
C) Operator-direct: Challenger contacts each operator directly (via their socket address) to re-request their partial decryption + DLEQ
proof. Operators are incentivized to respond (if they're honest, they want the bad operator slashed). No single point of failure.
My recommendation: Option A (EigenDA). Reasons:
- The gateway is already submitting BLS data on-chain — adding a DA blob is a small incremental cost
- Removes the gateway as a trust dependency for data retrieval (matches our "trustless" goal)
- The challenger doesn't need to contact operators individually (they might be unreachable)
- EigenDA is the natural DA layer for an EigenLayer AVS
- If EigenDA is unavailable, Option C (operator-direct) serves as a fallback
A, B, or C?
looks right
⏺ Question 4 of N: Where does the challenger get the raw DLEQ proofs (private inputs)?
The on-chain commitment (NEWT-630) stores only a hash. The challenger needs the actual per-operator data to feed into the SP1 circuit.
Three options:
A) EigenDA: Gateway posts the raw DLEQ data to EigenDA alongside the on-chain commitment hash. EigenDA provides data availability
guarantees — the data is retrievable by anyone for the dispute window. Cost: ~$0.01 per blob. This is how EigenLayer-native systems
typically handle DA.
B) Gateway API endpoint: Gateway exposes a newt_getDecryptionProofs(taskId) RPC endpoint that returns the raw data. Simpler but relies on
the gateway being online and honest about serving the data. A compromised gateway could refuse to serve proofs for tasks where it
colluded.
C) Operator-direct: Challenger contacts each operator directly (via their socket address) to re-request their partial decryption + DLEQ
proof. Operators are incentivized to respond (if they're honest, they want the bad operator slashed). No single point of failure.
My recommendation: Option A (EigenDA). Reasons:
- The gateway is already submitting BLS data on-chain — adding a DA blob is a small incremental cost
- Removes the gateway as a trust dependency for data retrieval (matches our "trustless" goal)
- The challenger doesn't need to contact operators individually (they might be unreachable)
- EigenDA is the natural DA layer for an EigenLayer AVS
- If EigenDA is unavailable, Option C (operator-direct) serves as a fallback
A, B, or C?
❯ Agreed. EigenDA
⏺ Question 5 of N: Should the SP1 DLEQ circuit be a new circuit or extend the existing sp1-rego circuit?
Currently we have one SP1 circuit at circuits/sp1-rego/ that verifies Rego policy evaluation for the existing challenger. Two options:
A) New separate circuit (circuits/sp1-dleq/): Independent binary, independent proof key, independent verifier on-chain. Clean separation
— DLEQ verification has nothing to do with Rego policy evaluation.
B) Extend sp1-rego: Add a "mode" flag — the circuit either verifies Rego policy OR verifies DLEQ proofs depending on the input. Shares
the same SP1 verifier contract on-chain.
My recommendation: Option A (separate circuit). The two proofs verify completely different things — Rego policy correctness vs DLEQ
cryptographic validity. Combining them into one circuit adds complexity for no benefit. The SP1 verifier contract on-chain can verify
proofs from any circuit (it's parameterized by vkey), so there's no deployment overhead from having two circuits.
A or B?
❯ a
⏺ Question 6 of N: How does the challenger know something is wrong in the first place?
The challenger needs a trigger to investigate a task's DLEQ proofs. It won't speculatively verify every task's proofs via SP1 (too
expensive). Three detection models:
A) Verify all — optimistic: The challenger fetches DLEQ data from EigenDA for every privacy-enabled task and runs off-chain DLEQ
verification (cheap Rust code, no SP1). Only when it detects an invalid proof does it generate the expensive SP1 proof for on-chain
submission. This is the same pattern as the existing policy challenger — it re-evaluates every task, only challenges when results differ.
B) Selective — commitment-based: The challenger only investigates tasks where the gateway's on-chain commitment includes an operator that
was previously flagged as suspicious (reputation system). Reduces work but could miss first-time attackers.
C) Reactive — external report: Anyone can tip the challenger (via an RPC endpoint or on-chain bounty) that a specific task has bad
proofs. The challenger then investigates that specific task.
My recommendation: Option A (verify all, optimistic). Reasons:
- Off-chain Ed25519 DLEQ verification is ~microseconds per proof — trivially cheap for the challenger to verify every privacy task
- Matches the existing challenger architecture: verify everything off-chain, only go on-chain when a violation is found
- No reputation system or tipping infrastructure needed
- The SP1 proof generation (expensive part) only happens on the rare slashing path
The challenger flow becomes:
- Watch EpochRegistered and respondToTask events
- For each privacy-enabled task: fetch DLEQ data from EigenDA
- Run dleq::verify() in Rust for each operator's proof (~microseconds)
- If any fail: generate SP1 batch proof and submit to PrivacySlasher
A, B, or C?
❯ option A
⏺ Question 7 of N: Timing — when does the commitment get posted?
The DLEQ proofs happen during the Prepare phase, but the on-chain transaction happens at respondToTask (end of Commit phase). The
commitment needs to be included in the on-chain submission. Two options:
A) Embed in respondToTask: Add decryptionCommitment and operatorBitmap as new fields in TaskResponseMetadata. The commitment travels with
the existing BLS-aggregated response — one transaction, no extra on-chain call. The gateway computes the commitment during the Prepare
phase and carries it through to submission.
B) Separate transaction: Gateway submits registerDecryptionCommitment(taskId, commitment, bitmap) as a separate call after respondToTask.
Two transactions per privacy task. More gas, but doesn't require modifying the TaskManager interface.
My recommendation: Option A (embed in respondToTask). Modifying TaskResponseMetadata is a struct change that affects the ABI selector
(per lessons.md — "struct changes silently break all callers"), but NEWT-630 is a planned breaking change anyway. Embedding avoids the
extra transaction cost (~21k gas saved per task) and ensures the commitment is atomically bound to the task response — no window where a
response exists without a commitment.
The trade-off: changing TaskResponseMetadata means all clients (gateway, challenger) must update their ABI. But this is a coordinated
deploy — same as any contract upgrade.
A or B?
❯ option A
⏺ Question 8 of N: What happens for non-privacy tasks?
Not every task uses threshold decryption. For tasks without privacy data, there are no DLEQ proofs to commit. The decryptionCommitment
field in TaskResponseMetadata needs to handle this.
A) Zero-value sentinel: decryptionCommitment = bytes32(0) and operatorBitmap = 0 for non-privacy tasks. The PrivacySlasher rejects
challenges against tasks with zero commitment. Simple, no branching in the contract.
B) Boolean flag: Add bool hasDecryptionCommitment to TaskResponseMetadata. Explicit but uses an extra storage slot.
C) Omit from non-privacy tasks: Use a separate respondToTaskWithPrivacy() function that includes the commitment fields. Non-privacy tasks
use the existing respondToTask(). Two code paths on both the gateway and contract side.
My recommendation: Option A (zero-value sentinel). Reasons:
- bytes32(0) is the natural default for unset storage — no extra logic needed
- One function signature for all tasks — the gateway always passes the commitment (zero for non-privacy)
- PrivacySlasher checks commitment != bytes32(0) before processing — one line
- No bool storage overhead, no function signature forking
A, B, or C?
❯ A
⏺ Question 9 of N: Challenge window — same 100 blocks as policy challenges, or different?
The existing ChallengeVerifier uses taskChallengeWindowBlock (configurable, time-normalized to ~360s across chains). Privacy slashing
could share the same window or have its own.
A) Same window: Both policy and privacy challenges must be submitted within taskChallengeWindowBlock of respondToTask. One window to
reason about. After it expires, the task is finalized — no privacy or policy challenges.
B) Separate window: Privacy challenges have their own privacyChallengeWindowBlock, potentially longer (DLEQ data needs to be fetched from
EigenDA + SP1 proof generated, which takes longer than a ZK policy re-evaluation). But adds configuration complexity.
My recommendation: Option A (same window). The SP1 proof generation for DLEQ (~10-30s) is comparable to or faster than the SP1 Rego
proof. The challenger runs off-chain DLEQ verification immediately (microseconds), only generates the SP1 proof when a violation is
found. The 360s window is more than enough for: detect violation (~1s) + generate SP1 proof (~30s) + submit transaction (~12s). Sharing
the window simplifies the mental model — after the window closes, the task is fully finalized.
A or B?
❯ A
⏺ I believe we've covered all the major design branches. Let me summarize.
DleqVerifier / Privacy Slashing Design Summary (9 decisions)
┌─────┬────────────────┬────────────────────────────────────────────────┬────────────────────────────────────────────────────────────┐
│ # │ Decision │ Choice │ Reasoning │
├─────┼────────────────┼────────────────────────────────────────────────┼────────────────────────────────────────────────────────────┤
│ 1 │ Verification │ SP1 ZK circuit │ Least execution risk — no DKG/PSS changes, no new operator │
│ │ approach │ │ dependencies, bug in circuit doesn't corrupt key material │
├─────┼────────────────┼────────────────────────────────────────────────┼────────────────────────────────────────────────────────────┤
│ │ │ │ Verifies all operator DLEQ proofs for a task, outputs │
│ 2 │ Circuit scope │ Batch verification │ invalid bitmap. Matches ChallengeLib.slashSigningOperators │
│ │ │ │ pattern │
│ │ │ │ ChallengeLib.slashSigningOperators pattern │
├─────┼────────────────┼──────────────────────────────────────────────────┼──────────────────────────────────────────────────────────┤
│ │ │ Public: commitmentHash + epochId + taskId. │ │
│ 3 │ Circuit I/O │ Output: invalidOperatorBitmap + re-computed │ Commitment binding prevents fabricated inputs │
│ │ │ commitmentHash for binding │ │
├─────┼────────────────┼──────────────────────────────────────────────────┼──────────────────────────────────────────────────────────┤
│ │ Data │ │ Gateway posts raw DLEQ data alongside on-chain │
│ 4 │ availability │ EigenDA │ commitment hash. Trustless retrieval, natural for │
│ │ │ │ EigenLayer AVS │
├─────┼────────────────┼──────────────────────────────────────────────────┼──────────────────────────────────────────────────────────┤
│ 5 │ Circuit │ New circuits/sp1-dleq/ │ Separate from sp1-rego — different proof types, clean │
│ │ organization │ │ separation │
├─────┼────────────────┼──────────────────────────────────────────────────┼──────────────────────────────────────────────────────────┤
│ │ Detection │ │ Challenger runs off-chain DLEQ verify (~microseconds) on │
│ 6 │ model │ Verify all (optimistic) │ every privacy task, only generates SP1 proof when │
│ │ │ │ violation found │
├─────┼────────────────┼──────────────────────────────────────────────────┼──────────────────────────────────────────────────────────┤
│ 7 │ Commitment │ Embed in respondToTask │ New fields in TaskResponseMetadata — atomic, one │
│ │ timing │ │ transaction, no commitment gap │
├─────┼────────────────┼──────────────────────────────────────────────────┼──────────────────────────────────────────────────────────┤
│ 8 │ Non-privacy │ Zero-value sentinel │ decryptionCommitment = bytes32(0), PrivacySlasher │
│ │ tasks │ │ rejects challenges on zero commitment │
├─────┼────────────────┼──────────────────────────────────────────────────┼──────────────────────────────────────────────────────────┤
│ 9 │ Challenge │ Same as policy (taskChallengeWindowBlock) │ ~360s is sufficient for detect + SP1 prove + submit. One │
│ │ window │ │ window simplifies finalization │
└─────┴────────────────┴──────────────────────────────────────────────────┴──────────────────────────────────────────────────────────┘
Implementation order refined:
- NEWT-641: sp1-dleq circuit (replaces Solidity DleqVerifier — SP1 verifier already deployed)
- NEWT-630: On-chain commitments in TaskResponseMetadata + EigenDA blob posting
- NEWT-640: PrivacySlasher contract — verifies SP1 proof, slashes via invalidOperatorBitmap
- Challenger extension: Fetch from EigenDA, off-chain DLEQ verify, SP1 proof on violation
Want me to save this as a plan and update the Linear tickets with the SP1 approach?
Remaining Backlog (Phase 2D Solidity, unchanged):
┌──────────┬────────────────────────────────────┬─────────┐
│ Ticket │ Title │ Status │
├──────────┼────────────────────────────────────┼─────────┤
│ NEWT-629 │ Privacy slashing conditions (spec) │ Backlog │
├──────────┼────────────────────────────────────┼─────────┤
│ NEWT-640 │ PrivacySlasher contract │ Backlog │
├──────────┼────────────────────────────────────┼─────────┤
│ NEWT-641 │ DleqVerifier library │ Backlog │
├──────────┼────────────────────────────────────┼─────────┤
│ NEWT-630 │ On-chain aggregation commitments │ Backlog │
└──────────┴────────────────────────────────────┴─────────┘
Far future (Phase 3/4): MPC/ZK, mainnet deployment, ecosystem — all Backlog.
No manual status updates needed — the GitHub integration auto-marked NEWT-639 as Done when PR #544 merged. The remaining backlog tickets
are correctly in Backlog status, unblocked and ready for when we pick them up.
✻ Cooked for 1m 6s
❯ analyze the importance and implication of phase 2D. What is our current state in terms of newton privacy layer, and what does phase 2D
bring and why important?
⏺ ★ Insight ─────────────────────────────────────
This is a critical architectural question. Phase 2D is the economic enforcement layer — without it, the privacy system relies entirely on
operator honesty. Let me analyze what we have, what's missing, and why it matters.
─────────────────────────────────────────────────
Current State: Privacy Layer without Economic Enforcement
What we have (Phases 1→2C, all merged):
What's missing (Phase 2D):
What Phase 2D Brings
NEWT-629: Slashing Conditions (spec)
Defines three provable violation types:
┌─────────────────────┬──────────────────────────────────────────────┬──────────────────────────────────────────────────┬───────────┐
│ Violation │ Detection │ Proof Type │ Severity │
├─────────────────────┼──────────────────────────────────────────────┼──────────────────────────────────────────────────┼───────────┤
│ Wrong-keyshare │ Operator submits an invalid partial DH │ DLEQ proof failure (deterministic, │ 10% slash │
│ │ output │ cryptographic) │ │
├─────────────────────┼──────────────────────────────────────────────┼──────────────────────────────────────────────────┼───────────┤
│ No-keyshare │ Operator absent during threshold decryption │ Quorum attestation (t-of-n operators attest to │ 1-2% │
│ │ window │ absence) │ slash │
├─────────────────────┼──────────────────────────────────────────────┼──────────────────────────────────────────────────┼───────────┤
│ Premature │ Operator participates in unauthorized task │ On-chain authorization check against │ 10% slash │
│ decryption │ │ EpochRegistry │ │
└─────────────────────┴──────────────────────────────────────────────┴──────────────────────────────────────────────────┴───────────┘
NEWT-641: DleqVerifier Library
Solidity library using EIP-196 precompiles (bn128 alt_bn128) to verify DLEQ proofs on-chain:
Prove: log_G(PK_i) == log_{enc}(D_i)
This is the cryptographic primitive that makes "wrong-keyshare" provable on-chain. Without it, you can only detect wrong shares off-chain
(which is what the Rust DLEQ code already does), but you can't slash for it.
NEWT-640: PrivacySlasher Contract
Calls EigenLayer's InstantSlasher.fulfillSlashingRequest() when a violation is proven. References EpochRegistry.isEpochActive() and
getEpoch() to validate that the slashing claim is against a valid epoch.
Why Phase 2D Matters
Without Phase 2D, the privacy system is trust-based, not trust-minimized.
Right now, operators can:
zero economic consequences. They can keep doing it.
participate, the task succeeds, but the attack is free.
The practical impact:
(needs t operators) with no slashing risk.
— without slashing, privacy is outside that model.
How Critical is Phase 2D?
For testnet/staging: Not blocking. The current system works correctly — DLEQ proofs verify off-chain, bad operators are excluded from
aggregation, and the permissioned operator set provides practical security.
For mainnet with real money: Phase 2D is a prerequisite. You can't launch privacy features with restaked ETH security claims unless
misbehavior has economic consequences. The DLEQ proof is the key differentiator from other threshold encryption systems — it makes
wrong-keyshare provable on-chain, not just detectable.
For the DAO transition: The PrivacySlasher is permissionless (anyone can submit a slashing proof). This aligns with decentralization —
you don't need a trusted entity to police operators.
Recommendation
Phase 2D is necessary for production but not urgent for the current testnet milestone. The natural order:
The DLEQ verification is the hard part — it requires mapping the Ristretto255 DLEQ proof to bn128 pairing-friendly operations on-chain,
which involves non-trivial curve arithmetic. That's why NEWT-641 exists as a separate library.