A playbook for simulating and testing malicious validator behaviors in Arbitrum chains for educational and security testing purposes.
The Malicious Validator playbook allows you to run two self-contained demos that demonstrate different attack scenarios on Arbitrum Orbit chains:
- Malicious Mint Demo — A malicious sequencer uses a custom ArbMinter precompile to mint ETH out of thin air, then withdraws it to the parent chain.
- Challenge Demo (BoLD) — A malicious validator creates invalid state via ReadInboxMessage bit-flip, and an honest validator challenges it using the BoLD (Bounded Liquidity Delay) protocol.
Important: Both demos are fully self-contained — they automatically deploy a fresh chain, apply configurations, and start nodes. You do not need to manually deploy a chain or start nodes beforehand. Even if you already deployed a chain, the demo will redeploy and overwrite it. Simply enter Chain Mode, go straight to the playbook, and let the program handle everything.
- Chain Mode only
? Select an action:
❯ Run Malicious Mint Demo — Full cross-chain malicious mint flow
Run Challenge Demo — Honest vs malicious validator challenge
──────────────
Start Malicious Node — Launch a misconfigured validator
Configure Malicious Validator — Modify node-config.json
View Rollup Status — Check on-chain rollup state
View Node Status — Show running node info
Stop All Nodes — Terminate all running nodes
──────────────
← Back to Playbook List
The top two options are the main demos (recommended). The remaining options are for manual/advanced usage.
Demonstrates a full attack scenario: a malicious sequencer uses a custom ArbMinter precompile to mint ETH out of thin air, then withdraws it to the parent chain.
- Start the CLI (
yarn start) - Select Chain Mode
- Select Playbook List → Malicious Validator
- Select Run Malicious Mint Demo
- Choose whether to use default deposit amounts or customize them
- Confirm to start — the demo handles everything automatically
| Step | Action | Details |
|---|---|---|
| 1 | Deploy chain | Fresh chain with confirmPeriodBlocks=16 for quick withdrawal |
| 2 | Apply config & start node | Applies malicious mint config flags (malicious-mint, deleting-bold-strategy, fast validator, fast batch poster, block validator with local WASM) and starts node with jasonwan123/nitro-node-malicious-arbminter image |
| 3 | Check bridge balance | Record initial bridge balance on parent chain |
| 4 | Deposit ETH | Main account deposits ETH from parent chain to child chain |
| 5 | Fund Hacker | Generate a random Hacker account, fund it with gas, and deposit to L2 |
| 6 | Wait for funds | Wait for deposits to arrive on child chain (up to ~120s) |
| 7 | Malicious mint | Hacker calls ArbMinter.mintBalanceTo at 0x74 to mint ETH (invalid state) |
| 8 | Withdraw | Hacker calls ArbSys.withdrawEth at 0x64 to withdraw minted funds to L1 |
| 9 | Start monitor | Start rollup assertion monitor (tracks AssertionCreated/AssertionConfirmed) |
| 10 | Wait for withdrawal | Poll until withdrawal is confirmed and execute on L1 |
| 11 | Final check | Compare bridge balance before and after |
| Parameter | Value |
|---|---|
mainDepositAmount |
0.005 ETH |
hackerDepositAmount |
0.001 ETH |
hackerFundingAmount |
0.002 ETH (gas) |
confirmPeriodBlocks |
16 |
jasonwan123/nitro-node-malicious-arbminter — Nitro node with built-in ArbMinter precompile at 0x74.
Demonstrates the BoLD (Bounded Liquidity Delay) challenge protocol: a malicious validator creates invalid state via ReadInboxMessage bit-flip, and an honest validator challenges it.
- Build the malicious validator image (see Prerequisites below)
- Start the CLI (
yarn start) - Select Chain Mode
- Select Playbook List → Malicious Validator
- Select Run Challenge Demo
- Choose whether to use default configuration or customize (max wait time, delayed message count, L2 tx count)
- Confirm to start — the demo handles everything automatically
| Step | Action | Details |
|---|---|---|
| 1 | Deploy chain | Fresh chain with confirmPeriodBlocks=1600, convert ETH to WETH, approve EdgeChallengeManager for both validators |
| 2 | Generate configs | Create node-config-malicious.json (with validation.wasm.malicious-mode=true) and node-config-honest.json (staker in MakeNodes mode) |
| 3 | Start malicious node | Launch malicious validator with locally-built nitro-malicious-playbook-challenge-demo:latest image |
| 4 | Start honest node | Launch honest validator with offchainlabs/nitro-node:v3.9.6-91bf578-validator image |
| 5 | Send delayed messages | Send L1 deposit transactions to create agreed execution prefix |
| 6 | Wait for batch posting | Wait for delayed sequencer + batch poster to process messages (~60s) |
| 7 | Send L2 transactions | Send transactions on child chain to trigger state divergence |
| 8 | Monitor challenge | Watch for BoLD challenge events until EdgeConfirmedByOneStepProof |
The BoLD challenge protocol uses three levels of bisection:
| Level | Name | Description |
|---|---|---|
| 0 | Block | Block-level bisection |
| 1 | BigStep | Big-step bisection |
| 2 | SmallStep | Small-step bisection (resolves via one-step proof) |
The challenge monitor tracks three core events:
- EdgeAdded — New edge created at a bisection level
- EdgeBisected — Edge split into lower/upper children
- EdgeConfirmedByOneStepProof — Terminal event: honest validator wins
| Parameter | Value |
|---|---|
maxWaitSeconds |
10800 (3 hours) |
pollIntervalMs |
3000 (3 seconds) |
delayedMessageCount |
10 |
delayedMessageAmount |
0.0001 ETH per message |
childChainTxCount |
5 |
confirmPeriodBlocks |
1600 |
| Role | Image |
|---|---|
| Malicious validator | nitro-malicious-playbook-challenge-demo:latest (locally built, see below) |
| Honest validator | offchainlabs/nitro-node:v3.9.6-91bf578-validator |
- The demo will check for the malicious image before starting. If it's not found, you'll be prompted to build it first.
- The challenge process typically takes 30–60 minutes, but may take longer depending on network conditions.
- It is recommended to use a third-party or paid RPC provider (e.g., Alchemy, Infura) to avoid rate limiting from public endpoints.
- Docker must be running
PARENT_CHAIN_RPCandMAIN_PRIVATE_KEYset in.env
The Challenge Demo requires a locally-built malicious validator Docker image. You must build it before running the demo.
# 1. Clone the repo with submodules
git clone --depth 1 \
--branch malicious-validator-readInbox \
--recurse-submodules --shallow-submodules \
https://github.com/OffchainLabs/nitro.git nitro-remote-build
cd nitro-remote-build
# 2. Build the Docker image (~15-20 minutes)
docker build -f Dockerfile.malicious -t nitro-malicious-remote-test .
# 3. Tag it for the playbook
docker tag nitro-malicious-remote-test nitro-malicious-playbook-challenge-demo:latestNote: The build compiles Rust (prover, jit), Solidity contracts, and Go binaries — it takes ~15–20 minutes. You only need to do this once.
You can verify the build succeeded by checking the module root hash:
docker run --rm --entrypoint cat nitro-malicious-playbook-challenge-demo:latest \
/home/user/target/machines/latest/module-root.txt
# Expected: 0xc2c02df561d4afaf9a1d6785f70098ec3874765c638e3cb6dbe8d3c83333e14cThe playbook manages these configuration files (generated automatically):
| File | Purpose |
|---|---|
node-config.json |
Base node config (generated at chain deployment) |
node-config-malicious.json |
Malicious validator config (Challenge Demo) |
node-config-honest.json |
Honest validator config (Challenge Demo) |
Available config overlay types:
malicious-mint— Enable malicious ArbMinter precompile modemalicious-validator— Skip block validationincorrect-wasm-validator— Use wrong WASM modulefast-batch-poster— Fast batch posting intervaldeleting-bold-strategy— BoLD deletion strategy
- Check Docker is running
- Verify the required Docker image exists (
docker images) - Ensure ports are not already in use
- Check container logs:
docker logs <container-name>
- The demo checks for
nitro-malicious-playbook-challenge-demo:latestbefore starting - Build it following the Prerequisites section
- Both demos require a clean on-chain state (no prior assertions beyond genesis)
- The demos auto-detect dirty state and will redeploy if needed
- If nodes are running, stop them first via Stop All Nodes before retrying
- The Challenge Demo polls on-chain logs frequently
- Use a paid RPC provider (Alchemy, Infura) instead of public endpoints
- Main README — General project documentation and quick start
- Developer Guide — Architecture and how to add new playbooks