Commit 4de5c8c2 authored by Minhyuk Kim's avatar Minhyuk Kim Committed by GitHub

op-signer, op-node: Integrate op-node with op-signer for block payload signing (#12325)

* Initial implementation of integrating op-node with op-signer for remote signer configuration for block payload signing

* op-service: remove the requirement for signer.address to be set when using op-service

* op-service: add blockpayload_args to send to rpc opsigner_signBlockPayload

* Implement mock rpc in gossip_test and apply review

* Clean up tests
parent 72ec8d3f
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-node/p2p" "github.com/ethereum-optimism/optimism/op-node/p2p"
opsigner "github.com/ethereum-optimism/optimism/op-service/signer"
) )
func p2pEnv(envprefix, v string) []string { func p2pEnv(envprefix, v string) []string {
...@@ -87,7 +88,7 @@ func deprecatedP2PFlags(envPrefix string) []cli.Flag { ...@@ -87,7 +88,7 @@ func deprecatedP2PFlags(envPrefix string) []cli.Flag {
// None of these flags are strictly required. // None of these flags are strictly required.
// Some are hidden if they are too technical, or not recommended. // Some are hidden if they are too technical, or not recommended.
func P2PFlags(envPrefix string) []cli.Flag { func P2PFlags(envPrefix string) []cli.Flag {
return []cli.Flag{ return append([]cli.Flag{
&cli.BoolFlag{ &cli.BoolFlag{
Name: DisableP2PName, Name: DisableP2PName,
Usage: "Completely disable the P2P stack", Usage: "Completely disable the P2P stack",
...@@ -410,5 +411,5 @@ func P2PFlags(envPrefix string) []cli.Flag { ...@@ -410,5 +411,5 @@ func P2PFlags(envPrefix string) []cli.Flag {
Required: false, Required: false,
EnvVars: p2pEnv(envPrefix, "PING"), EnvVars: p2pEnv(envPrefix, "PING"),
}, },
} }, opsigner.CLIFlags(envPrefix, P2PCategory)...)
} }
...@@ -5,18 +5,18 @@ import ( ...@@ -5,18 +5,18 @@ import (
"strings" "strings"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-node/flags" "github.com/ethereum-optimism/optimism/op-node/flags"
"github.com/ethereum-optimism/optimism/op-node/p2p" "github.com/ethereum-optimism/optimism/op-node/p2p"
opsigner "github.com/ethereum-optimism/optimism/op-service/signer"
) )
// TODO: implement remote signer setup (config to authenticated endpoint)
// and remote signer itself (e.g. a open http client to make signing requests)
// LoadSignerSetup loads a configuration for a Signer to be set up later // LoadSignerSetup loads a configuration for a Signer to be set up later
func LoadSignerSetup(ctx *cli.Context) (p2p.SignerSetup, error) { func LoadSignerSetup(ctx *cli.Context, logger log.Logger) (p2p.SignerSetup, error) {
key := ctx.String(flags.SequencerP2PKeyName) key := ctx.String(flags.SequencerP2PKeyName)
signerCfg := opsigner.ReadCLIConfig(ctx)
if key != "" { if key != "" {
// Mnemonics are bad because they leak *all* keys when they leak. // Mnemonics are bad because they leak *all* keys when they leak.
// Unencrypted keys from file are bad because they are easy to leak (and we are not checking file permissions). // Unencrypted keys from file are bad because they are easy to leak (and we are not checking file permissions).
...@@ -26,9 +26,13 @@ func LoadSignerSetup(ctx *cli.Context) (p2p.SignerSetup, error) { ...@@ -26,9 +26,13 @@ func LoadSignerSetup(ctx *cli.Context) (p2p.SignerSetup, error) {
} }
return &p2p.PreparedSigner{Signer: p2p.NewLocalSigner(priv)}, nil return &p2p.PreparedSigner{Signer: p2p.NewLocalSigner(priv)}, nil
} else if signerCfg.Enabled() {
remoteSigner, err := p2p.NewRemoteSigner(logger, signerCfg)
if err != nil {
return nil, err
}
return &p2p.PreparedSigner{Signer: remoteSigner}, nil
} }
// TODO: create remote signer
return nil, nil return nil, nil
} }
...@@ -3,31 +3,33 @@ package p2p ...@@ -3,31 +3,33 @@ package p2p
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/ecdsa"
"fmt" "fmt"
"io" "io"
"math/big" "math/big"
"testing" "testing"
"time" "time"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/golang/snappy" "github.com/golang/snappy"
// "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/eth"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
opsigner "github.com/ethereum-optimism/optimism/op-service/signer"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum-optimism/optimism/op-service/testutils" "github.com/ethereum-optimism/optimism/op-service/testutils"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
pubsub "github.com/libp2p/go-libp2p-pubsub" pubsub "github.com/libp2p/go-libp2p-pubsub"
pubsub_pb "github.com/libp2p/go-libp2p-pubsub/pb" pubsub_pb "github.com/libp2p/go-libp2p-pubsub/pb"
"github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/peer"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-service/testlog"
) )
func TestGuardGossipValidator(t *testing.T) { func TestGuardGossipValidator(t *testing.T) {
...@@ -62,30 +64,122 @@ func TestVerifyBlockSignature(t *testing.T) { ...@@ -62,30 +64,122 @@ func TestVerifyBlockSignature(t *testing.T) {
L2ChainID: big.NewInt(100), L2ChainID: big.NewInt(100),
} }
peerId := peer.ID("foo") peerId := peer.ID("foo")
secrets, err := e2eutils.DefaultMnemonicConfig.Secrets() secrets, err := crypto.GenerateKey()
require.NoError(t, err)
msg := []byte("any msg")
t.Run("Valid", func(t *testing.T) {
runCfg := &testutils.MockRuntimeConfig{P2PSeqAddress: crypto.PubkeyToAddress(secrets.PublicKey)}
signer := &PreparedSigner{Signer: NewLocalSigner(secrets)}
sig, err := signer.Sign(context.Background(), SigningDomainBlocksV1, cfg.L2ChainID, msg)
require.NoError(t, err)
result := verifyBlockSignature(logger, cfg, runCfg, peerId, sig[:], msg)
require.Equal(t, pubsub.ValidationAccept, result)
})
t.Run("WrongSigner", func(t *testing.T) {
runCfg := &testutils.MockRuntimeConfig{P2PSeqAddress: common.HexToAddress("0x1234")}
signer := &PreparedSigner{Signer: NewLocalSigner(secrets)}
sig, err := signer.Sign(context.Background(), SigningDomainBlocksV1, cfg.L2ChainID, msg)
require.NoError(t, err)
result := verifyBlockSignature(logger, cfg, runCfg, peerId, sig[:], msg)
require.Equal(t, pubsub.ValidationReject, result)
})
t.Run("InvalidSignature", func(t *testing.T) {
runCfg := &testutils.MockRuntimeConfig{P2PSeqAddress: crypto.PubkeyToAddress(secrets.PublicKey)}
sig := make([]byte, 65)
result := verifyBlockSignature(logger, cfg, runCfg, peerId, sig, msg)
require.Equal(t, pubsub.ValidationReject, result)
})
t.Run("NoSequencer", func(t *testing.T) {
runCfg := &testutils.MockRuntimeConfig{}
signer := &PreparedSigner{Signer: NewLocalSigner(secrets)}
sig, err := signer.Sign(context.Background(), SigningDomainBlocksV1, cfg.L2ChainID, msg)
require.NoError(t, err)
result := verifyBlockSignature(logger, cfg, runCfg, peerId, sig[:], msg)
require.Equal(t, pubsub.ValidationIgnore, result)
})
}
type mockRemoteSigner struct {
priv *ecdsa.PrivateKey
}
func (t *mockRemoteSigner) SignBlockPayload(args opsigner.BlockPayloadArgs) (hexutil.Bytes, error) {
signingHash, err := args.ToSigningHash()
if err != nil {
return nil, err
}
signature, err := crypto.Sign(signingHash[:], t.priv)
if err != nil {
return nil, err
}
return signature, nil
}
func TestVerifyBlockSignatureWithRemoteSigner(t *testing.T) {
secrets, err := crypto.GenerateKey()
require.NoError(t, err) require.NoError(t, err)
remoteSigner := &mockRemoteSigner{secrets}
server := oprpc.NewServer(
"127.0.0.1",
0,
"test",
oprpc.WithAPIs([]rpc.API{
{
Namespace: "opsigner",
Service: remoteSigner,
},
}),
)
require.NoError(t, server.Start())
defer func() {
_ = server.Stop()
}()
logger := testlog.Logger(t, log.LevelCrit)
cfg := &rollup.Config{
L2ChainID: big.NewInt(100),
}
peerId := peer.ID("foo")
msg := []byte("any msg") msg := []byte("any msg")
signerCfg := opsigner.NewCLIConfig()
signerCfg.Endpoint = fmt.Sprintf("http://%s", server.Endpoint())
signerCfg.TLSConfig.TLSKey = ""
signerCfg.TLSConfig.TLSCert = ""
signerCfg.TLSConfig.TLSCaCert = ""
signerCfg.TLSConfig.Enabled = false
t.Run("Valid", func(t *testing.T) { t.Run("Valid", func(t *testing.T) {
runCfg := &testutils.MockRuntimeConfig{P2PSeqAddress: crypto.PubkeyToAddress(secrets.SequencerP2P.PublicKey)} runCfg := &testutils.MockRuntimeConfig{P2PSeqAddress: crypto.PubkeyToAddress(secrets.PublicKey)}
signer := &PreparedSigner{Signer: NewLocalSigner(secrets.SequencerP2P)} remoteSigner, err := NewRemoteSigner(logger, signerCfg)
require.NoError(t, err)
signer := &PreparedSigner{Signer: remoteSigner}
sig, err := signer.Sign(context.Background(), SigningDomainBlocksV1, cfg.L2ChainID, msg) sig, err := signer.Sign(context.Background(), SigningDomainBlocksV1, cfg.L2ChainID, msg)
require.NoError(t, err) require.NoError(t, err)
result := verifyBlockSignature(logger, cfg, runCfg, peerId, sig[:65], msg) result := verifyBlockSignature(logger, cfg, runCfg, peerId, sig[:], msg)
require.Equal(t, pubsub.ValidationAccept, result) require.Equal(t, pubsub.ValidationAccept, result)
}) })
t.Run("WrongSigner", func(t *testing.T) { t.Run("WrongSigner", func(t *testing.T) {
runCfg := &testutils.MockRuntimeConfig{P2PSeqAddress: common.HexToAddress("0x1234")} runCfg := &testutils.MockRuntimeConfig{P2PSeqAddress: common.HexToAddress("0x1234")}
signer := &PreparedSigner{Signer: NewLocalSigner(secrets.SequencerP2P)} remoteSigner, err := NewRemoteSigner(logger, signerCfg)
require.NoError(t, err)
signer := &PreparedSigner{Signer: remoteSigner}
sig, err := signer.Sign(context.Background(), SigningDomainBlocksV1, cfg.L2ChainID, msg) sig, err := signer.Sign(context.Background(), SigningDomainBlocksV1, cfg.L2ChainID, msg)
require.NoError(t, err) require.NoError(t, err)
result := verifyBlockSignature(logger, cfg, runCfg, peerId, sig[:65], msg) result := verifyBlockSignature(logger, cfg, runCfg, peerId, sig[:], msg)
require.Equal(t, pubsub.ValidationReject, result) require.Equal(t, pubsub.ValidationReject, result)
}) })
t.Run("InvalidSignature", func(t *testing.T) { t.Run("InvalidSignature", func(t *testing.T) {
runCfg := &testutils.MockRuntimeConfig{P2PSeqAddress: crypto.PubkeyToAddress(secrets.SequencerP2P.PublicKey)} runCfg := &testutils.MockRuntimeConfig{P2PSeqAddress: crypto.PubkeyToAddress(secrets.PublicKey)}
sig := make([]byte, 65) sig := make([]byte, 65)
result := verifyBlockSignature(logger, cfg, runCfg, peerId, sig, msg) result := verifyBlockSignature(logger, cfg, runCfg, peerId, sig, msg)
require.Equal(t, pubsub.ValidationReject, result) require.Equal(t, pubsub.ValidationReject, result)
...@@ -93,12 +187,36 @@ func TestVerifyBlockSignature(t *testing.T) { ...@@ -93,12 +187,36 @@ func TestVerifyBlockSignature(t *testing.T) {
t.Run("NoSequencer", func(t *testing.T) { t.Run("NoSequencer", func(t *testing.T) {
runCfg := &testutils.MockRuntimeConfig{} runCfg := &testutils.MockRuntimeConfig{}
signer := &PreparedSigner{Signer: NewLocalSigner(secrets.SequencerP2P)} remoteSigner, err := NewRemoteSigner(logger, signerCfg)
require.NoError(t, err)
signer := &PreparedSigner{Signer: remoteSigner}
sig, err := signer.Sign(context.Background(), SigningDomainBlocksV1, cfg.L2ChainID, msg) sig, err := signer.Sign(context.Background(), SigningDomainBlocksV1, cfg.L2ChainID, msg)
require.NoError(t, err) require.NoError(t, err)
result := verifyBlockSignature(logger, cfg, runCfg, peerId, sig[:65], msg) result := verifyBlockSignature(logger, cfg, runCfg, peerId, sig[:], msg)
require.Equal(t, pubsub.ValidationIgnore, result) require.Equal(t, pubsub.ValidationIgnore, result)
}) })
t.Run("RemoteSignerNoTLS", func(t *testing.T) {
signerCfg := opsigner.NewCLIConfig()
signerCfg.Endpoint = fmt.Sprintf("http://%s", server.Endpoint())
signerCfg.TLSConfig.TLSKey = "invalid"
signerCfg.TLSConfig.TLSCert = "invalid"
signerCfg.TLSConfig.TLSCaCert = "invalid"
signerCfg.TLSConfig.Enabled = true
_, err := NewRemoteSigner(logger, signerCfg)
require.Error(t, err)
})
t.Run("RemoteSignerInvalidEndpoint", func(t *testing.T) {
signerCfg := opsigner.NewCLIConfig()
signerCfg.Endpoint = "Invalid"
signerCfg.TLSConfig.TLSKey = ""
signerCfg.TLSConfig.TLSCert = ""
signerCfg.TLSConfig.TLSCaCert = ""
_, err := NewRemoteSigner(logger, signerCfg)
require.Error(t, err)
})
} }
type MarshalSSZ interface { type MarshalSSZ interface {
...@@ -146,10 +264,10 @@ func TestBlockValidator(t *testing.T) { ...@@ -146,10 +264,10 @@ func TestBlockValidator(t *testing.T) {
cfg := &rollup.Config{ cfg := &rollup.Config{
L2ChainID: big.NewInt(100), L2ChainID: big.NewInt(100),
} }
secrets, err := e2eutils.DefaultMnemonicConfig.Secrets() secrets, err := crypto.GenerateKey()
require.NoError(t, err) require.NoError(t, err)
runCfg := &testutils.MockRuntimeConfig{P2PSeqAddress: crypto.PubkeyToAddress(secrets.SequencerP2P.PublicKey)} runCfg := &testutils.MockRuntimeConfig{P2PSeqAddress: crypto.PubkeyToAddress(secrets.PublicKey)}
signer := &PreparedSigner{Signer: NewLocalSigner(secrets.SequencerP2P)} signer := &PreparedSigner{Signer: NewLocalSigner(secrets)}
// Params Set 2: Call the validation function // Params Set 2: Call the validation function
peerID := peer.ID("foo") peerID := peer.ID("foo")
......
...@@ -9,8 +9,10 @@ import ( ...@@ -9,8 +9,10 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
opsigner "github.com/ethereum-optimism/optimism/op-service/signer"
) )
var SigningDomainBlocksV1 = [32]byte{} var SigningDomainBlocksV1 = [32]byte{}
...@@ -20,40 +22,27 @@ type Signer interface { ...@@ -20,40 +22,27 @@ type Signer interface {
io.Closer io.Closer
} }
func SigningHash(domain [32]byte, chainID *big.Int, payloadBytes []byte) (common.Hash, error) {
var msgInput [32 + 32 + 32]byte
// domain: first 32 bytes
copy(msgInput[:32], domain[:])
// chain_id: second 32 bytes
if chainID.BitLen() > 256 {
return common.Hash{}, errors.New("chain_id is too large")
}
chainID.FillBytes(msgInput[32:64])
// payload_hash: third 32 bytes, hash of encoded payload
copy(msgInput[64:], crypto.Keccak256(payloadBytes))
return crypto.Keccak256Hash(msgInput[:]), nil
}
func BlockSigningHash(cfg *rollup.Config, payloadBytes []byte) (common.Hash, error) { func BlockSigningHash(cfg *rollup.Config, payloadBytes []byte) (common.Hash, error) {
return SigningHash(SigningDomainBlocksV1, cfg.L2ChainID, payloadBytes) return opsigner.NewBlockPayloadArgs(SigningDomainBlocksV1, cfg.L2ChainID, payloadBytes, nil).ToSigningHash()
} }
// LocalSigner is suitable for testing // LocalSigner is suitable for testing
type LocalSigner struct { type LocalSigner struct {
priv *ecdsa.PrivateKey priv *ecdsa.PrivateKey
hasher func(domain [32]byte, chainID *big.Int, payloadBytes []byte) (common.Hash, error)
} }
func NewLocalSigner(priv *ecdsa.PrivateKey) *LocalSigner { func NewLocalSigner(priv *ecdsa.PrivateKey) *LocalSigner {
return &LocalSigner{priv: priv, hasher: SigningHash} return &LocalSigner{priv: priv}
} }
func (s *LocalSigner) Sign(ctx context.Context, domain [32]byte, chainID *big.Int, encodedMsg []byte) (sig *[65]byte, err error) { func (s *LocalSigner) Sign(ctx context.Context, domain [32]byte, chainID *big.Int, encodedMsg []byte) (sig *[65]byte, err error) {
if s.priv == nil { if s.priv == nil {
return nil, errors.New("signer is closed") return nil, errors.New("signer is closed")
} }
signingHash, err := s.hasher(domain, chainID, encodedMsg)
blockPayloadArgs := opsigner.NewBlockPayloadArgs(domain, chainID, encodedMsg, nil)
signingHash, err := blockPayloadArgs.ToSigningHash()
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -69,6 +58,39 @@ func (s *LocalSigner) Close() error { ...@@ -69,6 +58,39 @@ func (s *LocalSigner) Close() error {
return nil return nil
} }
type RemoteSigner struct {
client *opsigner.SignerClient
sender *common.Address
}
func NewRemoteSigner(logger log.Logger, config opsigner.CLIConfig) (*RemoteSigner, error) {
signerClient, err := opsigner.NewSignerClientFromConfig(logger, config)
if err != nil {
return nil, err
}
senderAddress := common.HexToAddress(config.Address)
return &RemoteSigner{signerClient, &senderAddress}, nil
}
func (s *RemoteSigner) Sign(ctx context.Context, domain [32]byte, chainID *big.Int, encodedMsg []byte) (sig *[65]byte, err error) {
if s.client == nil {
return nil, errors.New("signer is closed")
}
blockPayloadArgs := opsigner.NewBlockPayloadArgs(domain, chainID, encodedMsg, s.sender)
signature, err := s.client.SignBlockPayload(ctx, blockPayloadArgs)
if err != nil {
return nil, err
}
return &signature, nil
}
func (s *RemoteSigner) Close() error {
s.client = nil
return nil
}
type PreparedSigner struct { type PreparedSigner struct {
Signer Signer
} }
......
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
opsigner "github.com/ethereum-optimism/optimism/op-service/signer"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -14,10 +15,10 @@ func TestSigningHash_DifferentDomain(t *testing.T) { ...@@ -14,10 +15,10 @@ func TestSigningHash_DifferentDomain(t *testing.T) {
} }
payloadBytes := []byte("arbitraryData") payloadBytes := []byte("arbitraryData")
hash, err := SigningHash(SigningDomainBlocksV1, cfg.L2ChainID, payloadBytes) hash, err := opsigner.NewBlockPayloadArgs(SigningDomainBlocksV1, cfg.L2ChainID, payloadBytes, nil).ToSigningHash()
require.NoError(t, err, "creating first signing hash") require.NoError(t, err, "creating first signing hash")
hash2, err := SigningHash([32]byte{3}, cfg.L2ChainID, payloadBytes) hash2, err := opsigner.NewBlockPayloadArgs([32]byte{3}, cfg.L2ChainID, payloadBytes, nil).ToSigningHash()
require.NoError(t, err, "creating second signing hash") require.NoError(t, err, "creating second signing hash")
require.NotEqual(t, hash, hash2, "signing hash should be different when domain is different") require.NotEqual(t, hash, hash2, "signing hash should be different when domain is different")
...@@ -32,10 +33,10 @@ func TestSigningHash_DifferentChainID(t *testing.T) { ...@@ -32,10 +33,10 @@ func TestSigningHash_DifferentChainID(t *testing.T) {
} }
payloadBytes := []byte("arbitraryData") payloadBytes := []byte("arbitraryData")
hash, err := SigningHash(SigningDomainBlocksV1, cfg1.L2ChainID, payloadBytes) hash, err := opsigner.NewBlockPayloadArgs(SigningDomainBlocksV1, cfg1.L2ChainID, payloadBytes, nil).ToSigningHash()
require.NoError(t, err, "creating first signing hash") require.NoError(t, err, "creating first signing hash")
hash2, err := SigningHash(SigningDomainBlocksV1, cfg2.L2ChainID, payloadBytes) hash2, err := opsigner.NewBlockPayloadArgs(SigningDomainBlocksV1, cfg2.L2ChainID, payloadBytes, nil).ToSigningHash()
require.NoError(t, err, "creating second signing hash") require.NoError(t, err, "creating second signing hash")
require.NotEqual(t, hash, hash2, "signing hash should be different when chain ID is different") require.NotEqual(t, hash, hash2, "signing hash should be different when chain ID is different")
...@@ -46,10 +47,10 @@ func TestSigningHash_DifferentMessage(t *testing.T) { ...@@ -46,10 +47,10 @@ func TestSigningHash_DifferentMessage(t *testing.T) {
L2ChainID: big.NewInt(100), L2ChainID: big.NewInt(100),
} }
hash, err := SigningHash(SigningDomainBlocksV1, cfg.L2ChainID, []byte("msg1")) hash, err := opsigner.NewBlockPayloadArgs(SigningDomainBlocksV1, cfg.L2ChainID, []byte("msg1"), nil).ToSigningHash()
require.NoError(t, err, "creating first signing hash") require.NoError(t, err, "creating first signing hash")
hash2, err := SigningHash(SigningDomainBlocksV1, cfg.L2ChainID, []byte("msg2")) hash2, err := opsigner.NewBlockPayloadArgs(SigningDomainBlocksV1, cfg.L2ChainID, []byte("msg2"), nil).ToSigningHash()
require.NoError(t, err, "creating second signing hash") require.NoError(t, err, "creating second signing hash")
require.NotEqual(t, hash, hash2, "signing hash should be different when message is different") require.NotEqual(t, hash, hash2, "signing hash should be different when message is different")
...@@ -62,6 +63,6 @@ func TestSigningHash_LimitChainID(t *testing.T) { ...@@ -62,6 +63,6 @@ func TestSigningHash_LimitChainID(t *testing.T) {
cfg := &rollup.Config{ cfg := &rollup.Config{
L2ChainID: chainID, L2ChainID: chainID,
} }
_, err := SigningHash(SigningDomainBlocksV1, cfg.L2ChainID, []byte("arbitraryData")) _, err := opsigner.NewBlockPayloadArgs(SigningDomainBlocksV1, cfg.L2ChainID, []byte("arbitraryData"), nil).ToSigningHash()
require.ErrorContains(t, err, "chain_id is too large") require.ErrorContains(t, err, "chain_id is too large")
} }
...@@ -49,7 +49,7 @@ func NewConfig(ctx *cli.Context, log log.Logger) (*node.Config, error) { ...@@ -49,7 +49,7 @@ func NewConfig(ctx *cli.Context, log log.Logger) (*node.Config, error) {
driverConfig := NewDriverConfig(ctx) driverConfig := NewDriverConfig(ctx)
p2pSignerSetup, err := p2pcli.LoadSignerSetup(ctx) p2pSignerSetup, err := p2pcli.LoadSignerSetup(ctx, log)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to load p2p signer: %w", err) return nil, fmt.Errorf("failed to load p2p signer: %w", err)
} }
......
package signer
import (
"errors"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
// BlockPayloadArgs represents the arguments to sign a new block payload from the sequencer.
type BlockPayloadArgs struct {
Domain [32]byte `json:"domain"`
ChainID *big.Int `json:"chainId"`
PayloadHash []byte `json:"payloadHash"`
PayloadBytes []byte
SenderAddress *common.Address `json:"senderAddress"`
}
// NewBlockPayloadArgs creates a BlockPayloadArgs struct
func NewBlockPayloadArgs(domain [32]byte, chainId *big.Int, payloadBytes []byte, senderAddress *common.Address) *BlockPayloadArgs {
payloadHash := crypto.Keccak256(payloadBytes)
args := &BlockPayloadArgs{
Domain: domain,
ChainID: chainId,
PayloadHash: payloadHash,
PayloadBytes: payloadBytes,
SenderAddress: senderAddress,
}
return args
}
func (args *BlockPayloadArgs) Check() error {
if args.ChainID == nil {
return errors.New("chainId not specified")
}
if len(args.PayloadHash) == 0 {
return errors.New("payloadHash not specified")
}
return nil
}
// ToSigningHash creates a signingHash from the block payload args.
// Uses the hashing scheme from https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/rollup-node-p2p.md#block-signatures
func (args *BlockPayloadArgs) ToSigningHash() (common.Hash, error) {
if err := args.Check(); err != nil {
return common.Hash{}, err
}
var msgInput [32 + 32 + 32]byte
// domain: first 32 bytes
copy(msgInput[:32], args.Domain[:])
// chain_id: second 32 bytes
if args.ChainID.BitLen() > 256 {
return common.Hash{}, errors.New("chain_id is too large")
}
args.ChainID.FillBytes(msgInput[32:64])
// payload_hash: third 32 bytes, hash of encoded payload
copy(msgInput[64:], args.PayloadHash[:])
return crypto.Keccak256Hash(msgInput[:]), nil
}
...@@ -17,18 +17,20 @@ const ( ...@@ -17,18 +17,20 @@ const (
HeadersFlagName = "signer.header" HeadersFlagName = "signer.header"
) )
func CLIFlags(envPrefix string) []cli.Flag { func CLIFlags(envPrefix string, category string) []cli.Flag {
envPrefix += "_SIGNER" envPrefix += "_SIGNER"
flags := []cli.Flag{ flags := []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: EndpointFlagName, Name: EndpointFlagName,
Usage: "Signer endpoint the client will connect to", Usage: "Signer endpoint the client will connect to",
EnvVars: opservice.PrefixEnvVar(envPrefix, "ENDPOINT"), EnvVars: opservice.PrefixEnvVar(envPrefix, "ENDPOINT"),
Category: category,
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: AddressFlagName, Name: AddressFlagName,
Usage: "Address the signer is signing transactions for", Usage: "Address the signer is signing requests for",
EnvVars: opservice.PrefixEnvVar(envPrefix, "ADDRESS"), EnvVars: opservice.PrefixEnvVar(envPrefix, "ADDRESS"),
Category: category,
}, },
&cli.StringSliceFlag{ &cli.StringSliceFlag{
Name: HeadersFlagName, Name: HeadersFlagName,
...@@ -36,7 +38,7 @@ func CLIFlags(envPrefix string) []cli.Flag { ...@@ -36,7 +38,7 @@ func CLIFlags(envPrefix string) []cli.Flag {
EnvVars: opservice.PrefixEnvVar(envPrefix, "HEADER"), EnvVars: opservice.PrefixEnvVar(envPrefix, "HEADER"),
}, },
} }
flags = append(flags, optls.CLIFlagsWithFlagPrefix(envPrefix, "signer")...) flags = append(flags, optls.CLIFlagsWithFlagPrefix(envPrefix, "signer", category)...)
return flags return flags
} }
...@@ -65,10 +67,7 @@ func (c CLIConfig) Check() error { ...@@ -65,10 +67,7 @@ func (c CLIConfig) Check() error {
} }
func (c CLIConfig) Enabled() bool { func (c CLIConfig) Enabled() bool {
if c.Endpoint != "" && c.Address != "" { return c.Endpoint != "" && c.Address != ""
return true
}
return false
} }
func ReadCLIConfig(ctx *cli.Context) CLIConfig { func ReadCLIConfig(ctx *cli.Context) CLIConfig {
......
...@@ -93,7 +93,7 @@ func TestInvalidConfig(t *testing.T) { ...@@ -93,7 +93,7 @@ func TestInvalidConfig(t *testing.T) {
func configForArgs(args ...string) CLIConfig { func configForArgs(args ...string) CLIConfig {
app := cli.NewApp() app := cli.NewApp()
app.Flags = CLIFlags("TEST_") app.Flags = CLIFlags("TEST_", "")
app.Name = "test" app.Name = "test"
var config CLIConfig var config CLIConfig
app.Action = func(ctx *cli.Context) error { app.Action = func(ctx *cli.Context) error {
......
...@@ -113,3 +113,19 @@ func (s *SignerClient) SignTransaction(ctx context.Context, chainId *big.Int, fr ...@@ -113,3 +113,19 @@ func (s *SignerClient) SignTransaction(ctx context.Context, chainId *big.Int, fr
return &signed, nil return &signed, nil
} }
func (s *SignerClient) SignBlockPayload(ctx context.Context, args *BlockPayloadArgs) ([65]byte, error) {
var result hexutil.Bytes
if err := s.client.CallContext(ctx, &result, "opsigner_signBlockPayload", args); err != nil {
return [65]byte{}, fmt.Errorf("opsigner_signBlockPayload failed: %w", err)
}
if len(result) != 65 {
return [65]byte{}, fmt.Errorf("invalid signature: %s", result.String())
}
signature := [65]byte(result)
return signature, nil
}
...@@ -21,7 +21,7 @@ const ( ...@@ -21,7 +21,7 @@ const (
// CLIFlags returns flags with env var envPrefix // CLIFlags returns flags with env var envPrefix
// This should be used for server TLS configs, or when client and server tls configs are the same // This should be used for server TLS configs, or when client and server tls configs are the same
func CLIFlags(envPrefix string) []cli.Flag { func CLIFlags(envPrefix string) []cli.Flag {
return CLIFlagsWithFlagPrefix(envPrefix, "") return CLIFlagsWithFlagPrefix(envPrefix, "", "")
} }
var ( var (
...@@ -33,7 +33,7 @@ var ( ...@@ -33,7 +33,7 @@ var (
// CLIFlagsWithFlagPrefix returns flags with env var and cli flag prefixes // CLIFlagsWithFlagPrefix returns flags with env var and cli flag prefixes
// Should be used for client TLS configs when different from server on the same process // Should be used for client TLS configs when different from server on the same process
func CLIFlagsWithFlagPrefix(envPrefix string, flagPrefix string) []cli.Flag { func CLIFlagsWithFlagPrefix(envPrefix string, flagPrefix string, category string) []cli.Flag {
prefixFunc := func(flagName string) string { prefixFunc := func(flagName string) string {
return strings.Trim(fmt.Sprintf("%s.%s", flagPrefix, flagName), ".") return strings.Trim(fmt.Sprintf("%s.%s", flagPrefix, flagName), ".")
} }
...@@ -48,22 +48,25 @@ func CLIFlagsWithFlagPrefix(envPrefix string, flagPrefix string) []cli.Flag { ...@@ -48,22 +48,25 @@ func CLIFlagsWithFlagPrefix(envPrefix string, flagPrefix string) []cli.Flag {
EnvVars: prefixEnvVars("TLS_ENABLED"), EnvVars: prefixEnvVars("TLS_ENABLED"),
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: prefixFunc(TLSCaCertFlagName), Name: prefixFunc(TLSCaCertFlagName),
Usage: "tls ca cert path", Usage: "tls ca cert path",
Value: defaultTLSCaCert, Value: defaultTLSCaCert,
EnvVars: prefixEnvVars("TLS_CA"), EnvVars: prefixEnvVars("TLS_CA"),
Category: category,
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: prefixFunc(TLSCertFlagName), Name: prefixFunc(TLSCertFlagName),
Usage: "tls cert path", Usage: "tls cert path",
Value: defaultTLSCert, Value: defaultTLSCert,
EnvVars: prefixEnvVars("TLS_CERT"), EnvVars: prefixEnvVars("TLS_CERT"),
Category: category,
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: prefixFunc(TLSKeyFlagName), Name: prefixFunc(TLSKeyFlagName),
Usage: "tls key", Usage: "tls key",
Value: defaultTLSKey, Value: defaultTLSKey,
EnvVars: prefixEnvVars("TLS_KEY"), EnvVars: prefixEnvVars("TLS_KEY"),
Category: category,
}, },
} }
} }
......
...@@ -53,7 +53,7 @@ func TestInvalidConfig(t *testing.T) { ...@@ -53,7 +53,7 @@ func TestInvalidConfig(t *testing.T) {
func configForArgs(args ...string) CLIConfig { func configForArgs(args ...string) CLIConfig {
app := cli.NewApp() app := cli.NewApp()
app.Flags = CLIFlagsWithFlagPrefix("TEST_", "test") app.Flags = CLIFlagsWithFlagPrefix("TEST_", "test", "")
app.Name = "test" app.Name = "test"
var config CLIConfig var config CLIConfig
app.Action = func(ctx *cli.Context) error { app.Action = func(ctx *cli.Context) error {
......
...@@ -191,7 +191,7 @@ func CLIFlagsWithDefaults(envPrefix string, defaults DefaultFlagValues) []cli.Fl ...@@ -191,7 +191,7 @@ func CLIFlagsWithDefaults(envPrefix string, defaults DefaultFlagValues) []cli.Fl
Value: defaults.ReceiptQueryInterval, Value: defaults.ReceiptQueryInterval,
EnvVars: prefixEnvVars("TXMGR_RECEIPT_QUERY_INTERVAL"), EnvVars: prefixEnvVars("TXMGR_RECEIPT_QUERY_INTERVAL"),
}, },
}, opsigner.CLIFlags(envPrefix)...) }, opsigner.CLIFlags(envPrefix, "")...)
} }
type CLIConfig struct { type CLIConfig struct {
......
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