Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions examples/lighter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ The funds-custody outflow rate limit is a separate, independently deployable ass
drain past a rolling-window TVL fraction during normal operation; the breaker stands down in desert
mode so it never blocks the mass exits the escape hatch exists to enable. Deploy one instance per
watched ERC-20 token.
- **Experimental batch-proof verification** (`LighterBatchProofAssertion`) — re-verifies the
one-public-input gnark BN254 PLONK proof attached to each `verifyBatch` call using the executor's
compiled-in VK registry. It is bound to the configured Lighter proxy and fails closed on malformed
calldata, an unknown VK, or a rejected proof.

## Files

Expand All @@ -46,6 +50,7 @@ The funds-custody outflow rate limit is a separate, independently deployable ass
- `src/LighterBridgeHelpers.sol` — fork-aware reads.
- `src/LighterBridgeAssertion.sol` — state-machine + desert-mode bundle (trigger wiring + invariants).
- `src/LighterOutflowCircuitBreaker.sol` — standalone rolling-window collateral outflow breaker.
- `src/LighterBatchProofAssertion.sol` — Experimental registry-backed gnark PLONK batch-proof guard.
- `test/LighterBridgeAssertion.t.sol` — honest + malicious behavior per state-machine invariant.
- `test/LighterOutflowCircuitBreaker.t.sol` — breaker decision logic + constructor guards.

Expand Down
57 changes: 57 additions & 0 deletions examples/lighter/src/LighterBatchProofAssertion.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {Assertion} from "credible-std/Assertion.sol";
import {PhEvm} from "credible-std/PhEvm.sol";
import {AssertionSpec} from "credible-std/SpecRecorder.sol";

/// @title LighterBatchProofAssertion
/// @notice Independently verifies Lighter's submitted gnark PLONK batch proof.
/// @dev This experimental assertion is deliberately bound to one L1 proxy and
/// one compiled-in verifier key. It validates the matching call context,
/// then checks the batch commitment and proof extracted from verifyBatch.
contract LighterBatchProofAssertion is Assertion {
bytes4 internal constant VERIFY_BATCH_SELECTOR = 0x23ff50e1;
bytes32 internal constant LIGHTER_GNARK_PLONK_VK_ID =
0x7856ff107f35077ed23aa1cbd1a4ec95204585dc675e0d3632231c49e48109d0;

address public immutable lighterProxy;

/// @notice `StoredBatchInfo` has eleven static ABI words in the pinned Lighter ABI.
/// @dev Words are retained verbatim because this assertion only needs the final commitment.
struct StoredBatchInfo {
bytes32[11] words;
}

constructor(address lighterProxy_) {
require(lighterProxy_ != address(0), "LighterBatchProof: zero proxy");
lighterProxy = lighterProxy_;
registerAssertionSpec(AssertionSpec.Experimental);
}

function triggers() external view override {
registerFnCallTrigger(this.assertBatchProof.selector, VERIFY_BATCH_SELECTOR);
}

/// @notice Verifies the proof submitted with the exact triggering batch.
/// @dev Rejects calls from any adopter other than the configured Lighter proxy,
/// malformed verifyBatch calldata, and algebraically invalid proofs.
function assertBatchProof() external view {
require(ph.getAssertionAdopter() == lighterProxy, "LighterBatchProof: wrong adopter");
PhEvm.TriggerContext memory ctx = ph.context();
bytes memory input = ph.callinputAt(ctx.callStart);
require(input.length >= 4, "LighterBatchProof: malformed calldata");
require(bytes4(input) == VERIFY_BATCH_SELECTOR, "LighterBatchProof: wrong selector");

(StoredBatchInfo memory batch, bytes memory proof) = abi.decode(_withoutSelector(input), (StoredBatchInfo, bytes));
require(
ph.verifyGnarkPlonkProof(proof, batch.words[10], LIGHTER_GNARK_PLONK_VK_ID),
"LighterBatchProof: invalid proof"
);
}

function _withoutSelector(bytes memory input) private pure returns (bytes memory args) {
args = new bytes(input.length - 4);
for (uint256 i; i < args.length; ++i) args[i] = input[i + 4];
}
}
18 changes: 18 additions & 0 deletions src/PhEvm.sol
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,24 @@ interface PhEvm {
/// @return ctx The inflow rate-of-change context for the current invocation.
function inflowRate() external view returns (FlowRateContext memory ctx);

// ---------------------------------------------------------------
// Experimental: gnark BN254 PLONK verification
// ---------------------------------------------------------------

/// @notice Verifies a registry-backed gnark BN254 PLONK proof.
/// @dev EXPERIMENTAL: requires registerAssertionSpec(AssertionSpec.Experimental).
/// The executor accepts only compiled-in verifier-key identifiers; callers
/// cannot provide verifier-key bytes. This currently supports the demonstrated
/// one-public-input gnark BN254 PLONK layout.
/// @param proof The Solidity-encoded gnark proof.
/// @param commitment The sole public input before BN254 scalar reduction.
/// @param verifierKeyId The immutable registry identifier for the verifier key.
/// @return valid False when the proof is well-formed but algebraically invalid.
function verifyGnarkPlonkProof(bytes calldata proof, bytes32 commitment, bytes32 verifierKeyId)
external
view
returns (bool valid);

// ---------------------------------------------------------------
// V2: Anomaly detection
// ---------------------------------------------------------------
Expand Down
Loading