Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement EIP 1186 #1180

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions packages/vm/lib/state/stateManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import {
keccak256,
KECCAK256_NULL,
unpadBuffer,
PrefixedHexString,
bnToHex,
bufferToHex,
rlp,
} from 'ethereumjs-util'
import { encode, decode } from 'rlp'
import Common from '@ethereumjs/common'
Expand All @@ -23,6 +27,21 @@ const debug = createDebugLogger('vm:state')

type AddressHex = string

type StorageProof = {
key: PrefixedHexString
proof: PrefixedHexString[]
value: PrefixedHexString
}

type Proof = {
balance: PrefixedHexString
codeHash: PrefixedHexString
nonce: PrefixedHexString
storageHash: PrefixedHexString
accountProof: PrefixedHexString[]
storageProof: StorageProof[]
}

/**
* Options for constructing a [[StateManager]].
*/
Expand Down Expand Up @@ -447,6 +466,81 @@ export default class DefaultStateManager implements StateManager {
}
}

/**
* Get an EIP-1186 proof
* @param address - address to get proof of
* @param storageSlots - storage slots to get proof of
*/
async getProof(address: Address, storageSlots: Buffer[] = []): Promise<Proof> {
const account = await this.getAccount(address)
const accountProof: PrefixedHexString[] = (
await Trie.createProof(this._trie, address.buf)
).map((e: Buffer) => bufferToHex(e))
const storageProof: StorageProof[] = []
const storageTrie = await this._getStorageTrie(address)

await Promise.all(
storageSlots.map(async (storageKey: Buffer) => {
const proof = (await Trie.createProof(storageTrie, storageKey)).map((e: Buffer) =>
bufferToHex(e)
)
const proofItem: StorageProof = {
key: bufferToHex(storageKey),
value: bufferToHex(await this.getContractStorage(address, storageKey)),
proof,
}
storageProof.push(proofItem)
})
)

const returnValue: Proof = {
balance: bnToHex(account.balance),
codeHash: bufferToHex(account.codeHash),
nonce: bnToHex(account.nonce),
storageHash: bufferToHex(account.stateRoot),
accountProof,
storageProof,
}
return returnValue
}

/**
* Verify an EIP-1186 proof. Throws if proof is invalid, otherwise returns true
* @param proof - The Proof to prove
*/
async verifyProof(proof: Proof): Promise<boolean> {
const rootHash = rlp.decode(toBuffer(proof.accountProof[0]))
const key = keccak256(toBuffer(proof.accountProof[proof.accountProof.length - 1]))
const accountProof = proof.accountProof.map((rlpString: PrefixedHexString) =>
toBuffer(rlpString)
)

// This returns the account if the trie is OK, verify that it matches the reported account
await Trie.verifyProof(rootHash, key, accountProof)

const storageRoot = toBuffer(proof.storageHash)

/* TODO verify that value matches the account */
await Promise.all(
proof.storageProof.map(async (stProof: StorageProof) => {
const storageProof = stProof.proof.map((value: PrefixedHexString) => toBuffer(value))
const storageValue = toBuffer(stProof.value)
const storageKey = toBuffer(stProof.key)

// TODO figure out in what case verifyProof returns null (empty trie?)
const reportedValue = <Buffer>await Trie.verifyProof(storageRoot, storageKey, storageProof)

if (!reportedValue.equals(storageValue)) {
throw 'Reported trie value does not match storage'
}
})
).catch((e) => {
throw e
})

return true
}

/**
* Gets the state-root of the Merkle-Patricia trie representation
* of the state of this StateManager. Will error if there are uncommitted
Expand Down
23 changes: 23 additions & 0 deletions packages/vm/tests/api/state/stateManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,29 @@ tape('StateManager - Contract storage', (tester) => {
t.ok(contractValue.equals(expect), 'trailing zeros are not stripped')
t.end()
})

it('should get and verify EIP 1178 proofs', async (t) => {
const address = Address.zero()
const key = zeros(32)
const value = Buffer.from('0000aabb00', 'hex')
const code = Buffer.from('6000', 'hex')
const stateManager = new DefaultStateManager()
await stateManager.putContractStorage(address, key, value)
await stateManager.putContractCode(address, code)
const account = await stateManager.getAccount(address)
account.balance = new BN(1)
account.nonce = new BN(2)
await stateManager.putAccount(address, account)

const address2 = new Address(Buffer.from('20'.repeat(20), 'hex'))
const account2 = await stateManager.getAccount(address2)
account.nonce = new BN(2)
await stateManager.putAccount(address2, account2)

const proof = await stateManager.getProof(address, [key])
console.log(proof)
t.end()
})
})

tape('StateManager - generateAccessList', (tester) => {
Expand Down