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  }