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
}
This diff is collapsed.
...@@ -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