Skip to content

Commit

Permalink
feat: change bytecode hash function from Keccak to Poseidon (scroll-t…
Browse files Browse the repository at this point in the history
…ech#149)

* feat: change bytecode hash function from Keccak to Poseidon

* fix TestStateProcessorErrors

* rename files

* copy iden3's poseidon package to local

* update poseidon hash

* apply new hash in codehash

* extend hash method, use actual code size as cap

* prune keccak(nil) for empty code

* update empty code hash references to Poseidon

* add TestPoseidonCodeHash

* use keccak for empty code hash

* go mod tidy

* goimports

* use Poseidon code hash in state and sync tests

* run goimports

* run goimports

* remove unnecessary reallocation from CodeHash

* goimports

* move codehash API to separate module

* add missing package

* fix review comments

* improve comments

Co-authored-by: Ho Vei <[email protected]>
  • Loading branch information
Thegaram and noel2004 authored Sep 7, 2022
1 parent 346bfc8 commit 304af60
Show file tree
Hide file tree
Showing 25 changed files with 25,516 additions and 41 deletions.
4 changes: 2 additions & 2 deletions cmd/geth/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import (
"github.com/scroll-tech/go-ethereum/core/state/pruner"
"github.com/scroll-tech/go-ethereum/core/state/snapshot"
"github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/crypto"
"github.com/scroll-tech/go-ethereum/crypto/codehash"
"github.com/scroll-tech/go-ethereum/log"
"github.com/scroll-tech/go-ethereum/rlp"
"github.com/scroll-tech/go-ethereum/trie"
Expand All @@ -43,7 +43,7 @@ var (
emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")

// emptyCode is the known hash of the empty EVM bytecode.
emptyCode = crypto.Keccak256(nil)
emptyCode = codehash.EmptyCodeHash.Bytes()
)

var (
Expand Down
107 changes: 107 additions & 0 deletions core/blockchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import (
"testing"
"time"

"github.com/stretchr/testify/assert"

"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/consensus"
"github.com/scroll-tech/go-ethereum/consensus/ethash"
Expand Down Expand Up @@ -2986,3 +2988,108 @@ func TestEIP1559Transition(t *testing.T) {
t.Fatalf("sender balance incorrect: expected %d, got %d", expected, actual)
}
}

// TestPoseidonCodeHash makes sure that, after switching
// to Poseidon, code hashes change but addresses do not.
func TestPoseidonCodeHash(t *testing.T) {
// pragma solidity =0.8.7;
//
// contract Factory {
// event Deployed(address addr);
//
// function create(bytes memory code) public {
// address addr;
//
// assembly {
// addr := create(0, add(code, 0x20), mload(code))
// if iszero(extcodesize(addr)) {
// revert(0, 0)
// }
// }
//
// emit Deployed(addr);
// }
//
// function create2(bytes memory code) public {
// address addr;
//
// assembly {
// addr := create2(0, add(code, 0x20), mload(code), 0)
// if iszero(extcodesize(addr)) {
// revert(0, 0)
// }
// }
//
// emit Deployed(addr);
// }
// }
var deployCode = common.Hex2Bytes("608060405234801561001057600080fd5b506101d1806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063cf5ba53f1461003b578063f4754f6614610050575b600080fd5b61004e6100493660046100d4565b610063565b005b61004e61005e3660046100d4565b6100bb565b60008151602083016000f09050803b61007b57600080fd5b6040516001600160a01b03821681527ff40fcec21964ffb566044d083b4073f29f7f7929110ea19e1b3ebe375d89055e9060200160405180910390a15050565b6000808251602084016000f59050803b61007b57600080fd5b6000602082840312156100e657600080fd5b813567ffffffffffffffff808211156100fe57600080fd5b818401915084601f83011261011257600080fd5b81358181111561012457610124610185565b604051601f8201601f19908116603f0116810190838211818310171561014c5761014c610185565b8160405282815287602084870101111561016557600080fd5b826020860160208301376000928101602001929092525095945050505050565b634e487b7160e01b600052604160045260246000fdfea2646970667358221220c9bd2005b8669d44bf0254309308e422e7bdf086ac7a507990a0690a22b1eccd64736f6c63430008070033")

var callCreateCode = common.Hex2Bytes("cf5ba53f0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005c6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfea2646970667358221220707985753fcb6578098bb16f3709cf6d012993cba6dd3712661cf8f57bbc0d4d64736f6c6343000807003300000000")
var callCreate2Code = common.Hex2Bytes("f4754f660000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005c6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfea2646970667358221220707985753fcb6578098bb16f3709cf6d012993cba6dd3712661cf8f57bbc0d4d64736f6c6343000807003300000000")

var (
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
db = rawdb.NewMemoryDatabase()
gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000000)}}}
genesis = gspec.MustCommit(db)
signer = types.LatestSigner(gspec.Config)
engine = ethash.NewFaker()
blockchain, _ = NewBlockChain(db, nil, gspec.Config, engine, vm.Config{}, nil, nil)
)

defer blockchain.Stop()

// check empty code hash
state, _ := blockchain.State()
codeHash := state.GetCodeHash(addr1)
assert.Equal(t, codeHash, common.HexToHash("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"), "code hash mismatch")

// deploy contract through transaction
chain, receipts := GenerateChain(params.TestChainConfig, genesis, engine, db, 1, func(i int, gen *BlockGen) {
tx, _ := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, gen.header.BaseFee, deployCode), signer, key1)
gen.AddTx(tx)
})

if _, err := blockchain.InsertChain(chain); err != nil {
t.Fatalf("failed to insert chain: %v", err)
}

// make sure that the address did not change
contractAddress := receipts[0][0].ContractAddress
assert.Equal(t, common.HexToAddress("0x3A220f351252089D385b29beca14e27F204c296A"), contractAddress, "address mismatch")

state, _ = blockchain.State()
codeHash = state.GetCodeHash(contractAddress)

// keccak: 0x089bfd332dfa6117cbc20756f31801ce4f5a175eb258e46bf8123317da54cd96
assert.Equal(t, codeHash, common.HexToHash("0x28ec09723b285e17caabc4a8d52dbd097feddf408aee115cbb57c3c9c814d2b2"), "code hash mismatch")

// deploy contract through another contract (CREATE and CREATE2)
chain, receipts = GenerateChain(params.TestChainConfig, blockchain.CurrentBlock(), engine, db, 1, func(i int, gen *BlockGen) {
tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), contractAddress, new(big.Int), 1000000, gen.header.BaseFee, callCreateCode), signer, key1)
gen.AddTx(tx)

tx2, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), contractAddress, new(big.Int), 1000000, gen.header.BaseFee, callCreate2Code), signer, key1)
gen.AddTx(tx2)
})

if _, err := blockchain.InsertChain(chain); err != nil {
t.Fatalf("failed to insert chain: %v", err)
}

address1 := common.BytesToAddress(receipts[0][0].Logs[0].Data)
address2 := common.BytesToAddress(receipts[0][1].Logs[0].Data)

assert.Equal(t, common.HexToAddress("0x733f1083Fe476698001FA20D651376b7b3F1CA79"), address1, "address mismatch")
assert.Equal(t, common.HexToAddress("0x4099734c88B7D091E744da0E849df0e818e7E208"), address2, "address mismatch")

state, _ = blockchain.State()
codeHash1 := state.GetCodeHash(address1)
codeHash2 := state.GetCodeHash(address2)

// keccak: 0xfb5cd93a70ce47f91d33fac3afdb7b54680a6b0683506646a108ef4dfc047583
assert.Equal(t, common.HexToHash("0x2fa5836118b70a257defd2e54064ab63cc9bb2e91823eaacbdef32370050b5b2"), codeHash1, "code hash mismatch")
assert.Equal(t, common.HexToHash("0x2fa5836118b70a257defd2e54064ab63cc9bb2e91823eaacbdef32370050b5b2"), codeHash2, "code hash mismatch")
}
4 changes: 2 additions & 2 deletions core/state/pruner/pruner.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import (
"github.com/scroll-tech/go-ethereum/core/rawdb"
"github.com/scroll-tech/go-ethereum/core/state/snapshot"
"github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/crypto"
"github.com/scroll-tech/go-ethereum/crypto/codehash"
"github.com/scroll-tech/go-ethereum/ethdb"
"github.com/scroll-tech/go-ethereum/log"
"github.com/scroll-tech/go-ethereum/rlp"
Expand Down Expand Up @@ -60,7 +60,7 @@ var (
emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")

// emptyCode is the known hash of the empty EVM bytecode.
emptyCode = crypto.Keccak256(nil)
emptyCode = codehash.EmptyCodeHash.Bytes()
)

// Pruner is an offline tool to prune the stale state with the
Expand Down
4 changes: 2 additions & 2 deletions core/state/snapshot/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import (
"github.com/scroll-tech/go-ethereum/common/hexutil"
"github.com/scroll-tech/go-ethereum/common/math"
"github.com/scroll-tech/go-ethereum/core/rawdb"
"github.com/scroll-tech/go-ethereum/crypto"
"github.com/scroll-tech/go-ethereum/crypto/codehash"
"github.com/scroll-tech/go-ethereum/ethdb"
"github.com/scroll-tech/go-ethereum/ethdb/memorydb"
"github.com/scroll-tech/go-ethereum/log"
Expand All @@ -44,7 +44,7 @@ var (
emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")

// emptyCode is the known hash of the empty EVM bytecode.
emptyCode = crypto.Keccak256Hash(nil)
emptyCode = codehash.EmptyCodeHash

// accountCheckRange is the upper limit of the number of accounts involved in
// each range check. This is a value estimated based on experience. If this
Expand Down
3 changes: 2 additions & 1 deletion core/state/state_object.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ import (
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/crypto"
"github.com/scroll-tech/go-ethereum/crypto/codehash"
"github.com/scroll-tech/go-ethereum/metrics"
"github.com/scroll-tech/go-ethereum/rlp"
)

var emptyCodeHash = crypto.Keccak256(nil)
var emptyCodeHash = codehash.EmptyCodeHash.Bytes()

type Code []byte

Expand Down
12 changes: 6 additions & 6 deletions core/state/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (

"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/core/rawdb"
"github.com/scroll-tech/go-ethereum/crypto"
"github.com/scroll-tech/go-ethereum/crypto/codehash"
"github.com/scroll-tech/go-ethereum/ethdb"
)

Expand All @@ -47,7 +47,7 @@ func TestDump(t *testing.T) {
obj1 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01}))
obj1.AddBalance(big.NewInt(22))
obj2 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01, 0x02}))
obj2.SetCode(crypto.Keccak256Hash([]byte{3, 3, 3, 3, 3, 3, 3}), []byte{3, 3, 3, 3, 3, 3, 3})
obj2.SetCode(codehash.CodeHash([]byte{3, 3, 3, 3, 3, 3, 3}), []byte{3, 3, 3, 3, 3, 3, 3})
obj3 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x02}))
obj3.SetBalance(big.NewInt(44))

Expand All @@ -59,7 +59,7 @@ func TestDump(t *testing.T) {
// check that DumpToCollector contains the state objects that are in trie
got := string(s.state.Dump(nil))
want := `{
"root": "71edff0130dd2385947095001c73d9e28d862fc286fca2b922ca6f6f3cddfdd2",
"root": "bfd17853c56ac63be7327da432ef90a9b04376d71efede2a60765d2c94704cfe",
"accounts": {
"0x0000000000000000000000000000000000000001": {
"balance": "22",
Expand All @@ -79,7 +79,7 @@ func TestDump(t *testing.T) {
"balance": "0",
"nonce": 0,
"root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"codeHash": "0x87874902497a5bb968da31a2998d8f22e949d1ef6214bcdedd8bae24cca4b9e3",
"codeHash": "0x2263940ad476ca23bf01a9b033d65cd6b8bf9b7224a7ef2a4f10b61a2c039083",
"code": "0x03030303030303",
"key": "0xa17eacbc25cda025e81db9c5c62868822c73ce097cee2a63e33a2e41268358a1"
}
Expand Down Expand Up @@ -165,7 +165,7 @@ func TestSnapshot2(t *testing.T) {
so0 := state.getStateObject(stateobjaddr0)
so0.SetBalance(big.NewInt(42))
so0.SetNonce(43)
so0.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e'}), []byte{'c', 'a', 'f', 'e'})
so0.SetCode(codehash.CodeHash([]byte{'c', 'a', 'f', 'e'}), []byte{'c', 'a', 'f', 'e'})
so0.suicided = false
so0.deleted = false
state.setStateObject(so0)
Expand All @@ -177,7 +177,7 @@ func TestSnapshot2(t *testing.T) {
so1 := state.getStateObject(stateobjaddr1)
so1.SetBalance(big.NewInt(52))
so1.SetNonce(53)
so1.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e', '2'}), []byte{'c', 'a', 'f', 'e', '2'})
so1.SetCode(codehash.CodeHash([]byte{'c', 'a', 'f', 'e', '2'}), []byte{'c', 'a', 'f', 'e', '2'})
so1.suicided = true
so1.deleted = true
state.setStateObject(so1)
Expand Down
3 changes: 2 additions & 1 deletion core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/scroll-tech/go-ethereum/core/state/snapshot"
"github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/crypto"
"github.com/scroll-tech/go-ethereum/crypto/codehash"
"github.com/scroll-tech/go-ethereum/log"
"github.com/scroll-tech/go-ethereum/metrics"
"github.com/scroll-tech/go-ethereum/rlp"
Expand Down Expand Up @@ -459,7 +460,7 @@ func (s *StateDB) SetNonce(addr common.Address, nonce uint64) {
func (s *StateDB) SetCode(addr common.Address, code []byte) {
stateObject := s.GetOrNewStateObject(addr)
if stateObject != nil {
stateObject.SetCode(crypto.Keccak256Hash(code), code)
stateObject.SetCode(codehash.CodeHash(code), code)
}
}

Expand Down
7 changes: 4 additions & 3 deletions core/state/sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/scroll-tech/go-ethereum/core/rawdb"
"github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/crypto"
"github.com/scroll-tech/go-ethereum/crypto/codehash"
"github.com/scroll-tech/go-ethereum/ethdb"
"github.com/scroll-tech/go-ethereum/ethdb/memorydb"
"github.com/scroll-tech/go-ethereum/rlp"
Expand Down Expand Up @@ -58,12 +59,12 @@ func makeTestState() (Database, common.Hash, []*testAccount) {
acc.nonce = uint64(42 * i)

if i%3 == 0 {
obj.SetCode(crypto.Keccak256Hash([]byte{i, i, i, i, i}), []byte{i, i, i, i, i})
obj.SetCode(codehash.CodeHash([]byte{i, i, i, i, i}), []byte{i, i, i, i, i})
acc.code = []byte{i, i, i, i, i}
}
if i%5 == 0 {
for j := byte(0); j < 5; j++ {
hash := crypto.Keccak256Hash([]byte{i, i, i, i, i, j, j})
hash := codehash.CodeHash([]byte{i, i, i, i, i, j, j})
obj.SetState(db, hash, hash)
}
}
Expand Down Expand Up @@ -407,7 +408,7 @@ func TestIncompleteStateSync(t *testing.T) {
var isCode = make(map[common.Hash]struct{})
for _, acc := range srcAccounts {
if len(acc.code) > 0 {
isCode[crypto.Keccak256Hash(acc.code)] = struct{}{}
isCode[codehash.CodeHash(acc.code)] = struct{}{}
}
}
isCode[common.BytesToHash(emptyCodeHash)] = struct{}{}
Expand Down
2 changes: 1 addition & 1 deletion core/state_processor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ func TestStateProcessorErrors(t *testing.T) {
txs: []*types.Transaction{
mkDynamicTx(0, common.Address{}, params.TxGas-1000, big.NewInt(0), big.NewInt(0)),
},
want: "could not apply tx 0 [0x88626ac0d53cb65308f2416103c62bb1f18b805573d4f96a3640bbbfff13c14f]: sender not an eoa: address 0x71562b71999873DB5b286dF957af199Ec94617F7, codehash: 0x9280914443471259d4570a8661015ae4a5b80186dbc619658fb494bebc3da3d1",
want: "could not apply tx 0 [0x88626ac0d53cb65308f2416103c62bb1f18b805573d4f96a3640bbbfff13c14f]: sender not an eoa: address 0x71562b71999873DB5b286dF957af199Ec94617F7, codehash: 0x14644f8304e8c7052dee61ac235af61e12e25a730d13beccac8b3b72f5705874",
},
} {
block := GenerateBadBlock(genesis, ethash.NewFaker(), tt.txs, gspec.Config)
Expand Down
4 changes: 2 additions & 2 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ import (
cmath "github.com/scroll-tech/go-ethereum/common/math"
"github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/core/vm"
"github.com/scroll-tech/go-ethereum/crypto"
"github.com/scroll-tech/go-ethereum/crypto/codehash"
"github.com/scroll-tech/go-ethereum/params"
)

var emptyCodeHash = crypto.Keccak256Hash(nil)
var emptyCodeHash = codehash.EmptyCodeHash

/*
The State Transitioning Model
Expand Down
6 changes: 3 additions & 3 deletions core/types/zktrie/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ package zktrie
import (
"math/big"

"github.com/iden3/go-iden3-crypto/poseidon"
"github.com/scroll-tech/go-ethereum/crypto/poseidon"
)

// HashElems performs a recursive poseidon hash over the array of ElemBytes, each hash
// reduce 2 fieds into one
func HashElems(fst, snd *big.Int, elems ...*big.Int) (*Hash, error) {

l := len(elems)
baseH, err := poseidon.Hash([]*big.Int{fst, snd})
baseH, err := poseidon.HashFixed([]*big.Int{fst, snd})
if err != nil {
return nil, err
}
Expand All @@ -26,7 +26,7 @@ func HashElems(fst, snd *big.Int, elems ...*big.Int) (*Hash, error) {
if (i+1)*2 > l {
tmp[i] = elems[i*2+1]
} else {
h, err := poseidon.Hash(elems[i*2 : (i+1)*2])
h, err := poseidon.HashFixed(elems[i*2 : (i+1)*2])
if err != nil {
return nil, err
}
Expand Down
4 changes: 3 additions & 1 deletion core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@ import (

"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/crypto"
"github.com/scroll-tech/go-ethereum/crypto/codehash"
"github.com/scroll-tech/go-ethereum/params"
)

// emptyCodeHash is used by create to ensure deployment is disallowed to already
// deployed contract addresses (relevant after the account abstraction).
var emptyCodeHash = crypto.Keccak256Hash(nil)
var emptyCodeHash = codehash.EmptyCodeHash

type (
// CanTransferFunc is the signature of a transfer guard function
Expand Down Expand Up @@ -410,6 +411,7 @@ type codeAndHash struct {

func (c *codeAndHash) Hash() common.Hash {
if c.hash == (common.Hash{}) {
// when calculating CREATE2 address, we use Keccak256 not Poseidon
c.hash = crypto.Keccak256Hash(c.code)
}
return c.hash
Expand Down
16 changes: 16 additions & 0 deletions crypto/codehash/codehash.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package codehash

import (
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/crypto/poseidon"
)

var EmptyCodeHash common.Hash

func CodeHash(code []byte) (h common.Hash) {
return poseidon.CodeHash(code)
}

func init() {
EmptyCodeHash = poseidon.CodeHash(nil)
}
Loading

0 comments on commit 304af60

Please sign in to comment.