eth_getProof는 지정한 주소의 nonce, balance, codeHash, storageHash와 이를 블록 stateRoot에 연결하는 accountProof, 요청한 slot별 storageProof를 반환합니다. 검증자는 먼저 신뢰할 수 있는 블록 헤더와 정확한 stateRoot를 확보한 뒤, 주소를 Keccak-256한 account trie 경로에서 proof node를 검증합니다. 검증된 account 안의 storageRoot를 기준으로 각 32바이트 slot key를 Keccak-256한 경로와 RLP 값을 확인합니다. RPC가 함께 준 balance나 value만 비교하는 것으로는 충분하지 않으며, proof가 고정된 block hash와 헤더 root에 실제로 연결되는지 확인해야 합니다.
증명은 신뢰한 stateRoot에서 시작합니다
eth_getProof 응답에는 address, balance, nonce, codeHash, storageHash, accountProof와 storageProof가 들어갑니다. 이 필드들이 한 JSON에 같이 있다는 사실은 진위를 보장하지 않습니다. 검증자는 대상 block hash의 header를 신뢰 가능한 경로로 얻고 header의 stateRoot를 루트로 accountProof node를 순서대로 확인해야 합니다.
같은 RPC endpoint에서 header와 proof를 모두 받으면 전송 오류 탐지에는 도움이 되지만 악의적 provider를 독립 검증하는 신뢰 모델은 완성되지 않습니다. consensus 검증 light client, 자체 node 또는 별도 합의된 header source가 필요합니다. **증명보다 먼저 블록 헤더의 신뢰 경계를 정합니다**.
머클 증명은 값을 믿으라는 서명이 아니라, 이미 신뢰한 루트에 그 값이 연결된다는 경로입니다.
JOBCOIN 해설

그림은 이 주제의 공통 개념을 단순화한 설명입니다. 아래 항목에서 이 글의 구체적인 조건과 예외를 함께 읽어보세요.
- 증명은 신뢰한 stateRoot에서 시작합니다
eth_getProof 응답에는 address, balance, nonce, codeHash, storageHash, accountProof와 storageProof가 들어갑니다.
- accountProof에서 계정 네 필드를 복원합니다
Ethereum state trie의 account key는 20바이트 주소 자체가 아니라 주소의 Keccak-256 hash입니다.
- storageProof는 검증된 storageRoot에서 다시 시작합니다
각 storageProof 항목은 요청한 key, value와 proof node 배열을 제공합니다.
AI로 제작한 개념도 · 실제 가격·거래 내역·통계가 아닙니다.
accountProof에서 계정 네 필드를 복원합니다
Ethereum state trie의 account key는 20바이트 주소 자체가 아니라 주소의 Keccak-256 hash입니다. proof node를 hash 또는 inline reference 규칙에 따라 따라가면 해당 key의 leaf value에 도달합니다. 계정 값은 RLP로 인코딩된 nonce, balance, storageRoot, codeHash 구조이므로 이를 decode하고 응답 필드와 대조합니다.
Contract bytecode는 state trie 안에 직접 들어가지 않고 account의 codeHash가 code의 Keccak-256을 가리킵니다. 코드까지 검증하려면 같은 block에서 eth_getCode로 bytes를 받아 hash를 비교합니다. Externally owned account는 일반적인 빈 코드 hash를 갖지만 계정 존재 여부와 nonce·balance는 proof로 별도 판정합니다.
| 필드 | 검증 기준 | 자주 하는 오류 |
|---|---|---|
| accountProof | header stateRoot | 응답 balance만 비교 |
| storageHash | 검증된 account 값 | RPC 값을 새 root로 신뢰 |
| storageProof.key | 요청 slot key | mapping 논리 key와 혼동 |
| storageProof.value | storage trie leaf RLP | 32바이트 word 그대로 가정 |
| codeHash | code bytes의 Keccak-256 | code를 trie leaf로 가정 |
storageProof는 검증된 storageRoot에서 다시 시작합니다
각 storageProof 항목은 요청한 key, value와 proof node 배열을 제공합니다. 먼저 accountProof에서 얻은 storageRoot를 기준으로 삼습니다. 요청 slot은 32바이트로 정규화하고 그 바이트의 Keccak-256을 storage trie 경로로 사용합니다. Leaf에서 나온 RLP scalar를 decode해 정수 값으로 복원한 뒤 응답 value와 기대 ABI 표현을 대조합니다.
Solidity mapping의 논리 slot 계산과 trie path 계산은 두 단계입니다. 예를 들어 mapping(address=>uint)의 element slot은 ABI 방식으로 key와 mapping base slot을 32바이트씩 이어 Keccak-256해 구합니다. 그 결과인 storage slot key를 다시 Keccak-256한 값이 storage trie의 경로가 됩니다. **mapping slot hash와 trie key hash를 한 번으로 합치지 않습니다**.
- 대상 chainId와 block hash를 먼저 고정합니다.
- 헤더를 검증하고 stateRoot를 신뢰 기준점으로 둡니다.
- 주소 hash 경로로 accountProof를 검증합니다.
- RLP 계정에서 storageRoot와 codeHash를 복원합니다.
- Solidity layout에 따라 요청할 logical slot을 계산합니다.
- slot hash 경로로 storageProof와 부재를 검증합니다.
- decode한 값과 애플리케이션 기대 타입을 대조합니다.
부재도 정상적인 증명 결과입니다
Merkle Patricia Trie proof는 leaf 도달만 보여 주지 않습니다. 경로 중 branch에 필요한 child가 없거나 extension·leaf의 compact path가 요청 key와 달라 더 진행할 수 없음을 보이면 key 부재도 검증할 수 있습니다. 따라서 미생성 account나 zero storage slot은 ‘node가 오류를 냈다’가 아니라 검증 가능한 absence일 수 있습니다.
다만 값 0과 계정 자체 부재를 업무 의미에서 동일하게 취급할지는 별도 문제입니다. 생성되지 않은 계정, 존재하지만 zero balance인 계정, selfdestruct나 protocol 변화가 관련된 계정 상태를 구분하려면 nonce, codeHash와 proof 결과를 함께 해석합니다. **빈 값도 proof 경로를 끝까지 검사합니다**.
블록을 고정하지 않으면 proof와 기대값이 엇갈립니다
balance를 latest에서 읽고 잠시 뒤 proof도 latest에서 요청하면 두 호출 사이 새 블록이 들어와 값이 달라질 수 있습니다. 대상 header hash를 먼저 정하고 동일한 block identifier로 eth_getProof, eth_getCode와 관련 조회를 수행합니다. 지원 endpoint에서는 EIP-1898 blockHash 기반 요청과 canonical 요구를 검토합니다.
Finalized header를 기준으로 검증하면 재구성 위험은 낮지만 최신성은 줄어듭니다. Latest proof는 암호학적으로 해당 latest header와 연결돼도 그 header 자체가 나중에 canonical chain에서 빠질 수 있습니다. 증명 유효성과 체인 확정성은 서로 다른 검증 축입니다.
라이브러리 구현은 trie 인코딩 경계를 시험합니다
Proof node는 RLP encoded trie node bytes이며 branch는 17개 항목, extension과 leaf는 두 항목 구조를 사용합니다. Hex-prefix compact encoding의 leaf terminator와 홀수·짝수 nibble flag, 32바이트보다 짧은 node의 inline reference 규칙을 직접 구현할 때 경계 오류가 잦습니다. 검증이 잘 된 trie 라이브러리를 쓰고 원문 test vector를 추가합니다.
테스트에는 존재 account, 부재 account, zero slot, non-zero mapping slot, 짧은 inline child, 잘못된 node hash, 다른 block의 root를 포함합니다. Proof 배열 순서를 바꾸거나 한 node의 nibble을 수정했을 때 반드시 실패해야 합니다. 성공 사례만으로는 provider가 준 값을 그대로 통과시키는 가짜 verifier를 찾기 어렵습니다.
운영 기록은 재현 가능한 검증 묶음으로 남깁니다
감사 레코드에는 chainId, block number·hash, header stateRoot, address, 원래 요청 slot, proof bytes, verifier 버전과 최종 decode 값을 보관합니다. Dynamic array나 mapping이면 Solidity compiler storage layout과 key 계산 입력도 남겨 나중에 같은 slot을 재현합니다.
Endpoint를 교체해 같은 block hash의 proof를 받아도 node 배열 구성은 달라질 수 있지만 동일한 root와 값으로 검증돼야 합니다. 실패할 때는 header 불일치, account path, storage path, RLP decode, ABI decode 단계를 구분해 기록해야 원인을 provider 문제로 성급히 단정하지 않습니다.
자주 묻는 질문
eth_getProof 응답만 있으면 신뢰 없이 상태를 검증할 수 있나요?
아닙니다. 별도로 신뢰하거나 합의 검증한 블록 헤더의 stateRoot가 증명의 기준점으로 필요합니다.
Solidity mapping slot을 한 번 hash하면 proof key가 되나요?
Mapping element의 logical slot을 계산한 뒤 그 32바이트 slot key를 다시 Keccak-256해야 storage trie 경로가 됩니다.
값이 0이면 proof node가 없어도 되나요?
0 또는 부재 주장도 trie 경로가 해당 key를 포함하지 않음을 증명하는 유효한 proof node들이 필요합니다.
더 깊이 읽기
본문에서 다룬 개념과 확인 절차를 다음 글에서 이어서 살펴보세요.



