Commit c68fd363 authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

op-challenger: Implement keccak padding. (#9272)

* op-challenger: Implement keccak padding.

* op-challenger: Add verification to unit test that generated challenge has a StateMatrix preimage that matches the prestate leaf.

* op-challenger: Fix encoding of state matrix.
parent c874ecc9
......@@ -147,16 +147,11 @@ func (c *PreimageOracleContract) Squeeze(
// abiEncodeStateMatrix encodes the state matrix for the contract ABI
func abiEncodeStateMatrix(stateMatrix *matrix.StateMatrix) bindings.LibKeccakStateMatrix {
return abiEncodePackedState(stateMatrix.PackState())
return abiEncodeSnapshot(stateMatrix.StateSnapshot())
}
func abiEncodePackedState(packedState []byte) bindings.LibKeccakStateMatrix {
stateSlice := new([25]uint64)
// SAFETY: a maximum of 25 * 8 bytes will be read from packedState and written to stateSlice
for i := 0; i < min(len(packedState), 25*8); i += 8 {
stateSlice[i/8] = new(big.Int).SetBytes(packedState[i : i+8]).Uint64()
}
return bindings.LibKeccakStateMatrix{State: *stateSlice}
func abiEncodeSnapshot(packedState keccakTypes.StateSnapshot) bindings.LibKeccakStateMatrix {
return bindings.LibKeccakStateMatrix{State: packedState}
}
func (c *PreimageOracleContract) GetActivePreimages(ctx context.Context, blockHash common.Hash) ([]keccakTypes.LargePreimageMetaData, error) {
......@@ -271,7 +266,7 @@ func (c *PreimageOracleContract) ChallengeTx(ident keccakTypes.LargePreimageIden
methodChallengeLPP,
ident.Claimant,
ident.UUID,
abiEncodePackedState(challenge.StateMatrix),
abiEncodeSnapshot(challenge.StateMatrix),
toPreimageOracleLeaf(challenge.Prestate),
challenge.PrestateProof,
toPreimageOracleLeaf(challenge.Poststate),
......
package contracts
import (
"bytes"
"context"
"fmt"
"math"
......@@ -503,7 +502,7 @@ func TestChallenge_First(t *testing.T) {
UUID: big.NewInt(4829),
}
challenge := keccakTypes.Challenge{
StateMatrix: []byte{1, 2, 3, 4, 5},
StateMatrix: keccakTypes.StateSnapshot{1, 2, 3, 4, 5},
Prestate: keccakTypes.Leaf{},
Poststate: keccakTypes.Leaf{
Input: [136]byte{5, 4, 3, 2, 1},
......@@ -536,7 +535,7 @@ func TestChallenge_NotFirst(t *testing.T) {
UUID: big.NewInt(4829),
}
challenge := keccakTypes.Challenge{
StateMatrix: bytes.Repeat([]byte{1, 2, 3, 4, 5, 6, 7, 8}, 25),
StateMatrix: keccakTypes.StateSnapshot{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25},
Prestate: keccakTypes.Leaf{
Input: [136]byte{9, 8, 7, 6, 5},
Index: 3,
......@@ -553,7 +552,7 @@ func TestChallenge_NotFirst(t *testing.T) {
stubRpc.SetResponse(oracleAddr, methodChallengeLPP, batching.BlockLatest,
[]interface{}{
ident.Claimant, ident.UUID,
abiEncodePackedState(challenge.StateMatrix),
bindings.LibKeccakStateMatrix{State: challenge.StateMatrix},
bindings.PreimageOracleLeaf{
Input: challenge.Prestate.Input[:],
Index: new(big.Int).SetUint64(challenge.Prestate.Index),
......
package preimages
import (
"bytes"
"context"
"errors"
"io"
"math/big"
"testing"
"time"
......@@ -204,61 +206,21 @@ func TestLargePreimageUploader_UploadPreimage_Succeeds(t *testing.T) {
name: "FullLeaf",
input: fullLeaf[:],
addCalls: 1,
prestateLeaf: keccakTypes.Leaf{
Input: *fullLeaf,
Index: 0,
StateCommitment: common.HexToHash("9788a3b3bc36c482525b5890767be37130c997917bceca6e91a6c93359a4d1c6"),
},
poststateLeaf: keccakTypes.Leaf{
Input: [keccakTypes.BlockSize]byte{},
Index: 1,
StateCommitment: common.HexToHash("78358b902b7774b314bcffdf0948746f18d6044086e76e3924d585dca3486c7d"),
},
},
{
name: "MultipleLeaves",
input: append(fullLeaf[:], append(fullLeaf[:], fullLeaf[:]...)...),
addCalls: 1,
prestateLeaf: keccakTypes.Leaf{
Input: *fullLeaf,
Index: 2,
StateCommitment: common.HexToHash("e3deed8ab6f8bbcf3d4fe825d74f703b3f2fc2f5b0afaa2574926fcfd0d4c895"),
},
poststateLeaf: keccakTypes.Leaf{
Input: [keccakTypes.BlockSize]byte{},
Index: 3,
StateCommitment: common.HexToHash("79115eeab1ff2eccf5baf3ea2dda13bc79c548ce906bdd16433a23089c679df2"),
},
},
{
name: "MultipleLeavesUnaligned",
input: append(fullLeaf[:], append(fullLeaf[:], byte(9))...),
addCalls: 1,
prestateLeaf: keccakTypes.Leaf{
Input: *fullLeaf,
Index: 1,
StateCommitment: common.HexToHash("b5ea400e375b2c1ce348f3cc4ad5b6ad28e1b36759ddd2aba155f0b1d476b015"),
},
poststateLeaf: keccakTypes.Leaf{
Input: [keccakTypes.BlockSize]byte{byte(9)},
Index: 2,
StateCommitment: common.HexToHash("fa87e115dc4786e699bf80cc75d13ac1e2db0708c1418fc8cbc9800d17b5811a"),
},
},
{
name: "MultipleChunks",
input: append(chunk, append(fullLeaf[:], fullLeaf[:]...)...),
addCalls: 2,
prestateLeaf: keccakTypes.Leaf{
Input: *fullLeaf,
Index: 301,
StateCommitment: common.HexToHash("4e9c55542478939feca4ff55ee98fbc632bb65a784a55b94536644bc87298ca4"),
},
poststateLeaf: keccakTypes.Leaf{
Input: [keccakTypes.BlockSize]byte{},
Index: 302,
StateCommitment: common.HexToHash("775020bfcaa93700263d040a4eeec3c8c3cf09e178457d04044594beaaf5e20b"),
},
},
}
......@@ -275,8 +237,16 @@ func TestLargePreimageUploader_UploadPreimage_Succeeds(t *testing.T) {
// for successful large preimage upload calls.
require.Equal(t, 1, contract.initCalls)
require.Equal(t, 1, contract.squeezeCalls)
require.Equal(t, test.prestateLeaf, contract.squeezePrestate)
require.Equal(t, test.poststateLeaf, contract.squeezePoststate)
// Use the StateMatrix to determine the expected leaves so it includes padding correctly.
// We rely on the unit tests for StateMatrix to confirm that it does the right thing.
s := matrix.NewStateMatrix()
_, err = s.AbsorbUpTo(bytes.NewReader(test.input), keccakTypes.BlockSize*10000)
require.ErrorIs(t, err, io.EOF)
prestate, _ := s.PrestateWithProof()
poststate, _ := s.PoststateWithProof()
require.Equal(t, prestate, contract.squeezePrestate)
require.Equal(t, poststate, contract.squeezePoststate)
})
}
......
......@@ -60,6 +60,7 @@ func (c *PreimageChallenger) Challenge(ctx context.Context, blockHash common.Has
logger.Error("Failed to verify large preimage", "err", err)
return
}
logger.Info("Challenging preimage", "block", challenge.Poststate.Index)
tx, err := oracle.ChallengeTx(preimage.LargePreimageIdent, challenge)
if err != nil {
logger.Error("Failed to create challenge transaction", "err", err)
......
......@@ -43,8 +43,8 @@ func TestChallenge(t *testing.T) {
t.Run("SendChallenges", func(t *testing.T) {
verifier, sender, oracle, challenger := setupChallengerTest(logger)
verifier.challenges[preimages[1].LargePreimageIdent] = keccakTypes.Challenge{StateMatrix: []byte{0x01}}
verifier.challenges[preimages[2].LargePreimageIdent] = keccakTypes.Challenge{StateMatrix: []byte{0x02}}
verifier.challenges[preimages[1].LargePreimageIdent] = keccakTypes.Challenge{StateMatrix: keccakTypes.StateSnapshot{0x01}}
verifier.challenges[preimages[2].LargePreimageIdent] = keccakTypes.Challenge{StateMatrix: keccakTypes.StateSnapshot{0x02}}
err := challenger.Challenge(context.Background(), common.Hash{0xaa}, oracle, preimages)
require.NoError(t, err)
......@@ -59,7 +59,7 @@ func TestChallenge(t *testing.T) {
t.Run("ReturnErrorWhenSendingFails", func(t *testing.T) {
verifier, sender, oracle, challenger := setupChallengerTest(logger)
verifier.challenges[preimages[1].LargePreimageIdent] = keccakTypes.Challenge{StateMatrix: []byte{0x01}}
verifier.challenges[preimages[1].LargePreimageIdent] = keccakTypes.Challenge{StateMatrix: keccakTypes.StateSnapshot{0x01}}
sender.err = errors.New("boom")
err := challenger.Challenge(context.Background(), common.Hash{0xaa}, oracle, preimages)
require.ErrorIs(t, err, sender.err)
......@@ -69,7 +69,7 @@ func TestChallenge(t *testing.T) {
logs := testlog.Capture(logger)
verifier, _, oracle, challenger := setupChallengerTest(logger)
verifier.challenges[preimages[1].LargePreimageIdent] = keccakTypes.Challenge{StateMatrix: []byte{0x01}}
verifier.challenges[preimages[1].LargePreimageIdent] = keccakTypes.Challenge{StateMatrix: keccakTypes.StateSnapshot{0x01}}
oracle.err = errors.New("boom")
err := challenger.Challenge(context.Background(), common.Hash{0xaa}, oracle, preimages)
require.NoError(t, err)
......@@ -82,7 +82,7 @@ func TestChallenge(t *testing.T) {
logs := testlog.Capture(logger)
verifier, _, oracle, challenger := setupChallengerTest(logger)
verifier.challenges[preimages[1].LargePreimageIdent] = keccakTypes.Challenge{StateMatrix: []byte{0x01}}
verifier.challenges[preimages[1].LargePreimageIdent] = keccakTypes.Challenge{StateMatrix: keccakTypes.StateSnapshot{0x01}}
verifier.err = errors.New("boom")
err := challenger.Challenge(context.Background(), common.Hash{0xaa}, oracle, preimages)
require.NoError(t, err)
......@@ -157,6 +157,6 @@ func (s *stubChallengerOracle) ChallengeTx(ident keccakTypes.LargePreimageIdent,
}
return txmgr.TxCandidate{
To: &ident.Claimant,
TxData: append(ident.UUID.Bytes(), challenge.StateMatrix...),
TxData: append(ident.UUID.Bytes(), challenge.StateMatrix.Pack()...),
}, nil
}
......@@ -4,12 +4,10 @@ import (
"errors"
"fmt"
"io"
"math/big"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/merkle"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto"
)
......@@ -30,14 +28,13 @@ var (
ErrInvalidMaxLen = errors.New("invalid max length to absorb")
ErrIncorrectCommitmentCount = errors.New("incorrect number of commitments for input length")
ErrValid = errors.New("state commitments are valid")
uint256Size = 32
)
// Challenge creates a [types.Challenge] to invalidate the provided preimage data if possible.
// [ErrValid] is returned if the provided inputs are valid and no challenge can be created.
func Challenge(data io.Reader, commitments []common.Hash) (types.Challenge, error) {
s := NewStateMatrix()
lastValidState := s.PackState()
lastValidState := s.StateSnapshot()
var lastValidLeaf types.Leaf
var firstInvalidLeaf types.Leaf
for i := 0; ; i++ {
......@@ -59,7 +56,7 @@ func Challenge(data io.Reader, commitments []common.Hash) (types.Challenge, erro
lastValidLeaf = s.prestateLeaf
firstInvalidLeaf = s.poststateLeaf
} else {
lastValidState = s.PackState()
lastValidState = s.StateSnapshot()
}
}
if isEOF {
......@@ -99,24 +96,23 @@ func NewStateMatrix() *StateMatrix {
// StateCommitment returns the state commitment for the current state matrix.
// Additional data may be absorbed after calling this method.
func (d *StateMatrix) StateCommitment() common.Hash {
buf := d.PackState()
return crypto.Keccak256Hash(buf)
return crypto.Keccak256Hash(d.StateSnapshot().Pack())
}
// PackState packs the state in to the solidity ABI encoding required for the state matrix
func (d *StateMatrix) PackState() []byte {
buf := make([]byte, 0, len(d.s.a)*uint256Size)
for _, v := range d.s.a {
buf = append(buf, math.U256Bytes(new(big.Int).SetUint64(v))...)
}
return buf
func (d *StateMatrix) StateSnapshot() types.StateSnapshot {
var snap types.StateSnapshot
copy(snap[:], d.s.a[:])
return snap
}
// newLeafWithPadding creates a new [Leaf] from inputs, padding the input to the [BlockSize].
func newLeafWithPadding(input []byte, index uint64, commitment common.Hash) types.Leaf {
// TODO(client-pod#480): Add actual keccak padding to ensure the merkle proofs are correct (for readData)
func (d *StateMatrix) newLeafWithPadding(input []byte, index uint64, commitment common.Hash, final bool) types.Leaf {
var paddedInput [types.BlockSize]byte
copy(paddedInput[:], input)
if final {
pad(input, &paddedInput, d.s.dsbyte)
}
return types.Leaf{
Input: paddedInput,
Index: index,
......@@ -124,6 +120,19 @@ func newLeafWithPadding(input []byte, index uint64, commitment common.Hash) type
}
}
func pad(input []byte, paddedInput *[types.BlockSize]byte, dsbyte byte) {
// Pad with this instance's domain-separator bits. We know that there's
// at least one more byte of space in paddedInput because, if it were full,
// this wouldn't be the last block and the padding would be in the next block.
// dsbyte also contains the first one bit for the padding. See the comment in the state struct.
paddedInput[len(input)] = dsbyte
// The remaining bytes are already zeros since paddedInput is a new array.
// This adds the final one bit for the padding. Because of the way that
// bits are numbered from the LSB upwards, the final bit is the MSB of
// the last byte.
paddedInput[types.BlockSize-1] ^= 0x80
}
func (d *StateMatrix) AbsorbUpTo(in io.Reader, maxLen int) (types.InputData, error) {
if maxLen < types.BlockSize || maxLen%types.BlockSize != 0 {
return types.InputData{}, ErrInvalidMaxLen
......@@ -193,10 +202,10 @@ func (d *StateMatrix) absorbNextLeafInput(in io.Reader, stateCommitment func() c
commitment := stateCommitment()
if d.poststateLeaf == (types.Leaf{}) {
d.prestateLeaf = types.Leaf{}
d.poststateLeaf = newLeafWithPadding(input, 0, commitment)
d.poststateLeaf = d.newLeafWithPadding(input, 0, commitment, final)
} else {
d.prestateLeaf = d.poststateLeaf
d.poststateLeaf = newLeafWithPadding(input, d.prestateLeaf.Index+1, commitment)
d.poststateLeaf = d.newLeafWithPadding(input, d.prestateLeaf.Index+1, commitment, final)
}
d.merkleTree.AddLeaf(d.poststateLeaf.Hash())
if final {
......
......@@ -45,7 +45,7 @@ func TestStateCommitment(t *testing.T) {
copy(state.s.a[:], test.matrix)
expected := crypto.Keccak256Hash(common.Hex2Bytes(test.expectedPacked))
actual := state.StateCommitment()
require.Equal(t, test.expectedPacked, common.Bytes2Hex(state.PackState()))
require.Equal(t, test.expectedPacked, common.Bytes2Hex(state.StateSnapshot().Pack()))
require.Equal(t, expected, actual)
})
}
......@@ -281,7 +281,11 @@ func TestVerifyPreimage(t *testing.T) {
}
leafData := func(idx int) (out [types.BlockSize]byte) {
end := min((idx+1)*types.BlockSize, len(preimage))
copy(out[:], preimage[idx*types.BlockSize:end])
input := preimage[idx*types.BlockSize : end]
copy(out[:], input)
if len(input) < types.BlockSize {
pad(input, &out, newLegacyKeccak256().dsbyte)
}
return
}
// merkleTree creates the final merkle tree after including all leaves.
......@@ -308,7 +312,7 @@ func TestVerifyPreimage(t *testing.T) {
prestateLeaf := leafData(invalidIdx - 1)
poststateLeaf := leafData(invalidIdx)
return types.Challenge{
StateMatrix: s.PackState(),
StateMatrix: s.StateSnapshot(),
Prestate: types.Leaf{
Input: prestateLeaf,
Index: uint64(invalidIdx - 1),
......@@ -348,7 +352,7 @@ func TestVerifyPreimage(t *testing.T) {
return incorrectFirstCommitment
},
expected: types.Challenge{
StateMatrix: NewStateMatrix().PackState(),
StateMatrix: NewStateMatrix().StateSnapshot(),
Prestate: types.Leaf{},
Poststate: types.Leaf{
Input: poststateLeaf,
......@@ -380,6 +384,9 @@ func TestVerifyPreimage(t *testing.T) {
require.ErrorIs(t, err, test.expectedErr)
require.Equal(t, test.expected.StateMatrix, challenge.StateMatrix, "Correct state matrix")
require.Equal(t, test.expected.Prestate, challenge.Prestate, "Correct prestate")
if test.expected.Prestate != (types.Leaf{}) {
require.Equal(t, test.expected.Prestate.StateCommitment, crypto.Keccak256Hash(challenge.StateMatrix.Pack()), "Prestate matches leaf commitment")
}
require.Equal(t, test.expected.PrestateProof, challenge.PrestateProof, "Correct prestate proof")
require.Equal(t, test.expected.Poststate, challenge.Poststate, "Correct poststate")
require.Equal(t, test.expected.PoststateProof, challenge.PoststateProof, "Correct poststate proof")
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -8,6 +8,7 @@ import (
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto"
)
......@@ -29,7 +30,7 @@ type Leaf struct {
func (l Leaf) Hash() common.Hash {
concatted := make([]byte, 0, 136+32+32)
concatted = append(concatted, l.Input[:]...)
concatted = append(concatted, new(big.Int).SetUint64(l.Index).Bytes()...)
concatted = append(concatted, math.U256Bytes(new(big.Int).SetUint64(l.Index))...)
concatted = append(concatted, l.StateCommitment.Bytes()...)
return crypto.Keccak256Hash(concatted)
}
......@@ -70,9 +71,20 @@ func (m LargePreimageMetaData) ShouldVerify() bool {
return m.Timestamp > 0 && !m.Countered
}
type StateSnapshot [25]uint64
// Pack packs the state in to the solidity ABI encoding required for the state matrix
func (s StateSnapshot) Pack() []byte {
buf := make([]byte, 0, len(s)*32)
for _, v := range s {
buf = append(buf, math.U256Bytes(new(big.Int).SetUint64(v))...)
}
return buf
}
type Challenge struct {
// StateMatrix is the packed state matrix preimage of the StateCommitment in Prestate
StateMatrix []byte // TODO(client-pod#480): Need a better representation of this
StateMatrix StateSnapshot // TODO(client-pod#480): Need a better representation of this
// Prestate is the valid leaf immediately prior to the first invalid leaf
Prestate Leaf
......
......@@ -3,7 +3,6 @@ package keccak
import (
"bytes"
"context"
"errors"
"fmt"
"io"
......@@ -18,8 +17,6 @@ type Fetcher interface {
FetchInputs(ctx context.Context, blockHash common.Hash, oracle fetcher.Oracle, ident keccakTypes.LargePreimageIdent) ([]keccakTypes.InputData, error)
}
var ErrNotImplemented = errors.New("verify implementation not complete")
type PreimageVerifier struct {
log log.Logger
fetcher Fetcher
......
......@@ -13,8 +13,6 @@ import (
)
func TestChallengeLargePreimages_ChallengeFirst(t *testing.T) {
// TODO(client-pod#480: Fix padding and make this pass
t.Skip("Padding not implemented properly yet")
op_e2e.InitParallel(t)
ctx := context.Background()
sys, _ := startFaultDisputeSystem(t)
......@@ -34,8 +32,6 @@ func TestChallengeLargePreimages_ChallengeFirst(t *testing.T) {
}
func TestChallengeLargePreimages_ChallengeMiddle(t *testing.T) {
// TODO(client-pod#480: Fix padding and make this pass
t.Skip("Padding not implemented properly yet")
op_e2e.InitParallel(t)
ctx := context.Background()
sys, _ := startFaultDisputeSystem(t)
......@@ -52,3 +48,21 @@ func TestChallengeLargePreimages_ChallengeMiddle(t *testing.T) {
preimageHelper.WaitForChallenged(ctx, ident)
}
func TestChallengeLargePreimages_ChallengeLast(t *testing.T) {
op_e2e.InitParallel(t)
ctx := context.Background()
sys, _ := startFaultDisputeSystem(t)
t.Cleanup(sys.Close)
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys)
disputeGameFactory.StartChallenger(ctx, "Challenger",
challenger.WithAlphabet(sys.RollupEndpoint("sequencer")),
challenger.WithPrivKey(sys.Cfg.Secrets.Mallory))
preimageHelper := disputeGameFactory.PreimageHelper(ctx)
ident := preimageHelper.UploadLargePreimage(ctx, preimage.MinPreimageSize,
preimage.WithReplacedCommitment(132, common.Hash{0xaa}))
require.NotEqual(t, ident.Claimant, common.Address{})
preimageHelper.WaitForChallenged(ctx, ident)
}
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