Bridge: the verifier live on Sepolia at 0xAf74f3F512081291D663Bb1d6b6d37E99e37D744 with the Devnet 3 voter table (29 keys, 7,164 weight at index 2127; docs/contracts/sepolia.json), the Osaka test EVM, the account vector mode and the Devnet 3 account test (skips until the eth_getProof node serves)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-08 11:02:40 +00:00
parent 6cbb890b39
commit 496db8b9df
5 changed files with 56 additions and 3 deletions

View file

@ -6,8 +6,8 @@ out = "out"
libs = []
solc_version = "0.8.28"
# The verifier calls the BLS12-381 precompiles of EIP-2537 (live on Sepolia and mainnet since Pectra), so the test EVM
# runs the Prague rules.
evm_version = "prague"
# runs the Osaka rules, the fork Sepolia is on (EIP-7883 modexp pricing counts here).
evm_version = "osaka"
optimizer = true
optimizer_runs = 200
via_ir = true

View file

@ -123,4 +123,22 @@ contract VerifierTest {
require(signed == vm.parseJsonUint(j, ".signed_weight") && total == vm.parseJsonUint(j, ".total_weight"), "chain weights");
require(ok, "the chain's certificate does not verify");
}
/// One Devnet 3 account under a real Devnet 3 state root (test/vectors/dn3-account.json from a node's eth_getProof;
/// skipped when the file is absent). The root is the chain's own; the link from a certified checkpoint to that root
/// is the gap the doc names.
function test_devnet3_account_balance_proven() public {
string memory j;
try vm.readFile("test/vectors/dn3-account.json") returns (string memory s) {
j = s;
} catch {
return;
}
(bool exists, uint256 nonce, uint256 balance, bytes32 sroot, bytes32 chash) = v.verifyAccount(
vm.parseJsonBytes32(j, ".state_root"), vm.parseJsonAddress(j, ".account"), vm.parseJsonBytesArray(j, ".account_proof")
);
require(exists, "the Devnet 3 account is absent under its root");
require(nonce == vm.parseJsonUint(j, ".account_nonce") && balance == vm.parseJsonUint(j, ".account_balance"), "Devnet 3 account fields");
require(sroot == vm.parseJsonBytes32(j, ".account_storage_root") && chash == vm.parseJsonBytes32(j, ".account_code_hash"), "Devnet 3 account roots");
}
}

View file

@ -3,3 +3,7 @@ synthetic.json
chain.json
checkpoint-live.json
checkpoint-dn3.json
weights-dn3.json
checkpoints-dn3.json
dn3-table.json
dn3-account.json

View file

@ -3,6 +3,8 @@
// of them over the Devnet 3 vote message, a small account trie with proofs
// node gen.mjs table <weights.json> > dn3-table.json the voter table alone from a node's igneum_getFinalityWeights answer
// (voters in the canonical order: sorted by key hash; weight = blocks)
// node gen.mjs account <getProof.json> <stateRoot> <blockNumber> > dn3-account.json an eth_getProof answer from a Devnet 3
// node (the reference-apps lane's reader) as the suite's real-root account vector
// node gen.mjs chain <checkpoint.json> > dn3.json a real certificate as igneum.network/api/checkpoint?source=dn3 serves it:
// the voter table and the aggregate signature decompressed to the
// precompiles' encodings (the verifier checks the same bytes the node signed)
@ -100,8 +102,15 @@ function table(file) {
return { chain_id: process.env.IGNEUM_CHAIN_ID || CHAIN_ID, dst: DST, index: Number(BigInt(r.checkpointIndex)), checkpoint_at_index: '0x' + String(r.checkpointHash).replace(/^0x/, ''), keys: voters.map(v => v.key), weights: voters.map(v => v.weight), total_weight: total, total_weight_node: Number(BigInt(r.totalWeight)), voters: voters.length };
}
function account(file, stateRoot, blockNumber) {
const d = JSON.parse(readFileSync(file, 'utf8'));
const r = d.result || d;
return { chain_id: CHAIN_ID, state_root: stateRoot, block_number: Number(blockNumber), account: r.address, account_nonce: BigInt(r.nonce).toString(), account_balance: BigInt(r.balance).toString(), account_storage_root: r.storageHash, account_code_hash: r.codeHash, account_proof: r.accountProof };
}
const mode = process.argv[2];
if (mode === 'synthetic') synthetic().then(v => console.log(JSON.stringify(v, null, 1)));
else if (mode === 'chain') console.log(JSON.stringify(chain(process.argv[3]), null, 1));
else if (mode === 'table') console.log(JSON.stringify(table(process.argv[3]), null, 1));
else { console.error('usage: gen.mjs synthetic | table <weights.json> | chain <checkpoint.json>'); process.exit(2); }
else if (mode === 'account') console.log(JSON.stringify(account(process.argv[3], process.argv[4], process.argv[5]), null, 1));
else { console.error('usage: gen.mjs synthetic | table <weights.json> | account <getProof.json> <stateRoot> <blockNumber> | chain <checkpoint.json>'); process.exit(2); }

View file

@ -0,0 +1,22 @@
{
"network": "ethereum-sepolia",
"chain_id": 11155111,
"note": "The Igneum light-client bridge primitive: a verifier for Devnet 3 finality certificates and Ethereum account proofs. Devnet 3, test tokens, no value; Sepolia, test ether, no value. What it proves and what it does not: docs/bridge/light-client-bridge.md.",
"rpc": "https://ethereum-sepolia-rpc.publicnode.com",
"deployed_at": "2026-10-08T12:36:00Z",
"deployer": "0x0C896A191D6b76c37d704454b35B2D61276b7471",
"source": "contracts/bridge (Foundry, solc 0.8.28, evm_version osaka, via-IR, optimizer 200 runs)",
"contracts": {
"IgneumCertificateVerifier": {
"address": "0xAf74f3F512081291D663Bb1d6b6d37E99e37D744",
"create_tx": "0x55351cbbccda0f78311c22feb1174d99c5d8d9fe3488cb60ff8656f65007e079",
"create_block": 11869731,
"igneum_chain_id": "igneum-devnet-3",
"table": { "tx": "0x6ec1045dbc0abe981944e9e15987089d3f0cdc2e8e467f688b7b0c63e907b26b", "block": 11869732, "index": 2127, "voters": 29, "total_weight": 7164, "table_id": "0xabfcc2bc54ca92ec506beee8a44dced17f15cf1e7c518a6221bf2819b0d1cbd6", "read_from": "igneum_getFinalityWeights on a Devnet 3 node at checkpoint index 2127 (hash 0xcb21b52c…), 12:1x UTC 8 October 2026" },
"final_checkpoints": "none yet: the first Devnet 3 certificate is submitted when /api/checkpoint serves dn3_live_certificates",
"interface": "IIgneumCertificateVerifier in contracts/bridge/src/IgneumCertificateVerifier.sol: chainId(), tableId(), verifyCertificate(uint64 index, bytes32 checkpoint, bytes bitmap, bytes signature) view returns (bool ok, uint256 signedWeight, uint256 totalWeight), submitCertificate(...), finalCheckpoint(uint64) view returns (bytes32), isFinal(bytes32) view returns (bool), verifyAccount(bytes32 stateRoot, address account, bytes[] proof) pure returns (bool exists, uint256 nonce, uint256 balance, bytes32 storageRoot, bytes32 codeHash)"
}
},
"first_deploy_note": "A first creation at 0xD7dd375E14F92A4CE4782040325189e6d6E394b6 (tx 0x4db10565…, block 11869633) ran out of gas at Foundry's Prague-spec estimate of 3,323,884; Sepolia runs Osaka pricing (EIP-7883 modexp), so the test EVM and the deploy now use osaka and the RPC's own estimate. That address holds no code.",
"tests": "contracts/bridge/test/Verifier.t.sol: 9 of 9 green on build-3 (Foundry, osaka), 12:3x UTC 8 October 2026"
}