From 496db8b9dfcfd7f6653ef06f01e53bcf047cce7a Mon Sep 17 00:00:00 2001 From: igneum-labs <337424239+igneum-labs@users.noreply.github.com> Date: Thu, 8 Oct 2026 11:02:40 +0000 Subject: [PATCH] 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 --- contracts/bridge/foundry.toml | 4 ++-- contracts/bridge/test/Verifier.t.sol | 18 ++++++++++++++++++ contracts/bridge/test/vectors/.gitignore | 4 ++++ contracts/bridge/test/vectors/gen.mjs | 11 ++++++++++- docs/contracts/sepolia.json | 22 ++++++++++++++++++++++ 5 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 docs/contracts/sepolia.json diff --git a/contracts/bridge/foundry.toml b/contracts/bridge/foundry.toml index f23f953a6..1ec6c147f 100644 --- a/contracts/bridge/foundry.toml +++ b/contracts/bridge/foundry.toml @@ -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 diff --git a/contracts/bridge/test/Verifier.t.sol b/contracts/bridge/test/Verifier.t.sol index 0018d7547..a66ce7d85 100644 --- a/contracts/bridge/test/Verifier.t.sol +++ b/contracts/bridge/test/Verifier.t.sol @@ -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"); + } } diff --git a/contracts/bridge/test/vectors/.gitignore b/contracts/bridge/test/vectors/.gitignore index 7741964bf..339972c19 100644 --- a/contracts/bridge/test/vectors/.gitignore +++ b/contracts/bridge/test/vectors/.gitignore @@ -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 diff --git a/contracts/bridge/test/vectors/gen.mjs b/contracts/bridge/test/vectors/gen.mjs index b654cc70b..893bcc5b8 100644 --- a/contracts/bridge/test/vectors/gen.mjs +++ b/contracts/bridge/test/vectors/gen.mjs @@ -3,6 +3,8 @@ // of them over the Devnet 3 vote message, a small account trie with proofs // node gen.mjs table > 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 > 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 > 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 | chain '); 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 | account | chain '); process.exit(2); } diff --git a/docs/contracts/sepolia.json b/docs/contracts/sepolia.json new file mode 100644 index 000000000..db98b5618 --- /dev/null +++ b/docs/contracts/sepolia.json @@ -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" +}