Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
72 changes: 72 additions & 0 deletions op-chain-ops/crossdomain/encoding.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package crossdomain

import (
"math/big"
"strings"

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

var (
// NonceMask represents a mask used to extract version bytes from the nonce
NonceMask, _ = new(big.Int).SetString("0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16)
// relayMessage0ABI represents the v0 relay message encoding
relayMessage0ABI = "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_target\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_sender\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"_message\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"_messageNonce\",\"type\":\"uint256\"}],\"name\":\"relayMessage\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]"
// relayMessage1ABI represents the v1 relay message encoding
relayMessage1ABI = "[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_nonce\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"_sender\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_target\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_value\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_minGasLimit\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"_message\",\"type\":\"bytes\"}],\"name\":\"relayMessage\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"}]"
// relayMessage0 represents the ABI of relay message v0
relayMessage0 abi.ABI
// relayMessage1 represents the ABI of relay message v1
relayMessage1 abi.ABI
)

// Create the required ABIs
func init() {
var err error
relayMessage0, err = abi.JSON(strings.NewReader(relayMessage0ABI))
if err != nil {
panic(err)
}
relayMessage1, err = abi.JSON(strings.NewReader(relayMessage1ABI))
if err != nil {
panic(err)
}
}

// EncodeCrossDomainMessageV0 will encode the calldata for
// "relayMessage(address,address,bytes,uint256)",
func EncodeCrossDomainMessageV0(
target *common.Address,
sender *common.Address,
message []byte,
nonce *big.Int,
) ([]byte, error) {
return relayMessage0.Pack("relayMessage", target, sender, message, nonce)
}

// EncodeCrossDomainMessageV1 will encode the calldata for
// "relayMessage(uint256,address,address,uint256,uint256,bytes)",
func EncodeCrossDomainMessageV1(
nonce *big.Int,
sender *common.Address,
target *common.Address,
value *big.Int,
gasLimit *big.Int,
data []byte,
) ([]byte, error) {
return relayMessage1.Pack("relayMessage", nonce, sender, target, value, gasLimit, data)
}

// DecodeVersionedNonce will decode the version that is encoded in the nonce
func DecodeVersionedNonce(versioned *big.Int) (*big.Int, *big.Int) {
nonce := new(big.Int).And(versioned, NonceMask)
version := new(big.Int).Rsh(versioned, 240)
return nonce, version
}

// EncodeVersionedNonce will encode the version into the nonce
func EncodeVersionedNonce(nonce, version *big.Int) *big.Int {
shifted := new(big.Int).Lsh(version, 240)
return new(big.Int).Or(nonce, shifted)
}
30 changes: 30 additions & 0 deletions op-chain-ops/crossdomain/encoding_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package crossdomain_test

import (
"math/big"
"testing"

"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/stretchr/testify/require"
)

func FuzzVersionedNonce(f *testing.F) {
f.Fuzz(func(t *testing.T, _nonce []byte, _version uint16) {
inputNonce := new(big.Int).SetBytes(_nonce)

// Clamp nonce to uint240
if inputNonce.Cmp(crossdomain.NonceMask) > 0 {
inputNonce = new(big.Int).Set(crossdomain.NonceMask)
}
// Clamp version to 0 or 1
_version = _version % 2

inputVersion := new(big.Int).SetUint64(uint64(_version))
encodedNonce := crossdomain.EncodeVersionedNonce(inputNonce, inputVersion)

decodedNonce, decodedVersion := crossdomain.DecodeVersionedNonce(encodedNonce)

require.Equal(t, decodedNonce.Uint64(), inputNonce.Uint64())
require.Equal(t, decodedVersion.Uint64(), inputVersion.Uint64())
})
}
42 changes: 42 additions & 0 deletions op-chain-ops/crossdomain/hashing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package crossdomain

import (
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)

// HashCrossDomainMessageV0 computes the pre bedrock cross domain messaging
// hashing scheme.
func HashCrossDomainMessageV0(
target *common.Address,
sender *common.Address,
data []byte,
nonce *big.Int,
) (common.Hash, error) {
encoded, err := EncodeCrossDomainMessageV0(target, sender, data, nonce)
if err != nil {
return common.Hash{}, err
}
hash := crypto.Keccak256(encoded)
return common.BytesToHash(hash), nil
}

// HashCrossDomainMessageV1 computes the first post bedrock cross domain
// messaging hashing scheme.
func HashCrossDomainMessageV1(
nonce *big.Int,
sender *common.Address,
target *common.Address,
value *big.Int,
gasLimit *big.Int,
data []byte,
) (common.Hash, error) {
encoded, err := EncodeCrossDomainMessageV1(nonce, sender, target, value, gasLimit, data)
if err != nil {
return common.Hash{}, err
}
hash := crypto.Keccak256(encoded)
return common.BytesToHash(hash), nil
}
72 changes: 72 additions & 0 deletions op-chain-ops/crossdomain/message.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package crossdomain

import (
"fmt"
"math/big"

"github.com/ethereum/go-ethereum/common"
)

// CrossDomainMessage represents a cross domain message
// used by the CrossDomainMessenger. The version is encoded
// in the nonce. Version 0 messages do not have a value,
// version 1 messages have a value and the most significant
// byte of the nonce is a 1
type CrossDomainMessage struct {
Nonce *big.Int
Sender *common.Address
Target *common.Address
Value *big.Int
GasLimit *big.Int
Data []byte
}

// NewCrossDomainMessage creates a CrossDomainMessage.
func NewCrossDomainMessage(
nonce *big.Int,
sender, target *common.Address,
value, gasLimit *big.Int,
data []byte,
) *CrossDomainMessage {
return &CrossDomainMessage{
Nonce: nonce,
Sender: sender,
Target: target,
Value: value,
GasLimit: gasLimit,
Data: data,
}
}

// Version will return the version of the CrossDomainMessage.
// It does this by looking at the first byte of the nonce.
func (c *CrossDomainMessage) Version() uint64 {
_, version := DecodeVersionedNonce(c.Nonce)
return version.Uint64()
}

// Encode will encode a cross domain message based on the version.
func (c *CrossDomainMessage) Encode() ([]byte, error) {
version := c.Version()
switch version {
case 0:
return EncodeCrossDomainMessageV0(c.Target, c.Sender, c.Data, c.Nonce)
case 1:
return EncodeCrossDomainMessageV1(c.Nonce, c.Sender, c.Target, c.Value, c.GasLimit, c.Data)
default:
return nil, fmt.Errorf("unknown nonce version %d", version)
}
}

// Hash will compute the hash of the CrossDomainMessage
func (c *CrossDomainMessage) Hash() (common.Hash, error) {
version := c.Version()
switch version {
case 0:
return HashCrossDomainMessageV0(c.Target, c.Sender, c.Data, c.Nonce)
case 1:
return HashCrossDomainMessageV1(c.Nonce, c.Sender, c.Target, c.Value, c.GasLimit, c.Data)
default:
return common.Hash{}, fmt.Errorf("unknown nonce version %d", version)
}
}
100 changes: 100 additions & 0 deletions op-chain-ops/crossdomain/message_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package crossdomain_test

import (
"math/big"
"testing"

"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/stretchr/testify/require"
)

// TestEncode tests the encoding of a CrossDomainMessage. The assertion was
// created using solidity.
func TestEncode(t *testing.T) {
t.Parallel()

t.Run("V0", func(t *testing.T) {
msg := crossdomain.NewCrossDomainMessage(
crossdomain.EncodeVersionedNonce(common.Big0, common.Big0),
&common.Address{},
&common.Address{19: 0x01},
big.NewInt(0),
big.NewInt(5),
[]byte{},
)

require.Equal(t, uint64(0), msg.Version())

encoded, err := msg.Encode()
require.Nil(t, err)

expect := hexutil.MustDecode("0xcbd4ece900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
require.Equal(t, expect, encoded)
})

t.Run("V1", func(t *testing.T) {
msg := crossdomain.NewCrossDomainMessage(
crossdomain.EncodeVersionedNonce(common.Big1, common.Big1),
&common.Address{19: 0x01},
&common.Address{19: 0x02},
big.NewInt(100),
big.NewInt(555),
[]byte{},
)

require.Equal(t, uint64(1), msg.Version())

encoded, err := msg.Encode()
require.Nil(t, err)

expect := hexutil.MustDecode("0xd764ad0b0001000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000022b00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000")

require.Equal(t, expect, encoded)
})
}

// TestEncode tests the hash of a CrossDomainMessage. The assertion was
// created using solidity.
func TestHash(t *testing.T) {
t.Parallel()

t.Run("V0", func(t *testing.T) {
msg := crossdomain.NewCrossDomainMessage(
crossdomain.EncodeVersionedNonce(common.Big0, common.Big0),
&common.Address{},
&common.Address{19: 0x01},
big.NewInt(10),
big.NewInt(5),
[]byte{},
)

require.Equal(t, uint64(0), msg.Version())

hash, err := msg.Hash()
require.Nil(t, err)

expect := common.HexToHash("0x5bb579a193681e7c4d43c8c2e4bc6c2c447d21ef9fa887ca23b2d3f9a0fac065")
require.Equal(t, expect, hash)
})

t.Run("V1", func(t *testing.T) {
msg := crossdomain.NewCrossDomainMessage(
crossdomain.EncodeVersionedNonce(common.Big0, common.Big1),
&common.Address{},
&common.Address{19: 0x01},
big.NewInt(0),
big.NewInt(5),
[]byte{},
)

require.Equal(t, uint64(1), msg.Version())

hash, err := msg.Hash()
require.Nil(t, err)

expect := common.HexToHash("0x09bbda7f59cdaccab5c41cab4600bd458b2bd7d9f8410f13316fe07e5f4237cc")
require.Equal(t, expect, hash)
})
}