在之前的博文中,我们介绍了如何使用状态证明来验证账户余额。但我们没有谈及如何获得证明,以及在哪里查询某个账户的证明。
这就是这篇博文的内容。
在这篇博文中,我将介绍EIP1186,这是从全节点查询账户证明或合同存储证明的标准。
用例是,作为一个轻量级的客户,他们没有机会接触到整个区块链数据。
- 你可以为你的以太坊账户余额询问Merkle证明,并自己验证正确性。
- 你可以为你的ERC20代币账户余额询问Merkle证明。例如,USDC账户余额。
- 你可以要求为你的ERC721代币所有权提供Merkle证明。例如,为了证明你是CryptoKitties NFT的所有者。
你可以从不受信任的全节点查询这些Merkle证明,因为你可以自己轻松地验证证明。如果Merkle Proof是无效的,那么这意味着你的Ethereum账户或与证明一起发送的存储状态的结果是无效的。
好的。让我们从EIP标准开始 - EIP1186。
EIP-1186
EIP-1186定义了一个方法eth_getProof来查询Merkle证明。
eth_getProof方法需要一个要查询账户状态的地址和一个区块号码。它返回一个账户对象,其中包含账户状态数据,包括余额、nonce、storageHash和codeHash。更重要的是,它还返回accountProof以验证账户状态数据。
eth_getProof也支持智能合约账户,它提供了一个额外的参数(存储密钥)来查询存储状态的证明。我们不会在这篇博文中介绍,但会在以后的文章中介绍。
例子
让我们通过一个例子来查询一个以太坊账户的证明,并验证该证明。
首先,让我们随机挑选一个以太坊账户。
In order to query the state proof, we need to find a full node that supports EIP-1186 standard.
There are a few services that produce APIs for the eth_getProof RPC method call, such as Infura and Alchemy.
In this example, I will just use Alchemy’s API to query the proof.
Again, the reason I pick Alchemy is not because I trust them more than other services. As a light client, we don’t need to trust the result from any full node, because we are able to verify the result ourselves. We will only accept the result if the proof can pass our validation.
Now let’s use Alchemy’s API to query the proof. We will need an API key for using Alchemy API. Once we have an API key, we can make the following HTTP request:
Among the parameters, the 0xB856af30B938B6f52e5BfF365675F358CD52F91B is the account address, and 0xE35B21 is the block number 14900001 in hex format.
This HTTP request returns the following JSON response:
The response shows the account balance is 0x4ef05b2fe9d8c8 , which is
0.02221932261997588 Ether at block 14900001. The nonce is 0x10 , which is 16.
The codeHash 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is the keccak256 hash of empty string, which means the account is a user account. User account has no code.
Since user account has no extra storage, the storage hash is a constant string: 0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421, which is keccak256 hash of an empty Merkle trie.
Now we’ve got the account state from the response, should we trust it? No, we don’t want to blindly trust that. We can verify it with the account proof that comes along with the account state data.
Next, let’s verify the account state with the account proof.
Get State Root Hash
The account proof is a list of hex string. They are essentially the encoded Merkle trie nodes.
To verify the proof, we need one additional piece of data. That is the state root hash of the world state trie at that particular block.
The state root trie hash is included in the block header of each block. And the data in the block header can be verified with its fields, including difficulty and nonce, which are known as the source of Proof of Work.
For simplicity, I will look up the state root hash from Etherscan, since it’s a different source than Alchemy where we query the account data state and its proof from.
Etherscan shows that the StateRootHash for this block is):
Now, with the proof from Alchemy and state root hash from Etherscan, we have everything needed for verifying the proof.
Verify Merkle Proof
In order to verify the Merkle proof with the state root hash, we need to use the Merkle Patricia Trie data structure.
I’ve previously introduced how Merkle Patricia Trie works, and implemented a simplified version. I also introduced How to verify the account state with the proof using the Merkle trie.
Here, we will continue using the simplified implementation of the Merkle trie to verify the proof that we queried from eth_getProof call.
First, let’s define the type for the eth_getProofcall’s response:
Next, let’s save the HTTP response into a JSON, and parse the data into a data structure:
Let’s create a trie, then we add the proof to the trie:
The proof is essentially the encoded trie nodes on the path from the root node of the trie to the leaf node that stores the account state data.
Now the trie is constructed, we call the VerifyProof method of the trie and pass in the account address and the state root hash.
The state root hash is the start point from which we traverse through the trie, and the account address is the actual path to traverse through the trie.
If we are able to reach a leaf node in the end, then the proof is valid.
If we didn’t reach a LeafNode, then the proof is invalid.
In the example, when we run the test case, it passes. So it means the account state is valid.
You can find the source code of the example here.
Summary
In this blog post, we introduced EIP-1186 — the standard of querying proof from full node for light client to verify the account state.
We walked through an example to verify the account state of an Ethereum account on mainnet.
But how to verify the storage state in a smart contract? For instance the USDC balance of an Ethereum account.
To understand that, we need to know how Ethereum structures the smart contract storage. I will cover it in my next blog post.
Stay tuned.
References
https://ethereum.github.io/yellowpaper/paper.pdf
https://eips.ethereum.org/EIPS/eip-1186
GitHub - vocdoni/storage-proofs-eth-go: Golang library and utility for extracting erc20 token…
A storage trie is where all of the contract data lives. Each Ethereum account has its own storage trie. A 256-bit hash…
github.com
More
If you are interested in learning more, check out my blog post series: