Skip to content

Commit 08c5e9c

Browse files
decofedanrobinson
andauthored
fix: lower zone chain ID base to stay within EIP-2294 safe range (#361)
* fix: lower zone chain ID base to stay within EIP-2294 safe range Co-authored-by: Daniel Robinson <[email protected]> Amp-Thread-ID: https://ampcode.com/threads/T-019d54ae-a5ca-74b9-bd73-f71e8add4cad * fix: wrap zone chain IDs to stay within safe range Use modular arithmetic so zone chain IDs never exceed 2^31 - 1. Sequencers are responsible for ensuring their derived chain ID does not collide with any active chain. Co-authored-by: Daniel Robinson <[email protected]> Amp-Thread-ID: https://ampcode.com/threads/T-019d54ae-a5ca-74b9-bd73-f71e8add4cad --------- Co-authored-by: Daniel Robinson <[email protected]>
1 parent dce314f commit 08c5e9c

4 files changed

Lines changed: 71 additions & 10 deletions

File tree

crates/primitives/src/constants.rs

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,67 @@ pub const ZONE_OUTBOX_LAST_BATCH_INDEX_SLOT: U256 = {
8383
U256::from_le_bytes(le)
8484
};
8585

86-
/// Base offset for deriving zone chain IDs: `4217000000 + zone_id`.
86+
/// Base offset for deriving **mainnet** zone chain IDs.
8787
///
8888
/// Each zone gets a unique EIP-155 chain ID derived from its on-chain zone ID
89-
/// assigned by the `ZoneFactory` contract. The prefix `4217` comes from the
90-
/// Tempo L1 chain ID.
91-
pub const ZONE_CHAIN_ID_BASE: u64 = 4_217_000_000;
89+
/// assigned by the `ZoneFactory` contract:
90+
///
91+
/// ```text
92+
/// chain_id = ZONE_CHAIN_ID_BASE + (zone_id % ZONE_CHAIN_ID_RANGE)
93+
/// ```
94+
///
95+
/// # Range safety
96+
///
97+
/// EIP-2294 and ENSIP-11 reserve bit 31 (`0x8000_0000`) for coin-type flags,
98+
/// making chain IDs ≥ 2^31 (2,147,483,647) unsafe in parts of the ecosystem
99+
/// (ENS multi-chain address resolution, some JavaScript tooling that uses
100+
/// 32-bit integers, etc.).
101+
///
102+
/// The ranges are chosen so that both mainnet and testnet zones stay well below
103+
/// that limit while remaining non-overlapping:
104+
///
105+
/// | Network | Base | Range size | Chain ID span |
106+
/// |----------|-----------------|-------------------|---------------------------------------|
107+
/// | Mainnet | `421_700_000` | `1_002_610_000` | `421_700_000 .. 1_424_310_000` |
108+
/// | Testnet | `1_424_310_000` | `723_173_648` | `1_424_310_000 .. 2_147_483_648` |
109+
///
110+
/// Zone IDs wrap around via modular arithmetic so that chain IDs never leave
111+
/// their designated range, even with arbitrarily large zone IDs. Because
112+
/// wrapping can reuse a chain ID that was previously assigned to a different
113+
/// zone, it is the responsibility of the sequencer to ensure — after deploying
114+
/// a zone — that the derived chain ID does not correspond to any active chain,
115+
/// including any zone that has previously used that chain ID.
116+
pub const ZONE_CHAIN_ID_BASE: u64 = 421_700_000;
117+
118+
/// Number of distinct mainnet zone chain IDs before wrapping.
119+
///
120+
/// Equal to `ZONE_CHAIN_ID_BASE_TESTNET - ZONE_CHAIN_ID_BASE`, keeping the
121+
/// mainnet range strictly below the testnet range.
122+
pub const ZONE_CHAIN_ID_RANGE: u64 = 1_002_610_000;
92123

93-
/// Derives the EIP-155 chain ID for a zone from its on-chain zone ID.
124+
/// Base offset for deriving **testnet** (Moderato) zone chain IDs.
125+
///
126+
/// See [`ZONE_CHAIN_ID_BASE`] for range-safety rationale.
127+
pub const ZONE_CHAIN_ID_BASE_TESTNET: u64 = 1_424_310_000;
128+
129+
/// Number of distinct testnet zone chain IDs before wrapping.
130+
///
131+
/// Equal to `2^31 - ZONE_CHAIN_ID_BASE_TESTNET`, keeping the testnet range
132+
/// strictly below the EIP-2294 safe ceiling.
133+
pub const ZONE_CHAIN_ID_RANGE_TESTNET: u64 = 723_173_648;
134+
135+
/// Derives the EIP-155 chain ID for a **mainnet** zone from its on-chain zone ID.
136+
///
137+
/// Wraps via modulo so the result always falls in
138+
/// `[ZONE_CHAIN_ID_BASE, ZONE_CHAIN_ID_BASE + ZONE_CHAIN_ID_RANGE)`.
94139
pub const fn zone_chain_id(zone_id: u32) -> u64 {
95-
ZONE_CHAIN_ID_BASE + zone_id as u64
140+
ZONE_CHAIN_ID_BASE + (zone_id as u64 % ZONE_CHAIN_ID_RANGE)
141+
}
142+
143+
/// Derives the EIP-155 chain ID for a **testnet** zone from its on-chain zone ID.
144+
///
145+
/// Wraps via modulo so the result always falls in
146+
/// `[ZONE_CHAIN_ID_BASE_TESTNET, ZONE_CHAIN_ID_BASE_TESTNET + ZONE_CHAIN_ID_RANGE_TESTNET)`.
147+
pub const fn zone_chain_id_testnet(zone_id: u32) -> u64 {
148+
ZONE_CHAIN_ID_BASE_TESTNET + (zone_id as u64 % ZONE_CHAIN_ID_RANGE_TESTNET)
96149
}

docs/ZONES.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@ The xtasks use this Moderato `ZoneFactory` as their built-in default: `create-zo
492492
| `--l1.rpc-url` | (required) | L1 WebSocket RPC URL |
493493
| `--l1.portal-address` | (from zone.json) | ZonePortal contract on L1 |
494494
| `--l1.genesis-block-number` | (from zone.json) | L1 block when the zone was created |
495-
| `--zone.id` | 0 | Zone ID from ZoneFactory (for private RPC auth). The zone's chain ID is derived as `4217000000 + zone_id`. |
495+
| `--zone.id` | 0 | Zone ID from ZoneFactory (for private RPC auth). The zone's chain ID is derived as `421700000 + (zone_id % 1002610000)` (mainnet) or `1424310000 + (zone_id % 723173648)` (testnet). |
496496
| `--sequencer-key` | (optional) | Sequencer private key for block production |
497497
| `--block.interval-ms` | 250 | Block building interval |
498498
| `--zone.batch-interval-secs` | 60 | Max seconds to accumulate zone blocks before submitting a batch to L1 |

docs/specs/privacy/overview.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,18 @@ The factory deploys a `ZonePortal` that escrows enabled tokens on Tempo and a `Z
6262
Each zone has a unique EIP-155 chain ID derived deterministically from its on-chain zone ID:
6363

6464
```
65-
chain_id = 4217000000 + zone_id
65+
# Mainnet zones (range: 421,700,000 – 1,424,309,999)
66+
chain_id = 421700000 + (zone_id % 1002610000)
67+
68+
# Testnet / Moderato zones (range: 1,424,310,000 – 2,147,483,647)
69+
chain_id = 1424310000 + (zone_id % 723173648)
6670
```
6771

68-
The prefix `4217` corresponds to the Tempo L1 chain ID. This ensures replay protection between zones — a transaction signed for one zone cannot be replayed on another. The chain ID is set in the zone's genesis configuration during creation and validated by the zone node at startup.
72+
Both ranges stay below `2^31 − 1` (2,147,483,647), which is the safe ceiling defined by EIP-2294 and ENSIP-11. Chain IDs above this limit can break ENS multi-chain address resolution and JavaScript tooling that uses 32-bit integers. Zone IDs wrap via modular arithmetic so that the derived chain ID always stays within the designated range.
73+
74+
Because wrapping can reuse a chain ID that was previously assigned to a different zone, it is the responsibility of the sequencer to ensure — after deploying a zone — that the derived chain ID does not correspond to any active chain, including any zone that has previously used that chain ID.
75+
76+
This ensures replay protection between zones — a transaction signed for one zone cannot be replayed on another. The chain ID is set in the zone's genesis configuration during creation and validated by the zone node at startup.
6977

7078
### Token management
7179

zone-genesis/genesis.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"config": {
3-
"chainId": 4217000001,
3+
"chainId": 421700001,
44
"homesteadBlock": 0,
55
"daoForkSupport": false,
66
"eip150Block": 0,

0 commit comments

Comments
 (0)