How Containment Chamber separates Ethereum networks and remains fork-agnostic through per-request fork_info
This page explains how Containment Chamber handles Ethereum network identity and fork versions.
Fork-Agnostic Signing
Section titled “Fork-Agnostic Signing”Key insight: the remote signer does NOT need to know about specific forks.
Consensus signing requests carry fork_info:
{ "type": "ATTESTATION", "fork_info": { "fork": { "previous_version": "0x04000000", "current_version": "0x05000000", "epoch": "0" }, "genesis_validators_root": "0x4b363db94e286120d76eb905340fcd4e8a81b2147cfb21a2..." }, "attestation": { ... }}This means:
- The validator client handles fork transitions
- The signer just uses the provided fork version
- No code changes needed for new forks
- Existing and future forks work as long as they use a signing request type and domain that Containment Chamber implements
Network Support
Section titled “Network Support”Built-in Networks
Section titled “Built-in Networks”The --network flag accepts exactly three values:
| Network | Genesis fork version | Genesis validators root |
|---|---|---|
| Mainnet | 0x00000000 |
0x4b363db94e286120d76eb905340fcd4e8a81b2147cfb21a2beec26d710dce35a |
| Sepolia | 0x90000069 |
0xd8ea171f3c94aea21ebc42a1ed61052acf3f9209c00e4efbaaddac09ed9b8078 |
| Hoodi | 0x10000910 |
0x212f13fc4df078b6cb7db228f1c8307566dcecf900867401a92023d7ba99cb5f |
Use the value that matches your validator client:
# Mainnetcontainment-chamber server --network mainnet --key-sources-filesystem-paths ./keys
# Sepolia testnetcontainment-chamber server --network sepolia --key-sources-filesystem-paths ./keys
# Hoodi testnetcontainment-chamber server --network hoodi --key-sources-filesystem-paths ./keysNetwork Separation
Section titled “Network Separation”Networks are identified by genesis_validators_root (GVR). The signer validates the GVR in each fork_info against the configured --network. A mismatch returns HTTP 400 Bad Request and stops before key lookup, anti-slashing, or signing.
This is a safety guardrail. If your validator client is accidentally pointed at the wrong signer, or if a request carries the wrong network’s GVR, the signer refuses outright rather than producing a signature that would fail on-chain.
How Fork Compatibility Works
Section titled “How Fork Compatibility Works”Domain computation
Section titled “Domain computation”pub fn compute_domain( domain_type: [u8; 4], // Operation type (attestation, block, etc.) fork_version: [u8; 4], // From fork_info.fork.current_version genesis_validators_root: Hash256, // From fork_info.genesis_validators_root) -> [u8; 32]The 32-byte domain is split into two regions:
- Domain type (4 bytes) — which operation (
DOMAIN_BEACON_ATTESTER,DOMAIN_BEACON_PROPOSER, …) - Fork data root (28 bytes) — the leading 28 bytes of
tree_hash_root({current_version, genesis_validators_root}). Fork version and genesis root are SSZ-hashed together; they do not occupy independent byte ranges.
This means:
- Same attestation data on different forks → different signatures
- Same attestation data on different networks → different signatures
- Cryptographic guarantee against cross-fork/cross-network replay
See BLS Signing Internals for the full domain computation.
Example: same data, different contexts
Section titled “Example: same data, different contexts”// Mainnet Deneblet domain_mainnet = compute_domain( DOMAIN_BEACON_ATTESTER, [0x04, 0x00, 0x00, 0x00], // Deneb mainnet_genesis_root,);
// Sepolia Deneblet domain_sepolia = compute_domain( DOMAIN_BEACON_ATTESTER, [0x04, 0x00, 0x00, 0x00], // Also Deneb sepolia_genesis_root, // Different!);
// domain_mainnet != domain_sepolia// Therefore: signing_root differs, signature differsSupported Signing Flows
Section titled “Supported Signing Flows”Consensus operations
Section titled “Consensus operations”RANDAO_REVEALBLOCK_V2ATTESTATIONAGGREGATION_SLOTAGGREGATE_AND_PROOFVOLUNTARY_EXITSYNC_COMMITTEE_MESSAGESYNC_COMMITTEE_SELECTION_PROOFSYNC_COMMITTEE_CONTRIBUTION_AND_PROOF
Builder API (fork-independent)
Section titled “Builder API (fork-independent)”VALIDATOR_REGISTRATION
The VALIDATOR_REGISTRATION operation is special:
// Always uses fork version [0,0,0,0]// Always uses zero genesis_validators_root// This makes it independent from consensus fork_infolet domain = compute_domain( DOMAIN_APPLICATION_BUILDER, [0u8; 4], Hash256::zero(),);Adding Support for New Forks
Section titled “Adding Support for New Forks”You don’t need to change the signer.
When a new Ethereum fork is released:
- Update your validator client (Lighthouse, Prysm, etc.)
- The validator client sends the new fork version in
fork_info - The signer uses it in domain computation after the GVR check
- Everything works
The only time you’d need to change the signer is if:
- A new domain type is added (new
DOMAIN_*constant) - A completely new signing flow is introduced
Testing Coverage
Section titled “Testing Coverage”The test suite verifies the two important boundaries:
tests/signing_vectors.rs::test_different_domains_different_signatureschecks that changing the domain changes the signing root and signature.src/signer/ethereum/signer.rs::test_gvr_guard_wrong_gvr_returns_network_mismatchchecks that a consensus signing request carrying the wrong GVR is rejected.src/signer/ethereum/signer.rs::test_gvr_guard_validator_registration_bypasseschecks thatVALIDATOR_REGISTRATIONbypasses the GVR guard because it has nofork_info.
Best Practices
Section titled “Best Practices”For operators
Section titled “For operators”- Set
--networkcorrectly: usemainnet,hoodi, orsepoliato match your validator client’s network. The signer rejects signing requests from mismatched networks with HTTP 400. - Trust
fork_infofrom the validator client: the request carries fork context — you don’t need to configure fork versions. - No manual fork handling: don’t try to override fork versions.
For developers
Section titled “For developers”- Don’t hardcode fork versions: always use values from
fork_info - Test with multiple fork versions: verify signatures differ
- Handle
ValidatorRegistrationspecially: it uses genesis fork version
Troubleshooting
Section titled “Troubleshooting”“Signature verification failed”
Section titled ““Signature verification failed””Possible causes:
- Fork version mismatch between validator client and consensus layer
- Wrong
genesis_validators_root(testnet key on mainnet) - Domain type mismatch
Check: log the fork_info from requests and compare with expected values.
“Unknown fork version”
Section titled ““Unknown fork version””The signer doesn’t validate fork versions — it just uses them. If you see this error from the consensus layer, update your validator client.
Cross-network signing
Section titled “Cross-network signing”This is prevented by design. A signature created with mainnet’s genesis_validators_root will never verify on Sepolia, and vice versa. The domain computation ensures cryptographic separation.
Next Steps
Section titled “Next Steps”- BLS Signing Internals — how the domain is computed and why it matters
- Validator Clients — configure your client with the correct
--networkflag - Configuration Reference –network option and other signing settings

