github.com/ethereum-optimism/optimism@v1.7.2/packages/sdk/src/utils/merkle-utils.ts (about) 1 /* Imports: External */ 2 import { ethers, BigNumber } from 'ethers' 3 import { 4 fromHexString, 5 toHexString, 6 toRpcHexString, 7 } from '@eth-optimism/core-utils' 8 import { MerkleTree } from 'merkletreejs' 9 import * as rlp from 'rlp' 10 11 /** 12 * Generates a Merkle proof (using the particular scheme we use within Lib_MerkleTree). 13 * 14 * @param leaves Leaves of the merkle tree. 15 * @param index Index to generate a proof for. 16 * @returns Merkle proof sibling leaves, as hex strings. 17 */ 18 export const makeMerkleTreeProof = ( 19 leaves: string[], 20 index: number 21 ): string[] => { 22 // Our specific Merkle tree implementation requires that the number of leaves is a power of 2. 23 // If the number of given leaves is less than a power of 2, we need to round up to the next 24 // available power of 2. We fill the remaining space with the hash of bytes32(0). 25 const correctedTreeSize = Math.pow(2, Math.ceil(Math.log2(leaves.length))) 26 const parsedLeaves = [] 27 for (let i = 0; i < correctedTreeSize; i++) { 28 if (i < leaves.length) { 29 parsedLeaves.push(leaves[i]) 30 } else { 31 parsedLeaves.push(ethers.utils.keccak256('0x' + '00'.repeat(32))) 32 } 33 } 34 35 // merkletreejs prefers things to be Buffers. 36 const bufLeaves = parsedLeaves.map(fromHexString) 37 const tree = new MerkleTree(bufLeaves, (el: Buffer | string): Buffer => { 38 return fromHexString(ethers.utils.keccak256(el)) 39 }) 40 41 const proof = tree.getProof(bufLeaves[index], index).map((element: any) => { 42 return toHexString(element.data) 43 }) 44 45 return proof 46 } 47 48 /** 49 * Fix for the case where the final proof element is less than 32 bytes and the element exists 50 * inside of a branch node. Current implementation of the onchain MPT contract can't handle this 51 * natively so we instead append an extra proof element to handle it instead. 52 * 53 * @param key Key that the proof is for. 54 * @param proof Proof to potentially modify. 55 * @returns Modified proof. 56 */ 57 export const maybeAddProofNode = (key: string, proof: string[]) => { 58 const modifiedProof = [...proof] 59 const finalProofEl = modifiedProof[modifiedProof.length - 1] 60 const finalProofElDecoded = rlp.decode(finalProofEl) as any 61 if (finalProofElDecoded.length === 17) { 62 for (const item of finalProofElDecoded) { 63 // Find any nodes located inside of the branch node. 64 if (Array.isArray(item)) { 65 // Check if the key inside the node matches the key we're looking for. We remove the first 66 // two characters (0x) and then we remove one more character (the first nibble) since this 67 // is the identifier for the type of node we're looking at. In this case we don't actually 68 // care what type of node it is because a branch node would only ever be the final proof 69 // element if (1) it includes the leaf node we're looking for or (2) it stores the value 70 // within itself. If (1) then this logic will work, if (2) then this won't find anything 71 // and we won't append any proof elements, which is exactly what we would want. 72 const suffix = toHexString(item[0]).slice(3) 73 if (key.endsWith(suffix)) { 74 modifiedProof.push(toHexString(rlp.encode(item))) 75 } 76 } 77 } 78 } 79 80 // Return the modified proof. 81 return modifiedProof 82 } 83 84 /** 85 * Generates a Merkle-Patricia trie proof for a given account and storage slot. 86 * 87 * @param provider RPC provider attached to an EVM-compatible chain. 88 * @param blockNumber Block number to generate the proof at. 89 * @param address Address to generate the proof for. 90 * @param slot Storage slot to generate the proof for. 91 * @returns Account proof and storage proof. 92 */ 93 export const makeStateTrieProof = async ( 94 provider: ethers.providers.JsonRpcProvider, 95 blockNumber: number, 96 address: string, 97 slot: string 98 ): Promise<{ 99 accountProof: string[] 100 storageProof: string[] 101 storageValue: BigNumber 102 storageRoot: string 103 }> => { 104 const proof = await provider.send('eth_getProof', [ 105 address, 106 [slot], 107 toRpcHexString(blockNumber), 108 ]) 109 110 proof.storageProof[0].proof = maybeAddProofNode( 111 ethers.utils.keccak256(slot), 112 proof.storageProof[0].proof 113 ) 114 115 return { 116 accountProof: proof.accountProof, 117 storageProof: proof.storageProof[0].proof, 118 storageValue: BigNumber.from(proof.storageProof[0].value), 119 storageRoot: proof.storageHash, 120 } 121 }