國慶日來介紹 eth_getProof
這個 JSON RPC API,它可以向節點請求某地址在某區塊的資料證明。今天實作使用 ethereumjs 來驗證 Merkle Patricia Trie 的 proof,最後我們要確保 proof 的 stateRoot 跟我們要驗證的 blockNumber 的 stateRoot 是一致的。
EIP-1186: RPC-Method to get Merkle Proofs - eth_getProof (2018)
使用 ethers v6 呼叫 JSON RPC API:
const proof = await provider.send('eth_getProof', [address, [], toBeHex(blockNumber)])
address
: 帳戶或合約地址。storageKeys
: 用於合約的 storage slot 鍵值對的「鍵」blockNumber
: ex. 'latest'address
: 同上accountProof
: RLP 序列化後的節點陣列,第一個是 stateRoot,要將 RLP 編碼做雜湊才能得到 stateRoot hash。balance
: 帳戶在該 blockNumber 的餘額。codeHash
: 如果是合約,就是合約 bytecode 的雜湊值,如果是 EOA,就是空字串的雜湊值,等於 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
。nonce
storageHash
storageProof
key
value
proof
這段程式碼要證明 0xd78B5013757Ea4A7841811eF770711e6248dC282
在 Sepolia blockNumber 6848860
有 1.639982991581458239 ETH。
import { Trie } from '@ethereumjs/trie'
import { bytesToHex, hexToBytes } from '@ethereumjs/util'
import { decodeRlp, JsonRpcProvider, keccak256, toBeHex } from 'ethers'
// prove 0xd78B5013757Ea4A7841811eF770711e6248dC282 has 1639982991581458239 wei in block 6848860
const provider = new JsonRpcProvider(process.env.sepolia)
const address = process.env.dev!
const blockNumber = 6848860
const block = await provider.getBlock(blockNumber)
if (!block) throw new Error('block not found')
const stateRoot = block.stateRoot
console.log(`stateRoot at ${blockNumber}`, stateRoot)
const proof = await provider.send('eth_getProof', [address, [], toBeHex(blockNumber)])
console.log(proof)
// ==================================== verify proof ========================================
const addressHash = keccak256(address)
const uint8ArrayProof = proof.accountProof.map((node: string) => hexToBytes(node))
const value = await Trie.verifyProof(hexToBytes(addressHash), uint8ArrayProof)
const decoded = decodeRlp(bytesToHex(value!))
console.log('decoded', decoded)
// [nonce, balance, storageHash, codeHash]
// the hash of the accountProof's first node is the stateRoot
if (keccak256(proof.accountProof[0]) === stateRoot) {
console.log('verified')
}
今日實作的過程在 hex, bytes, RLP, keccak256 之間的轉換花了好多時間才弄懂,對以太坊協議層和 bytes 的操作真的不太熟,驗證失敗了好多次,好在最後有驗證成功 :)