diff --git a/op-node/p2p/gossip.go b/op-node/p2p/gossip.go index 9d3ab539bc124..3da4a3572923f 100644 --- a/op-node/p2p/gossip.go +++ b/op-node/p2p/gossip.go @@ -23,6 +23,7 @@ import ( "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-service/eth" + opsigner "github.com/ethereum-optimism/optimism/op-service/signer" ) const ( @@ -569,7 +570,7 @@ func (p *publisher) PublishL2Payload(ctx context.Context, envelope *eth.Executio data := buf.Bytes() payloadData := data[65:] - sig, err := signer.Sign(ctx, SigningDomainBlocksV1, p.cfg.L2ChainID, payloadData) + sig, err := signer.Sign(ctx, SigningDomainBlocksV1, eth.ChainIDFromBig(p.cfg.L2ChainID), opsigner.PayloadHash(payloadData)) if err != nil { return fmt.Errorf("failed to sign execution payload with signer: %w", err) } diff --git a/op-node/p2p/gossip_test.go b/op-node/p2p/gossip_test.go index 3943f0f3154b0..d8559022da261 100644 --- a/op-node/p2p/gossip_test.go +++ b/op-node/p2p/gossip_test.go @@ -71,7 +71,7 @@ func TestVerifyBlockSignature(t *testing.T) { 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) + sig, err := signer.Sign(context.Background(), SigningDomainBlocksV1, eth.ChainIDFromBig(cfg.L2ChainID), opsigner.PayloadHash(msg)) require.NoError(t, err) result := verifyBlockSignature(logger, cfg, runCfg, peerId, sig[:], msg) require.Equal(t, pubsub.ValidationAccept, result) @@ -80,7 +80,7 @@ func TestVerifyBlockSignature(t *testing.T) { 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) + sig, err := signer.Sign(context.Background(), SigningDomainBlocksV1, eth.ChainIDFromBig(cfg.L2ChainID), opsigner.PayloadHash(msg)) require.NoError(t, err) result := verifyBlockSignature(logger, cfg, runCfg, peerId, sig[:], msg) require.Equal(t, pubsub.ValidationReject, result) @@ -96,7 +96,7 @@ func TestVerifyBlockSignature(t *testing.T) { 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) + sig, err := signer.Sign(context.Background(), SigningDomainBlocksV1, eth.ChainIDFromBig(cfg.L2ChainID), opsigner.PayloadHash(msg)) require.NoError(t, err) result := verifyBlockSignature(logger, cfg, runCfg, peerId, sig[:], msg) require.Equal(t, pubsub.ValidationIgnore, result) @@ -108,10 +108,11 @@ type mockRemoteSigner struct { } func (t *mockRemoteSigner) SignBlockPayload(args opsigner.BlockPayloadArgs) (hexutil.Bytes, error) { - signingHash, err := args.ToSigningHash() + msg, err := args.Message() if err != nil { return nil, err } + signingHash := msg.ToSigningHash() signature, err := crypto.Sign(signingHash[:], t.priv) if err != nil { return nil, err @@ -161,7 +162,7 @@ func TestVerifyBlockSignatureWithRemoteSigner(t *testing.T) { 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, eth.ChainIDFromBig(cfg.L2ChainID), opsigner.PayloadHash(msg)) require.NoError(t, err) result := verifyBlockSignature(logger, cfg, runCfg, peerId, sig[:], msg) require.Equal(t, pubsub.ValidationAccept, result) @@ -172,7 +173,7 @@ func TestVerifyBlockSignatureWithRemoteSigner(t *testing.T) { 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, eth.ChainIDFromBig(cfg.L2ChainID), opsigner.PayloadHash(msg)) require.NoError(t, err) result := verifyBlockSignature(logger, cfg, runCfg, peerId, sig[:], msg) require.Equal(t, pubsub.ValidationReject, result) @@ -190,7 +191,7 @@ func TestVerifyBlockSignatureWithRemoteSigner(t *testing.T) { 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, eth.ChainIDFromBig(cfg.L2ChainID), opsigner.PayloadHash(msg)) require.NoError(t, err) result := verifyBlockSignature(logger, cfg, runCfg, peerId, sig[:], msg) require.Equal(t, pubsub.ValidationIgnore, result) @@ -231,7 +232,7 @@ func createSignedP2Payload(payload MarshalSSZ, signer Signer, l2ChainID *big.Int } data := buf.Bytes() payloadData := data[65:] - sig, err := signer.Sign(context.TODO(), SigningDomainBlocksV1, l2ChainID, payloadData) + sig, err := signer.Sign(context.TODO(), SigningDomainBlocksV1, eth.ChainIDFromBig(l2ChainID), opsigner.PayloadHash(payloadData)) if err != nil { return nil, fmt.Errorf("failed to sign execution payload with signer: %w", err) } diff --git a/op-node/p2p/signer.go b/op-node/p2p/signer.go index 20a52d0a2625f..7c963c9511e4b 100644 --- a/op-node/p2p/signer.go +++ b/op-node/p2p/signer.go @@ -5,25 +5,30 @@ import ( "crypto/ecdsa" "errors" "io" - "math/big" "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" + "github.com/ethereum-optimism/optimism/op-service/eth" opsigner "github.com/ethereum-optimism/optimism/op-service/signer" ) var SigningDomainBlocksV1 = [32]byte{} type Signer interface { - Sign(ctx context.Context, domain [32]byte, chainID *big.Int, encodedMsg []byte) (sig *[65]byte, err error) + Sign(ctx context.Context, domain eth.Bytes32, chainID eth.ChainID, payloadHash common.Hash) (sig *[65]byte, err error) io.Closer } func BlockSigningHash(cfg *rollup.Config, payloadBytes []byte) (common.Hash, error) { - return opsigner.NewBlockPayloadArgs(SigningDomainBlocksV1, cfg.L2ChainID, payloadBytes, nil).ToSigningHash() + msg := opsigner.BlockSigningMessage{ + Domain: SigningDomainBlocksV1, + ChainID: eth.ChainIDFromBig(cfg.L2ChainID), + PayloadHash: opsigner.PayloadHash(payloadBytes), + } + return msg.ToSigningHash(), nil } // LocalSigner is suitable for testing @@ -35,17 +40,16 @@ func NewLocalSigner(priv *ecdsa.PrivateKey) *LocalSigner { 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 eth.Bytes32, chainID eth.ChainID, payloadHash common.Hash) (sig *[65]byte, err error) { if s.priv == nil { return nil, errors.New("signer is closed") } - - blockPayloadArgs := opsigner.NewBlockPayloadArgs(domain, chainID, encodedMsg, nil) - signingHash, err := blockPayloadArgs.ToSigningHash() - - if err != nil { - return nil, err + msg := opsigner.BlockSigningMessage{ + Domain: domain, + ChainID: chainID, + PayloadHash: payloadHash, } + signingHash := msg.ToSigningHash() signature, err := crypto.Sign(signingHash[:], s.priv) if err != nil { return nil, err @@ -72,14 +76,19 @@ func NewRemoteSigner(logger log.Logger, config opsigner.CLIConfig) (*RemoteSigne 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) { +func (s *RemoteSigner) Sign(ctx context.Context, domain eth.Bytes32, chainID eth.ChainID, payloadHash common.Hash) (sig *[65]byte, err error) { if s.client == nil { return nil, errors.New("signer is closed") } - blockPayloadArgs := opsigner.NewBlockPayloadArgs(domain, chainID, encodedMsg, s.sender) + // We use V1 for now, since the server may not support V2 yet + blockPayloadArgs := &opsigner.BlockPayloadArgs{ + Domain: domain, + ChainID: chainID.ToBig(), + PayloadHash: payloadHash[:], + SenderAddress: s.sender, + } signature, err := s.client.SignBlockPayload(ctx, blockPayloadArgs) - if err != nil { return nil, err } diff --git a/op-node/p2p/signer_test.go b/op-node/p2p/signer_test.go index 53f78504d18fb..a466a66ff542a 100644 --- a/op-node/p2p/signer_test.go +++ b/op-node/p2p/signer_test.go @@ -15,12 +15,14 @@ func TestSigningHash_DifferentDomain(t *testing.T) { } payloadBytes := []byte("arbitraryData") - hash, err := opsigner.NewBlockPayloadArgs(SigningDomainBlocksV1, cfg.L2ChainID, payloadBytes, nil).ToSigningHash() + msg, err := opsigner.NewBlockPayloadArgs(SigningDomainBlocksV1, cfg.L2ChainID, payloadBytes, nil).Message() require.NoError(t, err, "creating first signing hash") - hash2, err := opsigner.NewBlockPayloadArgs([32]byte{3}, cfg.L2ChainID, payloadBytes, nil).ToSigningHash() + msg2, err := opsigner.NewBlockPayloadArgs([32]byte{3}, cfg.L2ChainID, payloadBytes, nil).Message() require.NoError(t, err, "creating second signing hash") + hash := msg.ToSigningHash() + hash2 := msg2.ToSigningHash() require.NotEqual(t, hash, hash2, "signing hash should be different when domain is different") } @@ -33,27 +35,31 @@ func TestSigningHash_DifferentChainID(t *testing.T) { } payloadBytes := []byte("arbitraryData") - hash, err := opsigner.NewBlockPayloadArgs(SigningDomainBlocksV1, cfg1.L2ChainID, payloadBytes, nil).ToSigningHash() + msg, err := opsigner.NewBlockPayloadArgs(SigningDomainBlocksV1, cfg1.L2ChainID, payloadBytes, nil).Message() require.NoError(t, err, "creating first signing hash") - hash2, err := opsigner.NewBlockPayloadArgs(SigningDomainBlocksV1, cfg2.L2ChainID, payloadBytes, nil).ToSigningHash() + msg2, err := opsigner.NewBlockPayloadArgs(SigningDomainBlocksV1, cfg2.L2ChainID, payloadBytes, nil).Message() require.NoError(t, err, "creating second signing hash") + hash := msg.ToSigningHash() + hash2 := msg2.ToSigningHash() require.NotEqual(t, hash, hash2, "signing hash should be different when chain ID is different") } -func TestSigningHash_DifferentMessage(t *testing.T) { +func TestSigningHash_DifferentPayload(t *testing.T) { cfg := &rollup.Config{ L2ChainID: big.NewInt(100), } - hash, err := opsigner.NewBlockPayloadArgs(SigningDomainBlocksV1, cfg.L2ChainID, []byte("msg1"), nil).ToSigningHash() + msg, err := opsigner.NewBlockPayloadArgs(SigningDomainBlocksV1, cfg.L2ChainID, []byte("payload1"), nil).Message() require.NoError(t, err, "creating first signing hash") - hash2, err := opsigner.NewBlockPayloadArgs(SigningDomainBlocksV1, cfg.L2ChainID, []byte("msg2"), nil).ToSigningHash() + msg2, err := opsigner.NewBlockPayloadArgs(SigningDomainBlocksV1, cfg.L2ChainID, []byte("payload2"), nil).Message() require.NoError(t, err, "creating second signing hash") - require.NotEqual(t, hash, hash2, "signing hash should be different when message is different") + hash := msg.ToSigningHash() + hash2 := msg2.ToSigningHash() + require.NotEqual(t, hash, hash2, "signing hash should be different when payload is different") } func TestSigningHash_LimitChainID(t *testing.T) { @@ -63,6 +69,6 @@ func TestSigningHash_LimitChainID(t *testing.T) { cfg := &rollup.Config{ L2ChainID: chainID, } - _, err := opsigner.NewBlockPayloadArgs(SigningDomainBlocksV1, cfg.L2ChainID, []byte("arbitraryData"), nil).ToSigningHash() + _, err := opsigner.NewBlockPayloadArgs(SigningDomainBlocksV1, cfg.L2ChainID, []byte("arbitraryData"), nil).Message() require.ErrorContains(t, err, "chain_id is too large") } diff --git a/op-service/eth/types.go b/op-service/eth/types.go index c4352a524828a..ae458b1e0e2a0 100644 --- a/op-service/eth/types.go +++ b/op-service/eth/types.go @@ -71,6 +71,32 @@ func (ie InputError) Is(target error) bool { return ok // we implement Unwrap, so we do not have to check the inner type now } +// Bytes65 is a 65-byte long byte string, and encoded with 0x-prefix in hex. +// This can be used to represent encoded secp256k ethereum signatures. +type Bytes65 [65]byte + +func (b *Bytes65) UnmarshalJSON(text []byte) error { + return hexutil.UnmarshalFixedJSON(reflect.TypeOf(b), text, b[:]) +} + +func (b *Bytes65) UnmarshalText(text []byte) error { + return hexutil.UnmarshalFixedText("Bytes65", text, b[:]) +} + +func (b Bytes65) MarshalText() ([]byte, error) { + return hexutil.Bytes(b[:]).MarshalText() +} + +func (b Bytes65) String() string { + return hexutil.Encode(b[:]) +} + +// TerminalString implements log.TerminalStringer, formatting a string for console +// output during logging. +func (b Bytes65) TerminalString() string { + return fmt.Sprintf("0x%x..%x", b[:3], b[65-3:]) +} + type Bytes32 [32]byte func (b *Bytes32) UnmarshalJSON(text []byte) error { @@ -92,7 +118,7 @@ func (b Bytes32) String() string { // TerminalString implements log.TerminalStringer, formatting a string for console // output during logging. func (b Bytes32) TerminalString() string { - return fmt.Sprintf("%x..%x", b[:3], b[29:]) + return fmt.Sprintf("0x%x..%x", b[:3], b[29:]) } type Bytes8 [8]byte @@ -116,7 +142,7 @@ func (b Bytes8) String() string { // TerminalString implements log.TerminalStringer, formatting a string for console // output during logging. func (b Bytes8) TerminalString() string { - return fmt.Sprintf("%x", b[:]) + return fmt.Sprintf("0x%x", b[:]) } type Bytes96 [96]byte @@ -140,7 +166,7 @@ func (b Bytes96) String() string { // TerminalString implements log.TerminalStringer, formatting a string for console // output during logging. func (b Bytes96) TerminalString() string { - return fmt.Sprintf("%x..%x", b[:3], b[93:]) + return fmt.Sprintf("0x%x..%x", b[:3], b[93:]) } type Bytes256 [256]byte @@ -164,7 +190,7 @@ func (b Bytes256) String() string { // TerminalString implements log.TerminalStringer, formatting a string for console // output during logging. func (b Bytes256) TerminalString() string { - return fmt.Sprintf("%x..%x", b[:3], b[253:]) + return fmt.Sprintf("0x%x..%x", b[:3], b[253:]) } type Uint64Quantity = hexutil.Uint64 @@ -211,6 +237,14 @@ type ExecutionPayloadEnvelope struct { RequestsHash *common.Hash `json:"requestsHash,omitempty"` } +func (env *ExecutionPayloadEnvelope) ID() BlockID { + return env.ExecutionPayload.ID() +} + +func (env *ExecutionPayloadEnvelope) String() string { + return fmt.Sprintf("envelope(%s)", env.ID()) +} + type ExecutionPayload struct { ParentHash common.Hash `json:"parentHash"` FeeRecipient common.Address `json:"feeRecipient"` @@ -242,6 +276,10 @@ func (payload *ExecutionPayload) ID() BlockID { return BlockID{Hash: payload.BlockHash, Number: uint64(payload.BlockNumber)} } +func (payload *ExecutionPayload) String() string { + return fmt.Sprintf("payload(%s)", payload.ID()) +} + func (payload *ExecutionPayload) ParentID() BlockID { n := uint64(payload.BlockNumber) if n > 0 { diff --git a/op-service/eth/types_test.go b/op-service/eth/types_test.go index 661fd41b9dea4..5c24330f0d41a 100644 --- a/op-service/eth/types_test.go +++ b/op-service/eth/types_test.go @@ -3,7 +3,9 @@ package eth import ( "encoding/json" "errors" + "fmt" "math" + "strings" "testing" "github.com/ethereum/go-ethereum/common" @@ -131,3 +133,42 @@ func TestStorageKey(t *testing.T) { require.Equal(t, c.marshaled, []uint8(key)[:]) } } + +func TestBytes(t *testing.T) { + testBytesN(t, 8, &Bytes8{0: 1, 8 - 1: 2}, func() BytesN { return new(Bytes8) }) + testBytesN(t, 32, &Bytes32{0: 1, 32 - 1: 2}, func() BytesN { return new(Bytes32) }) + testBytesN(t, 65, &Bytes65{0: 1, 65 - 1: 2}, func() BytesN { return new(Bytes65) }) + testBytesN(t, 96, &Bytes96{0: 1, 96 - 1: 2}, func() BytesN { return new(Bytes96) }) + testBytesN(t, 256, &Bytes256{0: 1, 256 - 1: 2}, func() BytesN { return new(Bytes256) }) +} + +type BytesN interface { + String() string + TerminalString() string + UnmarshalJSON(text []byte) error + UnmarshalText(text []byte) error + MarshalText() ([]byte, error) +} + +func testBytesN(t *testing.T, n int, x BytesN, alloc func() BytesN) { + t.Run(fmt.Sprintf("Bytes%d", n), func(t *testing.T) { + xStr := "0x01" + strings.Repeat("00", n-2) + "02" + require.Equal(t, xStr, x.String()) + if n == 8 { // too short for dots + require.Equal(t, "0x0100000000000002", x.TerminalString()) + } else { + require.Equal(t, "0x010000..000002", x.TerminalString()) + } + out, err := x.MarshalText() + require.NoError(t, err) + require.Equal(t, xStr, string(out)) + + y := alloc() + require.NoError(t, y.UnmarshalText([]byte(xStr))) + require.Equal(t, x, y) + + z := alloc() + require.NoError(t, z.UnmarshalJSON([]byte(fmt.Sprintf("%q", xStr)))) + require.Equal(t, x, z) + }) +} diff --git a/op-service/signer/blockpayload_args.go b/op-service/signer/blockpayload_args.go index 8239bc0967d6b..24c451da0b993 100644 --- a/op-service/signer/blockpayload_args.go +++ b/op-service/signer/blockpayload_args.go @@ -6,57 +6,115 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + + "github.com/ethereum-optimism/optimism/op-service/eth" ) // BlockPayloadArgs represents the arguments to sign a new block payload from the sequencer. +// This is maintained until the V1 signing API in the op-signer server is no longer served. type BlockPayloadArgs struct { - Domain [32]byte `json:"domain"` - ChainID *big.Int `json:"chainId"` - PayloadHash []byte `json:"payloadHash"` - PayloadBytes []byte + Domain [32]byte `json:"domain"` + ChainID *big.Int `json:"chainId"` + PayloadHash []byte `json:"payloadHash"` SenderAddress *common.Address `json:"senderAddress"` + + // note: older versions of this included a `PayloadBytes` value, + // not JSON-named, but JSON-encoded anyway. + // Since this was unused, it is no longer included. } // NewBlockPayloadArgs creates a BlockPayloadArgs struct func NewBlockPayloadArgs(domain [32]byte, chainId *big.Int, payloadBytes []byte, senderAddress *common.Address) *BlockPayloadArgs { - payloadHash := crypto.Keccak256(payloadBytes) + payloadHash := PayloadHash(payloadBytes) args := &BlockPayloadArgs{ Domain: domain, ChainID: chainId, - PayloadHash: payloadHash, - PayloadBytes: payloadBytes, + PayloadHash: payloadHash[:], SenderAddress: senderAddress, } return args } +// Check checks that the attributes are set and conform to type assumptions. func (args *BlockPayloadArgs) Check() error { if args.ChainID == nil { return errors.New("chainId not specified") } + if args.ChainID.BitLen() > 256 { + return errors.New("chain_id is too large") + } if len(args.PayloadHash) == 0 { return errors.New("payloadHash not specified") } + if len(args.PayloadHash) != 32 { + return errors.New("payloadHash has unexpected length") + } 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) { +func (args *BlockPayloadArgs) Message() (*BlockSigningMessage, error) { if err := args.Check(); err != nil { - return common.Hash{}, err + return nil, err + } + return &BlockSigningMessage{ + Domain: args.Domain, + ChainID: eth.ChainIDFromBig(args.ChainID), + PayloadHash: common.BytesToHash(args.PayloadHash), + }, nil +} + +// BlockPayloadArgsV2 represents the arguments to sign a new block payload from the sequencer. +// This replaces BlockPayloadArgs, to fix JSON encoding. +type BlockPayloadArgsV2 struct { + Domain eth.Bytes32 `json:"domain"` + ChainID eth.ChainID `json:"chainId"` + PayloadHash common.Hash `json:"payloadHash"` + + // SenderAddress is optional, it helps determine which account to sign with, + // if multiple accounts are available. + SenderAddress *common.Address `json:"senderAddress"` +} + +// PayloadHash computes the hash of the payload, an attribute of the signing message. +// The payload-hash is NOT the signing-hash. +func PayloadHash(payload []byte) common.Hash { + return crypto.Keccak256Hash(payload) +} + +func (args *BlockPayloadArgsV2) Message() (*BlockSigningMessage, error) { + // A zero Domain is valid, and thus not checked. + if args.ChainID == (eth.ChainID{}) { + return nil, errors.New("chainId not specified") + } + if args.PayloadHash == (common.Hash{}) { + return nil, errors.New("payloadHash not specified") } + return &BlockSigningMessage{ + Domain: args.Domain, + ChainID: args.ChainID, + PayloadHash: args.PayloadHash, + }, nil +} + +// BlockSigningMessage is the message representing a block. For signing-message construction. +// NOT FOR API USAGE. See BlockPayloadArgsV2 instead. +type BlockSigningMessage struct { + Domain eth.Bytes32 + ChainID eth.ChainID + PayloadHash common.Hash +} + +// 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 (msg *BlockSigningMessage) ToSigningHash() common.Hash { var msgInput [32 + 32 + 32]byte // domain: first 32 bytes - copy(msgInput[:32], args.Domain[:]) + copy(msgInput[:32], msg.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]) - + chainID := msg.ChainID.Bytes32() + copy(msgInput[32:64], chainID[:]) // payload_hash: third 32 bytes, hash of encoded payload - copy(msgInput[64:], args.PayloadHash[:]) + copy(msgInput[64:], msg.PayloadHash[:]) - return crypto.Keccak256Hash(msgInput[:]), nil + return crypto.Keccak256Hash(msgInput[:]) } diff --git a/op-service/signer/blockpayload_args_test.go b/op-service/signer/blockpayload_args_test.go new file mode 100644 index 0000000000000..302e0d2b605cf --- /dev/null +++ b/op-service/signer/blockpayload_args_test.go @@ -0,0 +1,52 @@ +package signer + +import ( + "encoding/json" + "math/big" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum/go-ethereum/common" +) + +func TestBlockPayloadArgs(t *testing.T) { + l2ChainID := big.NewInt(100) + payloadBytes := []byte("arbitraryData") + addr := common.HexToAddress("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266") + exampleDomain := [32]byte{0: 123, 1: 42} + args := NewBlockPayloadArgs(exampleDomain, l2ChainID, payloadBytes, &addr) + out, err := json.MarshalIndent(args, " ", " ") + require.NoError(t, err) + content := string(out) + // previously erroneously included in every request. Not used on server-side. Should be dropped now. + require.NotContains(t, content, "PayloadBytes") + // mistyped as list of ints in v0 + require.Contains(t, content, `"domain": [`) + require.Contains(t, content, ` 123,`) + require.Contains(t, content, ` 42,`) + require.Contains(t, content, `"chainId": 100`) + // mistyped as standard Go bytes, hence base64 + require.Contains(t, content, `"payloadHash": "7qa7ZZHSC1LytldPsgv3J5zQPVgWE9jqHojIK4QAFEs="`) + require.Contains(t, content, `"senderAddress": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"`) + + // Assert signing hash, to detect breaking changes to hashing + msg, err := args.Message() + require.NoError(t, err) + h := msg.ToSigningHash() + require.Equal(t, common.HexToHash("0xc724de7aefa316a12cbbd5edc39d512830598cce5d6ac81d1b518ef69450ad77"), h) + + argsV2 := BlockPayloadArgsV2{ + Domain: eth.Bytes32(exampleDomain), + ChainID: eth.ChainIDFromUInt64(100), + PayloadHash: PayloadHash(payloadBytes), + SenderAddress: &addr, + } + msg2, err := argsV2.Message() + require.NoError(t, err) + require.Equal(t, msg, msg2) + h2 := msg2.ToSigningHash() + require.NoError(t, err) + require.Equal(t, h, h2, "signing hash still the same in V2 API") +} diff --git a/op-service/signer/client.go b/op-service/signer/client.go index acd753ecef7ac..0a970e4bcba9c 100644 --- a/op-service/signer/client.go +++ b/op-service/signer/client.go @@ -10,18 +10,19 @@ import ( "os" "time" - optls "github.com/ethereum-optimism/optimism/op-service/tls" - "github.com/ethereum-optimism/optimism/op-service/tls/certman" "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/log" "github.com/ethereum/go-ethereum/rpc" + + "github.com/ethereum-optimism/optimism/op-service/eth" + optls "github.com/ethereum-optimism/optimism/op-service/tls" + "github.com/ethereum-optimism/optimism/op-service/tls/certman" ) type SignerClient struct { client *rpc.Client - status string logger log.Logger } @@ -74,7 +75,7 @@ func NewSignerClient(logger log.Logger, endpoint string, headers http.Header, tl if err != nil { return nil, err } - signer.status = fmt.Sprintf("ok [version=%v]", version) + logger.Info("Connected to op-signer server", "version", version) return signer, nil } @@ -129,3 +130,11 @@ func (s *SignerClient) SignBlockPayload(ctx context.Context, args *BlockPayloadA return signature, nil } + +func (s *SignerClient) SignBlockPayloadV2(ctx context.Context, args *BlockPayloadArgsV2) (eth.Bytes65, error) { + var result eth.Bytes65 + if err := s.client.CallContext(ctx, &result, "opsigner_signBlockPayloadV2", args); err != nil { + return [65]byte{}, fmt.Errorf("opsigner_signBlockPayloadV2 failed: %w", err) + } + return result, nil +}