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

Feature merkle trie proof #6

Merged
merged 5 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
48 changes: 39 additions & 9 deletions core/vm/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/vm/libplanet"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/blake2b"
"github.com/ethereum/go-ethereum/crypto/bls12381"
Expand Down Expand Up @@ -82,15 +83,16 @@ var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{
// PrecompiledContractsBerlin contains the default set of pre-compiled Ethereum
// contracts used in the Berlin release.
var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
common.BytesToAddress([]byte{5}): &bigModExp{eip2565: true},
common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
common.BytesToAddress([]byte{9}): &blake2F{},
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
common.BytesToAddress([]byte{5}): &bigModExp{eip2565: true},
common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
common.BytesToAddress([]byte{9}): &blake2F{},
common.BytesToAddress([]byte{0x02, 0x00}): &libplanetVerifyProof{},
sircoon4 marked this conversation as resolved.
Show resolved Hide resolved
}

// PrecompiledContractsCancun contains the default set of pre-compiled Ethereum
Expand Down Expand Up @@ -201,6 +203,34 @@ func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uin
return output, suppliedGas, err
}

type libplanetVerifyProof struct{}

func (c *libplanetVerifyProof) RequiredGas(input []byte) uint64 {
return uint64(3000)
}

func (c *libplanetVerifyProof) Run(input []byte) ([]byte, error) {
proofMap := map[string]any{
"stateRootHash": nil, // sha256(bencoded) []byte
"proof": nil, // bencoded list [][]byte
"key": nil, // keyBytes []byte
"value": nil, // bencoded []byte
}
proofMap, err := libplanet.ParseMerkleTrieProofInput(input)
if err != nil {
return nil, err
}

stateRootHash := proofMap["stateRootHash"].([]byte)
proof := proofMap["proof"].([][]byte)
key := proofMap["key"].([]byte)
value := proofMap["value"].([]byte)

valid, _ := libplanet.ValidateProof(stateRootHash, proof, key, value)

return common.CopyBytes(libplanet.BoolAbi(valid)), nil
}

// ECRECOVER implemented as a native contract.
type ecrecover struct{}

Expand Down
5 changes: 4 additions & 1 deletion core/vm/contracts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ var allPrecompiles = map[common.Address]PrecompiledContract{

common.BytesToAddress([]byte{0x01, 0x00}): &p256Verify{},

common.BytesToAddress([]byte{0x02, 0x00}): &libplanetVerifyProof{},

common.BytesToAddress([]byte{0x0f, 0x0a}): &bls12381G1Add{},
common.BytesToAddress([]byte{0x0f, 0x0b}): &bls12381G1Mul{},
common.BytesToAddress([]byte{0x0f, 0x0c}): &bls12381G1MultiExp{},
Expand Down Expand Up @@ -408,4 +410,5 @@ func BenchmarkPrecompiledP256Verify(bench *testing.B) {
benchmarkPrecompiled("100", t, bench)
}

func TestPrecompiledP256Verify(t *testing.T) { testJson("p256Verify", "100", t) }
func TestPrecompiledP256Verify(t *testing.T) { testJson("p256Verify", "100", t) }
func TestPrecompiledLibplanetVerifyProof(t *testing.T) { testJson("libplanetVerifyProof", "200", t) }
105 changes: 105 additions & 0 deletions core/vm/libplanet/merkle_trie_node.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package libplanet

import (
"fmt"

"github.com/sircoon4/bencodex-go"
)

type node interface {
name() string
}

type (
fullNode struct {
Children [17]node
}
shortNode struct {
Key []byte
Value node
}
hashNode []byte // sha256(bencoded)
valueNode []byte // bencoded
)

func (n *fullNode) name() string {
return "fullNode"
}
func (n *shortNode) name() string {
return "shortNode"
}
func (n hashNode) name() string {
return "hashNode"
}
func (n valueNode) name() string {
return "valueNode"
}

func (n *fullNode) GetValue() node {
return n.Children[16]
}
func (n *shortNode) GetValue() node {
return n.Value
}
func (n hashNode) GetValue() []byte {
return n
}
func (n valueNode) GetValue() []byte {
return n
}

func nodeFromProof(proof []byte) (node, error) {
data, err := bencodex.Decode(proof)
if err != nil {
return nil, err
}

return nodeFromData(data)
}

func nodeFromData(data any) (node, error) {
if data == nil {
return nil, nil
}

switch data := data.(type) {
case []byte:
return hashNode(data), nil
case []interface{}:
list := data
if len(list) == 2 {
if list[0] == nil {
value, err := bencodex.Encode(list[1])
if err != nil {
return nil, err
}
return valueNode(value), nil
} else {
value, err := nodeFromData(list[1])
if err != nil {
return nil, err
}
return &shortNode{
Key: list[0].([]byte),
Value: value,
}, nil
}
} else if len(list) == 17 {
children := [17]node{}
for i, child := range list {
var err error
children[i], err = nodeFromData(child)
if err != nil {
return nil, err
}
}
return &fullNode{
Children: children,
}, nil
}
default:
return nil, fmt.Errorf("invalid node")
}

return nil, fmt.Errorf("invalid node")
}
68 changes: 68 additions & 0 deletions core/vm/libplanet/merkle_trie_proof.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package libplanet

import (
"bytes"
"fmt"
)

func ValidateProof(
stateRootHash []byte, // []byte
proof [][]byte, // bencoded list
key []byte, // []byte
value []byte, // bencoded
) (bool, error) {
targetHash := stateRootHash
nibbles := keybytesToNibbles(key)

for i, bencodedProofNode := range proof {
proofNode, err := nodeFromProof(bencodedProofNode)
if err != nil {
return false, err
}

first := i == 0
last := i == len(proof)-1

if _, ok := proofNode.(hashNode); ok {
return false, fmt.Errorf("proof node cannot be a hash node")
}

if err := checkProofNodeHash(targetHash, bencodedProofNode, first); err != nil {
return false, err
}

nextNode, nextNibbles, err := resolveToNextCandidateNode(proofNode, nibbles)
if err != nil {
return false, err
}

switch nextNode := nextNode.(type) {
case hashNode:
if !last {
nibbles = nextNibbles
targetHash = nextNode.GetValue()
continue
} else {
return false, fmt.Errorf("hash node cannot be the last node")
}
case valueNode:
if last {
if len(nextNibbles) != 0 {
return false, fmt.Errorf("nibbles not exhausted")
}

if bytes.Equal(nextNode.GetValue(), value) {
return true, nil
} else {
return false, fmt.Errorf("value mismatch")
}
} else {
return false, fmt.Errorf("value node must be the last node")
}
default:
return false, fmt.Errorf("invalid node")
}
}

return false, fmt.Errorf("proof exhausted")
}
117 changes: 117 additions & 0 deletions core/vm/libplanet/merkle_trie_proof_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package libplanet

import (
"bytes"
"crypto/sha256"
"fmt"

"github.com/ethereum/go-ethereum/accounts/abi"
)

func ParseMerkleTrieProofInput(input []byte) (map[string]any, error) {
Bytes, _ := abi.NewType("bytes", "", nil)
BytesArr, _ := abi.NewType("bytes[]", "", nil)

var arguments = abi.Arguments{
abi.Argument{Name: "stateRootHash", Type: Bytes, Indexed: false},
abi.Argument{Name: "proof", Type: BytesArr, Indexed: false},
abi.Argument{Name: "key", Type: Bytes, Indexed: false},
abi.Argument{Name: "value", Type: Bytes, Indexed: false},
}

decoded := map[string]any{
"stateRootHash": nil,
"proof": nil,
"key": nil,
"value": nil,
}
err := arguments.UnpackIntoMap(decoded, input)
if err != nil {
return nil, err
}

return decoded, nil
}

func BoolAbi(input bool) []byte {

Bool, _ := abi.NewType("bool", "", nil)

var arguments = abi.Arguments{
abi.Argument{Name: "proofResult", Type: Bool, Indexed: false},
}

encoded, err := arguments.Pack(input)
if err != nil {
panic(err)
}
return encoded
}

func keybytesToNibbles(str []byte) []byte {
l := len(str) * 2
var nibbles = make([]byte, l)
for i, b := range str {
nibbles[i*2] = b / 16
nibbles[i*2+1] = b % 16
}
return nibbles
}

func checkProofNodeHash(
targetHash []byte, // sha256(bencoded)
bencodedProofNode []byte, // bencoded
first bool,
) error {
if !first && len(bencodedProofNode) <= sha256.Size {
return fmt.Errorf("proof node must be longer than hash size")
}

proofNodeHash := sha256.Sum256(bencodedProofNode)
if !bytes.Equal(proofNodeHash[:], targetHash) {
return fmt.Errorf("proof node hash does not match target hash")
}

return nil
}

func resolveToNextCandidateNode(
proofNode node,
nibbles []byte,
) (node, []byte, error) {
switch proofNode := proofNode.(type) {
case hashNode:
hash := proofNode
return hash, nibbles, nil
case valueNode:
value := proofNode
return value, nibbles, nil
case *shortNode:
short := proofNode
if len(nibbles) < len(short.Key) {
return nil, nil, fmt.Errorf("nibbles exhausted")
}

if bytes.Equal(short.Key, nibbles[:len(short.Key)]) {
return resolveToNextCandidateNode(short.Value, nibbles[len(short.Key):])
} else {
return nil, nil, fmt.Errorf("key mismatch")
}
case *fullNode:
full := proofNode
if len(nibbles) == 0 {
if full.GetValue() != nil {
return full.GetValue(), nil, nil
} else {
return nil, nil, fmt.Errorf("nibbles exhausted")
}
}
child := full.Children[int(nibbles[0])]
if child == nil {
return nil, nil, fmt.Errorf("child not found")
}
return resolveToNextCandidateNode(child, nibbles[1:])
}

return nil, nil, fmt.Errorf("invalid proof node")
}
Loading