Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

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
5 changes: 3 additions & 2 deletions op-node/flags/p2p_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/urfave/cli/v2"

"github.com/ethereum-optimism/optimism/op-node/p2p"
opsigner "github.com/ethereum-optimism/optimism/op-service/signer"
)

func p2pEnv(envprefix, v string) []string {
Expand Down Expand Up @@ -87,7 +88,7 @@ func deprecatedP2PFlags(envPrefix string) []cli.Flag {
// None of these flags are strictly required.
// Some are hidden if they are too technical, or not recommended.
func P2PFlags(envPrefix string) []cli.Flag {
return []cli.Flag{
return append([]cli.Flag{
&cli.BoolFlag{
Name: DisableP2PName,
Usage: "Completely disable the P2P stack",
Expand Down Expand Up @@ -410,5 +411,5 @@ func P2PFlags(envPrefix string) []cli.Flag {
Required: false,
EnvVars: p2pEnv(envPrefix, "PING"),
},
}
}, opsigner.CLIFlags(envPrefix, P2PCategory)...)
}
16 changes: 10 additions & 6 deletions op-node/p2p/cli/load_signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@ import (
"strings"

"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli/v2"

"github.com/ethereum-optimism/optimism/op-node/flags"
"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
func LoadSignerSetup(ctx *cli.Context) (p2p.SignerSetup, error) {
func LoadSignerSetup(ctx *cli.Context, logger log.Logger) (p2p.SignerSetup, error) {
key := ctx.String(flags.SequencerP2PKeyName)
signerCfg := opsigner.ReadCLIConfig(ctx)
if key != "" {
// 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).
Expand All @@ -26,9 +26,13 @@ func LoadSignerSetup(ctx *cli.Context) (p2p.SignerSetup, error) {
}

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
}
154 changes: 136 additions & 18 deletions op-node/p2p/gossip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,33 @@ package p2p
import (
"bytes"
"context"
"crypto/ecdsa"
"fmt"
"io"
"math/big"
"testing"
"time"

"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"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"
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/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"

"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"

pubsub "github.com/libp2p/go-libp2p-pubsub"
pubsub_pb "github.com/libp2p/go-libp2p-pubsub/pb"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/stretchr/testify/require"

"github.com/ethereum-optimism/optimism/op-service/testlog"
)

func TestGuardGossipValidator(t *testing.T) {
Expand Down Expand Up @@ -62,43 +64,159 @@ func TestVerifyBlockSignature(t *testing.T) {
L2ChainID: big.NewInt(100),
}
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)

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")

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) {
runCfg := &testutils.MockRuntimeConfig{P2PSeqAddress: crypto.PubkeyToAddress(secrets.SequencerP2P.PublicKey)}
signer := &PreparedSigner{Signer: NewLocalSigner(secrets.SequencerP2P)}
runCfg := &testutils.MockRuntimeConfig{P2PSeqAddress: crypto.PubkeyToAddress(secrets.PublicKey)}
remoteSigner, err := NewRemoteSigner(logger, signerCfg)
require.NoError(t, err)
signer := &PreparedSigner{Signer: remoteSigner}
sig, err := signer.Sign(context.Background(), SigningDomainBlocksV1, cfg.L2ChainID, msg)
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)
})

t.Run("WrongSigner", func(t *testing.T) {
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)
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)
})

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)
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.SequencerP2P)}
remoteSigner, err := NewRemoteSigner(logger, signerCfg)
require.NoError(t, err)
signer := &PreparedSigner{Signer: remoteSigner}
sig, err := signer.Sign(context.Background(), SigningDomainBlocksV1, cfg.L2ChainID, msg)
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)
})

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 {
Expand Down Expand Up @@ -146,10 +264,10 @@ func TestBlockValidator(t *testing.T) {
cfg := &rollup.Config{
L2ChainID: big.NewInt(100),
}
secrets, err := e2eutils.DefaultMnemonicConfig.Secrets()
secrets, err := crypto.GenerateKey()
require.NoError(t, err)
runCfg := &testutils.MockRuntimeConfig{P2PSeqAddress: crypto.PubkeyToAddress(secrets.SequencerP2P.PublicKey)}
signer := &PreparedSigner{Signer: NewLocalSigner(secrets.SequencerP2P)}
runCfg := &testutils.MockRuntimeConfig{P2PSeqAddress: crypto.PubkeyToAddress(secrets.PublicKey)}
signer := &PreparedSigner{Signer: NewLocalSigner(secrets)}
// Params Set 2: Call the validation function
peerID := peer.ID("foo")

Expand Down
62 changes: 42 additions & 20 deletions op-node/p2p/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import (

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

"github.com/ethereum-optimism/optimism/op-node/rollup"
opsigner "github.com/ethereum-optimism/optimism/op-service/signer"
)

var SigningDomainBlocksV1 = [32]byte{}
Expand All @@ -20,40 +22,27 @@ type Signer interface {
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) {
return SigningHash(SigningDomainBlocksV1, cfg.L2ChainID, payloadBytes)
return opsigner.NewBlockPayloadArgs(SigningDomainBlocksV1, cfg.L2ChainID, payloadBytes, nil).ToSigningHash()
}

// LocalSigner is suitable for testing
type LocalSigner struct {
priv *ecdsa.PrivateKey
hasher func(domain [32]byte, chainID *big.Int, payloadBytes []byte) (common.Hash, error)
priv *ecdsa.PrivateKey
}

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) {
if s.priv == nil {
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 {
return nil, err
}
Expand All @@ -69,6 +58,39 @@ func (s *LocalSigner) Close() error {
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 {
Signer
}
Expand Down
Loading