In previous blog post, we introduced how to verify account balance using state proof. But we didn’t talk about how to get the proof and where to query the proof for a given account.
That is what this blog post is about.
In this blog post, I will introduce EIP1186, which is the standard for querying account proof or contract storage proof from a full node.
The use cases are, as a light client, who don’t have access to the entire blockchain data,
You can ask for Merkle Proof for your Ethereum account balance and verify the correctness yourself.
You can ask for Merkle Proof for your ERC20 token account balance. For instance, USDC account balance.
You can ask for Merkle Proof for your ERC721 token ownership. For instance, to prove you are the owner of a CryptoKitties NFT.
You can query these Merkle Proof from untrusted full node, because you can verify the proof yourself easily. If the Merkle Proof is invalid, then it means the result of your Ethereum account or storage state that is sent along with the proof is invalid.
OK. Let’s start with the EIP standard — EIP1186.
EIP-1186
EIP-1186 defines a method eth_getProof to query for Merkle proof:
The eth_getProof method takes an address to query account state for and a block number. It returns an account object, which contains the account state data including balance, nonce, storageHash and codeHash. More importantly, it also return the accountProof for verifying the account state data.
eth_getProof also support for smart contract account, it provides an additional parameter (storage key) to query proof for the storage state. We won’t cover it in this blog post, but will cover in future post.
Example
Let’s walk through an example to query the proof for an Ethereum account, and verify the proof.
First, let’s pick an random Ethereum account:
0xB856af30B938B6f52e5BfF365675F358CD52F91B
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:
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):
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:
type AccountStateResult struct {
Nonce hexutil.Uint64 `json:"nonce"`
Balance *hexutil.Big `json:"balance"`
StorageHash common.Hash `json:"storageHash"`
CodeHash common.Hash `json:"codeHash"`
AccountProof []hexutil.Bytes `json:"accountProof"`
}
type EthRPCGetProofResponse struct {
Result AccountStateResult `json:"result"`
}
Next, let’s save the HTTP response into a JSON, and parse the data into a data structure:
jsonFile, err := os.Open("eip1186_proof.json")
require.NoError(t, err)
defer jsonFile.Close()
byteValue, err := ioutil.ReadAll(jsonFile)
require.NoError(t, err)
// load into the struct
var response EthRPCGetProofResponse
err = json.Unmarshal(byteValue, &response)
require.NoError(t, err)
result := response.Result
Let’s create a trie, then we add the proof to the trie:
// create a proof trie, and add each node from the account proof
proofTrie := NewProofDB()
for _, node := range result.AccountProof {
proofTrie.Put(crypto.Keccak256(node), node)
}
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.
// get the state root hash from etherscan: https://etherscan.io/block/14900001
stateRootHash := common.HexToHash("0x024c056bc5db60d71c7908c5fad6050646bd70fd772ff222702d577e2af2e56b")
// verify the proof against the stateRootHash
validAccountState, err := VerifyProof(
stateRootHash.Bytes(), crypto.Keccak256(account.Bytes()), proofTrie)
require.NoError(t, err)
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.
The rich text element allows you to create and format headings, paragraphs, blockquotes, images, and video all in one place instead of having to add and format them individually. Just double-click and easily create content.
Decentralized because you can set up a blockchain system so that every entity can only make changes within its predefined purview. Companies can also use permissionless, permissioned, or hybrid-approach blockchains, allowing for useful trade-offs between network security and system privacy.
Decentralized because you can set up a blockchain system so that every entity can only make changes within its predefined purview. Companies can also use permissionless, permissioned, or hybrid-approach blockchains, allowing for useful trade-offs between network security and system privacy.
scscsssc
Static and dynamic content editing
A rich text element can be used with static or dynamic content. For static content, just drop it into any page and begin editing. For dynamic content, add a rich text field to any collection and then connect a rich text element to that field in the settings panel. Voila!
How to customize formatting for each rich text
Headings, paragraphs, blockquotes, figures, images, and figure captions can all be styled after a class is added to the rich text element using the "When inside of" nested selector system.