Commit f44446ab authored by refcell.eth's avatar refcell.eth Committed by GitHub

feat(op-challenger): binary merkle tree with proof generation (#9146)

parent 6a3b1ec1
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/matrix" "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/matrix"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/merkle"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types" keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching" "github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
...@@ -49,24 +50,6 @@ func toPreimageOracleLeaf(l keccakTypes.Leaf) bindings.PreimageOracleLeaf { ...@@ -49,24 +50,6 @@ func toPreimageOracleLeaf(l keccakTypes.Leaf) bindings.PreimageOracleLeaf {
} }
} }
// MerkleProof is a place holder for the actual type we use for merkle proofs
// TODO(client-pod#481): Move this somewhere better and add useful functionality
type MerkleProof [][]byte
// toSized converts a [][]byte to a [][32]byte
func (p MerkleProof) toSized() [][32]byte {
var sized [][32]byte
for _, proof := range p {
// SAFETY: if the proof is less than 32 bytes, it will be padded with 0s
if len(proof) < 32 {
proof = append(proof, make([]byte, 32-len(proof))...)
}
// SAFETY: the proof is 32 or more bytes here, so it will be truncated to 32 bytes
sized = append(sized, [32]byte(proof[:32]))
}
return sized
}
func NewPreimageOracleContract(addr common.Address, caller *batching.MultiCaller) (*PreimageOracleContract, error) { func NewPreimageOracleContract(addr common.Address, caller *batching.MultiCaller) (*PreimageOracleContract, error) {
oracleAbi, err := bindings.PreimageOracleMetaData.GetAbi() oracleAbi, err := bindings.PreimageOracleMetaData.GetAbi()
if err != nil { if err != nil {
...@@ -104,9 +87,9 @@ func (c *PreimageOracleContract) Squeeze( ...@@ -104,9 +87,9 @@ func (c *PreimageOracleContract) Squeeze(
uuid *big.Int, uuid *big.Int,
stateMatrix *matrix.StateMatrix, stateMatrix *matrix.StateMatrix,
preState keccakTypes.Leaf, preState keccakTypes.Leaf,
preStateProof MerkleProof, preStateProof merkle.Proof,
postState keccakTypes.Leaf, postState keccakTypes.Leaf,
postStateProof MerkleProof, postStateProof merkle.Proof,
) (txmgr.TxCandidate, error) { ) (txmgr.TxCandidate, error) {
call := c.contract.Call( call := c.contract.Call(
methodSqueezeLPP, methodSqueezeLPP,
...@@ -114,9 +97,9 @@ func (c *PreimageOracleContract) Squeeze( ...@@ -114,9 +97,9 @@ func (c *PreimageOracleContract) Squeeze(
uuid, uuid,
abiEncodeStateMatrix(stateMatrix), abiEncodeStateMatrix(stateMatrix),
toPreimageOracleLeaf(preState), toPreimageOracleLeaf(preState),
preStateProof.toSized(), preStateProof,
toPreimageOracleLeaf(postState), toPreimageOracleLeaf(postState),
postStateProof.toSized(), postStateProof,
) )
return call.ToTxCandidate() return call.ToTxCandidate()
} }
......
...@@ -10,6 +10,7 @@ import ( ...@@ -10,6 +10,7 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/matrix" "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/matrix"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/merkle"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types" keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching" "github.com/ethereum-optimism/optimism/op-service/sources/batching"
batchingTest "github.com/ethereum-optimism/optimism/op-service/sources/batching/test" batchingTest "github.com/ethereum-optimism/optimism/op-service/sources/batching/test"
...@@ -84,21 +85,21 @@ func TestPreimageOracleContract_Squeeze(t *testing.T) { ...@@ -84,21 +85,21 @@ func TestPreimageOracleContract_Squeeze(t *testing.T) {
Index: big.NewInt(123), Index: big.NewInt(123),
StateCommitment: common.Hash{0x34}, StateCommitment: common.Hash{0x34},
} }
preStateProof := MerkleProof{{0x34}} preStateProof := merkle.Proof{{0x34}}
postState := keccakTypes.Leaf{ postState := keccakTypes.Leaf{
Input: [keccakTypes.BlockSize]byte{0x34}, Input: [keccakTypes.BlockSize]byte{0x34},
Index: big.NewInt(456), Index: big.NewInt(456),
StateCommitment: common.Hash{0x56}, StateCommitment: common.Hash{0x56},
} }
postStateProof := MerkleProof{{0x56}} postStateProof := merkle.Proof{{0x56}}
stubRpc.SetResponse(oracleAddr, methodSqueezeLPP, batching.BlockLatest, []interface{}{ stubRpc.SetResponse(oracleAddr, methodSqueezeLPP, batching.BlockLatest, []interface{}{
claimant, claimant,
uuid, uuid,
abiEncodeStateMatrix(stateMatrix), abiEncodeStateMatrix(stateMatrix),
toPreimageOracleLeaf(preState), toPreimageOracleLeaf(preState),
preStateProof.toSized(), preStateProof,
toPreimageOracleLeaf(postState), toPreimageOracleLeaf(postState),
postStateProof.toSized(), postStateProof,
}, nil) }, nil)
tx, err := oracle.Squeeze(claimant, uuid, stateMatrix, preState, preStateProof, postState, postStateProof) tx, err := oracle.Squeeze(claimant, uuid, stateMatrix, preState, preStateProof, postState, postStateProof)
......
...@@ -6,9 +6,9 @@ import ( ...@@ -6,9 +6,9 @@ import (
"math/big" "math/big"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/matrix" "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/matrix"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/merkle"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types" keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching" "github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testlog"
...@@ -203,9 +203,10 @@ func (s *mockPreimageOracleContract) AddLeaves(_ *big.Int, _ *big.Int, input []b ...@@ -203,9 +203,10 @@ func (s *mockPreimageOracleContract) AddLeaves(_ *big.Int, _ *big.Int, input []b
return txmgr.TxCandidate{}, nil return txmgr.TxCandidate{}, nil
} }
func (s *mockPreimageOracleContract) Squeeze(_ common.Address, _ *big.Int, _ *matrix.StateMatrix, _ keccakTypes.Leaf, _ contracts.MerkleProof, _ keccakTypes.Leaf, _ contracts.MerkleProof) (txmgr.TxCandidate, error) { func (s *mockPreimageOracleContract) Squeeze(_ common.Address, _ *big.Int, _ *matrix.StateMatrix, _ keccakTypes.Leaf, _ merkle.Proof, _ keccakTypes.Leaf, _ merkle.Proof) (txmgr.TxCandidate, error) {
return txmgr.TxCandidate{}, nil return txmgr.TxCandidate{}, nil
} }
func (s *mockPreimageOracleContract) GetProposalMetadata(_ context.Context, _ batching.Block, idents ...keccakTypes.LargePreimageIdent) ([]keccakTypes.LargePreimageMetaData, error) { func (s *mockPreimageOracleContract) GetProposalMetadata(_ context.Context, _ batching.Block, idents ...keccakTypes.LargePreimageIdent) ([]keccakTypes.LargePreimageMetaData, error) {
if s.initialized || s.bytesProcessed > 0 { if s.initialized || s.bytesProcessed > 0 {
metadata := make([]keccakTypes.LargePreimageMetaData, 0) metadata := make([]keccakTypes.LargePreimageMetaData, 0)
......
...@@ -5,9 +5,9 @@ import ( ...@@ -5,9 +5,9 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/matrix" "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/matrix"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/merkle"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types" keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching" "github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
...@@ -26,6 +26,6 @@ type PreimageUploader interface { ...@@ -26,6 +26,6 @@ type PreimageUploader interface {
type PreimageOracleContract interface { type PreimageOracleContract interface {
InitLargePreimage(uuid *big.Int, partOffset uint32, claimedSize uint32) (txmgr.TxCandidate, error) InitLargePreimage(uuid *big.Int, partOffset uint32, claimedSize uint32) (txmgr.TxCandidate, error)
AddLeaves(uuid *big.Int, startingBlockIndex *big.Int, input []byte, commitments []common.Hash, finalize bool) (txmgr.TxCandidate, error) AddLeaves(uuid *big.Int, startingBlockIndex *big.Int, input []byte, commitments []common.Hash, finalize bool) (txmgr.TxCandidate, error)
Squeeze(claimant common.Address, uuid *big.Int, stateMatrix *matrix.StateMatrix, preState keccakTypes.Leaf, preStateProof contracts.MerkleProof, postState keccakTypes.Leaf, postStateProof contracts.MerkleProof) (txmgr.TxCandidate, error) Squeeze(claimant common.Address, uuid *big.Int, stateMatrix *matrix.StateMatrix, preState keccakTypes.Leaf, preStateProof merkle.Proof, postState keccakTypes.Leaf, postStateProof merkle.Proof) (txmgr.TxCandidate, error)
GetProposalMetadata(ctx context.Context, block batching.Block, idents ...keccakTypes.LargePreimageIdent) ([]keccakTypes.LargePreimageMetaData, error) GetProposalMetadata(ctx context.Context, block batching.Block, idents ...keccakTypes.LargePreimageIdent) ([]keccakTypes.LargePreimageMetaData, error)
} }
package merkle
import (
"errors"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
// BinaryMerkleTreeDepth is the depth of the merkle tree.
const BinaryMerkleTreeDepth = 16
var (
// MaxLeafCount is the maximum number of leaves in the merkle tree.
MaxLeafCount = 1<<BinaryMerkleTreeDepth - 1 // 2^16 - 1
// IndexOutOfBoundsError is returned when an index is out of bounds.
IndexOutOfBoundsError = errors.New("index out of bounds")
// zeroHashes is a list of empty hashes in the binary merkle tree, indexed by height.
zeroHashes [BinaryMerkleTreeDepth]common.Hash
// rootHash is the known root hash of the empty binary merkle tree.
rootHash common.Hash
)
func init() {
// Initialize the zero hashes. These hashes are pre-computed for the starting state of the tree, where all leaves
// are equal to `[32]byte{}`.
for height := 0; height < BinaryMerkleTreeDepth-1; height++ {
rootHash = crypto.Keccak256Hash(rootHash[:], zeroHashes[height][:])
zeroHashes[height+1] = crypto.Keccak256Hash(zeroHashes[height][:], zeroHashes[height][:])
}
rootHash = crypto.Keccak256Hash(rootHash[:], zeroHashes[BinaryMerkleTreeDepth-1][:])
}
// Proof is a list of [common.Hash]s that prove the merkle inclusion of a leaf.
// These are the sibling hashes of the leaf's path from the root to the leaf.
type Proof [BinaryMerkleTreeDepth]common.Hash
// merkleNode is a single node in the binary merkle tree.
type merkleNode struct {
Label common.Hash
Parent *merkleNode
Left *merkleNode
Right *merkleNode
}
func (m *merkleNode) IsLeftChild(o *merkleNode) bool {
return m.Left == o
}
func (m *merkleNode) IsRightChild(o *merkleNode) bool {
return m.Right == o
}
// BinaryMerkleTree is a binary hash tree that uses the keccak256 hash function.
// It is an append-only tree, where leaves are added from left to right.
type BinaryMerkleTree struct {
Root *merkleNode
LeafCount int
}
func NewBinaryMerkleTree() *BinaryMerkleTree {
return &BinaryMerkleTree{
Root: &merkleNode{Label: rootHash},
LeafCount: 0,
}
}
// RootHash returns the root hash of the binary merkle tree.
func (m *BinaryMerkleTree) RootHash() (rootHash common.Hash) {
return m.Root.Label
}
// walkDownToMaxLeaf walks down the tree to the max leaf node.
func (m *BinaryMerkleTree) walkDownToLeafCount(subtreeLeafCount int) *merkleNode {
maxSubtreeLeafCount := MaxLeafCount + 1
levelNode := m.Root
for height := 0; height < BinaryMerkleTreeDepth; height++ {
if subtreeLeafCount*2 <= maxSubtreeLeafCount {
if levelNode.Left == nil {
levelNode.Left = &merkleNode{
Label: zeroHashes[height],
Parent: levelNode,
}
}
levelNode = levelNode.Left
} else {
if levelNode.Right == nil {
levelNode.Right = &merkleNode{
Label: zeroHashes[height],
Parent: levelNode,
}
}
levelNode = levelNode.Right
subtreeLeafCount -= maxSubtreeLeafCount / 2
}
maxSubtreeLeafCount /= 2
}
return levelNode
}
// AddLeaf adds a leaf to the binary merkle tree.
func (m *BinaryMerkleTree) AddLeaf(leaf types.Leaf) {
// Walk down to the new max leaf node.
m.LeafCount += 1
levelNode := m.walkDownToLeafCount(m.LeafCount)
// Set the leaf node data.
levelNode.Label = leaf.Hash()
// Walk back up the tree, updating the hashes with its sibling hash.
for height := 0; height < BinaryMerkleTreeDepth; height++ {
if levelNode.Parent.IsLeftChild(levelNode) {
if levelNode.Parent.Right == nil {
levelNode.Parent.Right = &merkleNode{
Label: zeroHashes[height],
Parent: levelNode.Parent,
}
}
levelNode.Parent.Label = crypto.Keccak256Hash(levelNode.Label[:], levelNode.Parent.Right.Label[:])
} else {
if levelNode.Parent.Left == nil {
levelNode.Parent.Left = &merkleNode{
Label: zeroHashes[height],
Parent: levelNode.Parent,
}
}
levelNode.Parent.Label = crypto.Keccak256Hash(levelNode.Parent.Left.Label[:], levelNode.Label[:])
}
levelNode = levelNode.Parent
}
}
// ProofAtIndex returns a merkle proof at the given leaf node index.
func (m *BinaryMerkleTree) ProofAtIndex(index uint64) (proof Proof, err error) {
if index >= uint64(MaxLeafCount) {
return proof, IndexOutOfBoundsError
}
levelNode := m.walkDownToLeafCount(int(index) + 1)
for height := 0; height < BinaryMerkleTreeDepth; height++ {
if levelNode.Parent.IsLeftChild(levelNode) {
if levelNode.Parent.Right == nil {
proof[height] = common.Hash{}
} else {
proof[height] = levelNode.Parent.Right.Label
}
} else {
if levelNode.Parent.Left == nil {
proof[height] = common.Hash{}
} else {
proof[height] = levelNode.Parent.Left.Label
}
}
levelNode = levelNode.Parent
}
return proof, nil
}
package merkle
import (
"bytes"
"fmt"
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
func TestBinaryMerkleTree_AddLeaf(t *testing.T) {
var tests []struct {
name string
leafCount int
}
// Test only three leaf counts since this requires adding a lot of leaves.
// To test more thoroughly, increase the divisor and run locally.
for i := 0; i < MaxLeafCount; i += MaxLeafCount / 3 {
tests = append(tests, struct {
name string
leafCount int
}{
name: fmt.Sprintf("AddLeaf-%d", i),
leafCount: i,
})
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
tree := NewBinaryMerkleTree()
expectedLeafHash := zeroHashes[BinaryMerkleTreeDepth-1]
for i := 0; i < test.leafCount; i++ {
input := ([types.BlockSize]byte)(bytes.Repeat([]byte{byte(i)}, types.BlockSize))
lastLeaf := types.Leaf{
Input: input,
Index: big.NewInt(int64(i)),
StateCommitment: common.Hash{},
}
tree.AddLeaf(lastLeaf)
expectedLeafHash = lastLeaf.Hash()
}
leaf := tree.walkDownToLeafCount(tree.LeafCount)
require.Equal(t, expectedLeafHash, leaf.Label)
})
}
}
func TestBinaryMerkleTree_RootHash(t *testing.T) {
tests := []struct {
name string
leafCount int
rootHash common.Hash
}{
{
name: "EmptyBinaryMerkleTree",
leafCount: 0,
rootHash: common.HexToHash("2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f"),
},
{
name: "SingleLeaf",
leafCount: 1,
rootHash: common.HexToHash("de8451f1c4f0153718b46951d0764a63e979fa13d496e709cceafcdbbe4ae68c"),
},
{
name: "TwoLeaves",
leafCount: 2,
rootHash: common.HexToHash("caa0130e02ef997ebab07643394f7fa90767a68c49170669a9262573bfc46116"),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
tree := NewBinaryMerkleTree()
for i := 0; i < test.leafCount; i++ {
input := ([types.BlockSize]byte)(bytes.Repeat([]byte{byte(i)}, types.BlockSize))
tree.AddLeaf(types.Leaf{
Input: input,
Index: big.NewInt(int64(i)),
StateCommitment: common.Hash{},
})
}
require.Equal(t, test.rootHash, tree.RootHash())
})
}
}
func TestBinaryMerkleTree_ProofAtIndex(t *testing.T) {
proof := Proof{}
for i := 0; i < BinaryMerkleTreeDepth; i++ {
proof[i] = common.Hash{}
}
tests := []struct {
name string
leafCount int
index int
proof Proof
}{
{
name: "EmptyBinaryMerkleTree",
leafCount: 0,
index: 0,
proof: proof,
},
{
name: "SingleLeaf_ZeroIndex",
leafCount: 1,
index: 0,
proof: Proof{
common.HexToHash("0000000000000000000000000000000000000000000000000000000000000000"),
common.HexToHash("ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5"),
common.HexToHash("b4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30"),
common.HexToHash("21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85"),
common.HexToHash("e58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344"),
common.HexToHash("0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d"),
common.HexToHash("887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968"),
common.HexToHash("ffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83"),
common.HexToHash("9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af"),
common.HexToHash("cefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0"),
common.HexToHash("f9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5"),
common.HexToHash("f8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892"),
common.HexToHash("3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c"),
common.HexToHash("c1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb"),
common.HexToHash("5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc"),
common.HexToHash("da7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2"),
},
},
{
name: "SingleLeaf_FirstIndex",
leafCount: 1,
index: 1,
proof: Proof{
common.HexToHash("cfd23b6298abaea12ade48cd472295893b7facf37c92f425e50722a72ed084ac"),
common.HexToHash("ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5"),
common.HexToHash("b4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30"),
common.HexToHash("21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85"),
common.HexToHash("e58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344"),
common.HexToHash("0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d"),
common.HexToHash("887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968"),
common.HexToHash("ffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83"),
common.HexToHash("9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af"),
common.HexToHash("cefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0"),
common.HexToHash("f9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5"),
common.HexToHash("f8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892"),
common.HexToHash("3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c"),
common.HexToHash("c1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb"),
common.HexToHash("5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc"),
common.HexToHash("da7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2"),
},
},
{
name: "PartialTree_EvenMiddleIndex",
leafCount: 20,
index: 10,
proof: Proof{
common.HexToHash("30009b412aade7d8309511a4408ae2f4b72573dde601905693af9f3abb2e1dc8"),
common.HexToHash("a7ca9b77295eadcfe6d58ef5e86e88432a0417b36ac6e4ab2d2e9d45702292e5"),
common.HexToHash("0a8eb56a75e17742db02f5de94120005cfe95e26c80891de57e390b3e6a3ebc5"),
common.HexToHash("c9909a93d2c0248ef490da737dabda9e41eb3d9a379ddb004cfe66c60f3072df"),
common.HexToHash("a063b1a2583a43114cd150b3c2320fe0bccf5f1b0f75c92c9d7e55433a291517"),
common.HexToHash("0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d"),
common.HexToHash("887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968"),
common.HexToHash("ffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83"),
common.HexToHash("9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af"),
common.HexToHash("cefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0"),
common.HexToHash("f9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5"),
common.HexToHash("f8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892"),
common.HexToHash("3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c"),
common.HexToHash("c1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb"),
common.HexToHash("5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc"),
common.HexToHash("da7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2"),
},
},
{
name: "PartialTree_OddMiddleIndex",
leafCount: 20,
index: 11,
proof: Proof{
common.HexToHash("536c2dfcd1e4d209e13b2e17323dc3d71171114b4ea9481dcfaac0361eeaffae"),
common.HexToHash("a7ca9b77295eadcfe6d58ef5e86e88432a0417b36ac6e4ab2d2e9d45702292e5"),
common.HexToHash("0a8eb56a75e17742db02f5de94120005cfe95e26c80891de57e390b3e6a3ebc5"),
common.HexToHash("c9909a93d2c0248ef490da737dabda9e41eb3d9a379ddb004cfe66c60f3072df"),
common.HexToHash("a063b1a2583a43114cd150b3c2320fe0bccf5f1b0f75c92c9d7e55433a291517"),
common.HexToHash("0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d"),
common.HexToHash("887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968"),
common.HexToHash("ffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83"),
common.HexToHash("9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af"),
common.HexToHash("cefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0"),
common.HexToHash("f9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5"),
common.HexToHash("f8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892"),
common.HexToHash("3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c"),
common.HexToHash("c1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb"),
common.HexToHash("5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc"),
common.HexToHash("da7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2"),
},
},
{
name: "PartialTree_SecondLastIndex",
leafCount: MaxLeafCount,
index: MaxLeafCount - 2,
proof: Proof{
common.HexToHash("a7ead3155af3540785a09632e1a9a0e905700dddc29ca3531643209d11abde34"),
common.HexToHash("2510bc5d56f49d7f5cdce4e37424936b31f7a01ba27c0201b40bd00d7241f525"),
common.HexToHash("69251e33d3beeac7d24417e095adf8994eca90f7b220477b7dbdf7833ed5c646"),
common.HexToHash("37e281b275aa30e45a87ad2cfb8fbd6402fca47633dd698c519595e1360714dd"),
common.HexToHash("5fdae72b17eea7ab12066760cfe8dff858665ece3b5ec7ea30fb88737bc63e00"),
common.HexToHash("6657aa64e042b55b6915c5ac65c9f07e29e1e67466d159ab8432f15e7088ded6"),
common.HexToHash("a689859b6b6f0b586d267b5228b3d07c169889b219a49231386c4ed6c934df6e"),
common.HexToHash("b9d17d1a81bcc1e9434e517008e3f68e2e19ab73b464ca13885c9e3b1072fd0c"),
common.HexToHash("af79e21764536f60b54298ba40da1d16fe432510c40d54e8d6d12f4c5491418d"),
common.HexToHash("0071a9f78633e4bf4f008134694b97783ffdf7651bf09096bce74b1cc77c9254"),
common.HexToHash("7d0475c42ed689be225ec7de3d6f620f5afa50d17223d3cf3b5faebe6b8eab63"),
common.HexToHash("7efcb6ac59e3ebd6cb62985827c634e88a9cf118a906d2d25f0facf44f8ce48d"),
common.HexToHash("0c2264f5c766dafd9ed23277449b2affe176e7ffeef29dad949932654df90f17"),
common.HexToHash("0622a9974a31b7774c1b89e494663f008a82d525dd00ac6fbaf322ae9845261a"),
common.HexToHash("882284d7f31dec4cadb4b46d2209ef7806e440d541f1d6efd2e1b53a32ee9e65"),
common.HexToHash("fbcf92d99fd2e103e15b1647b177895b0529dd715e551a80c90744818458a8e3"),
},
},
{
name: "FullTree_LastIndex",
leafCount: MaxLeafCount,
index: MaxLeafCount - 1,
proof: Proof{
common.HexToHash("0000000000000000000000000000000000000000000000000000000000000000"),
common.HexToHash("5d5992fd072e73c425c84efbd29e9e4a87756a67d972f096661c967583809c8f"),
common.HexToHash("69251e33d3beeac7d24417e095adf8994eca90f7b220477b7dbdf7833ed5c646"),
common.HexToHash("37e281b275aa30e45a87ad2cfb8fbd6402fca47633dd698c519595e1360714dd"),
common.HexToHash("5fdae72b17eea7ab12066760cfe8dff858665ece3b5ec7ea30fb88737bc63e00"),
common.HexToHash("6657aa64e042b55b6915c5ac65c9f07e29e1e67466d159ab8432f15e7088ded6"),
common.HexToHash("a689859b6b6f0b586d267b5228b3d07c169889b219a49231386c4ed6c934df6e"),
common.HexToHash("b9d17d1a81bcc1e9434e517008e3f68e2e19ab73b464ca13885c9e3b1072fd0c"),
common.HexToHash("af79e21764536f60b54298ba40da1d16fe432510c40d54e8d6d12f4c5491418d"),
common.HexToHash("0071a9f78633e4bf4f008134694b97783ffdf7651bf09096bce74b1cc77c9254"),
common.HexToHash("7d0475c42ed689be225ec7de3d6f620f5afa50d17223d3cf3b5faebe6b8eab63"),
common.HexToHash("7efcb6ac59e3ebd6cb62985827c634e88a9cf118a906d2d25f0facf44f8ce48d"),
common.HexToHash("0c2264f5c766dafd9ed23277449b2affe176e7ffeef29dad949932654df90f17"),
common.HexToHash("0622a9974a31b7774c1b89e494663f008a82d525dd00ac6fbaf322ae9845261a"),
common.HexToHash("882284d7f31dec4cadb4b46d2209ef7806e440d541f1d6efd2e1b53a32ee9e65"),
common.HexToHash("fbcf92d99fd2e103e15b1647b177895b0529dd715e551a80c90744818458a8e3"),
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
tree := NewBinaryMerkleTree()
for i := 0; i < test.leafCount; i++ {
input := ([types.BlockSize]byte)(bytes.Repeat([]byte{byte(i)}, types.BlockSize))
tree.AddLeaf(types.Leaf{
Input: input,
Index: big.NewInt(int64(i)),
StateCommitment: common.Hash{},
})
}
proof, err := tree.ProofAtIndex(uint64(test.index))
require.NoError(t, err)
require.Equal(t, test.proof, proof)
})
}
}
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"github.com/ethereum-optimism/optimism/op-service/sources/batching" "github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
) )
// BlockSize is the size in bytes required for leaf data. // BlockSize is the size in bytes required for leaf data.
...@@ -21,6 +22,16 @@ type Leaf struct { ...@@ -21,6 +22,16 @@ type Leaf struct {
StateCommitment common.Hash StateCommitment common.Hash
} }
// Hash returns the hash of the leaf data. That is the
// bytewise concatenation of the input, index, and state commitment.
func (l *Leaf) Hash() common.Hash {
concatted := make([]byte, 0, 136+32+32)
concatted = append(concatted, l.Input[:]...)
concatted = append(concatted, l.Index.Bytes()...)
concatted = append(concatted, l.StateCommitment.Bytes()...)
return crypto.Keccak256Hash(concatted)
}
// InputData is a contiguous segment of preimage data. // InputData is a contiguous segment of preimage data.
type InputData struct { type InputData struct {
// Input is the preimage data. // Input is the preimage data.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment