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
3 changes: 3 additions & 0 deletions changelog/james-prysm_remote-signer-gloas.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Added

- Validator client now supports remote signing (web3signer) of Gloas blocks and the new Gloas signing types: builder request auth, execution payload envelope, payload attestation message, and proposer preferences.
1 change: 1 addition & 0 deletions validator/client/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ go_test(
"aggregate_test.go",
"aggregator_selector_test.go",
"attest_test.go",
"builder_request_auth_test.go",
"conn_tracker_test.go",
"doppelganger_test.go",
"duties_test.go",
Expand Down
1 change: 1 addition & 0 deletions validator/client/builder_request_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ func (v *validator) signBuilderRequestAuth(
SigningRoot: r[:],
SignatureDomain: domain,
Object: &validatorpb.SignRequest_BuilderRequestAuth{BuilderRequestAuth: auth},
SigningSlot: auth.Slot,
})
if err != nil {
return nil, errors.Wrap(err, "could not sign builder request auth")
Expand Down
59 changes: 59 additions & 0 deletions validator/client/builder_request_auth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package client

import (
"testing"

"github.com/OffchainLabs/prysm/v7/beacon-chain/core/signing"
"github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/crypto/bls"
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
validatorpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1/validator-client"
"github.com/OffchainLabs/prysm/v7/testing/require"
)

func Test_signBuilderRequestAuth(t *testing.T) {
kp := randKeypair(t)
km := newMockKeymanager(t, kp)
v := validator{}
auth := &ethpb.BuilderRequestAuth{
Data: []byte("https://builder.example.org"),
Slot: 123,
}

t.Run("signs with the fork-independent domain", func(t *testing.T) {
signed, err := v.signBuilderRequestAuth(t.Context(), km, kp.pub, auth)
require.NoError(t, err)
require.Equal(t, auth, signed.Message)

domain, err := signing.ComputeDomain(
params.BeaconConfig().DomainBuilderRequestAuth,
params.BeaconConfig().GenesisForkVersion,
make([]byte, 32),
)
require.NoError(t, err)
root, err := signing.ComputeSigningRoot(auth, domain)
require.NoError(t, err)
sig, err := bls.SignatureFromBytes(signed.Signature)
require.NoError(t, err)
require.Equal(t, true, sig.Verify(kp.pri.PublicKey(), root[:]))
})

t.Run("sign request carries the auth slot and object", func(t *testing.T) {
_, err := v.signBuilderRequestAuth(t.Context(), km, kp.pub, auth)
require.NoError(t, err)

req := km.lastSignRequest()
require.NotNil(t, req)
require.Equal(t, auth.Slot, req.SigningSlot)
require.DeepEqual(t, kp.pub[:], req.PublicKey)
obj, ok := req.Object.(*validatorpb.SignRequest_BuilderRequestAuth)
require.Equal(t, true, ok)
require.Equal(t, auth, obj.BuilderRequestAuth)
})

t.Run("unknown key", func(t *testing.T) {
other := randKeypair(t)
_, err := v.signBuilderRequestAuth(t.Context(), km, other.pub, auth)
require.ErrorContains(t, "could not sign builder request auth", err)
})
}
1 change: 1 addition & 0 deletions validator/client/registration.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ func (v *validator) signProposerPreferences(
SigningRoot: r[:],
SignatureDomain: domain,
Object: &validatorpb.SignRequest_ProposerPreference{ProposerPreference: pref},
SigningSlot: pref.ProposalSlot,
})
if err != nil {
return nil, errors.Wrap(err, "could not sign proposer preferences")
Expand Down
10 changes: 10 additions & 0 deletions validator/client/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ type mockKeymanager struct {
keys [][fieldparams.BLSPubkeyLength]byte
fetchNoKeys bool
accountsChangedFeed *event.Feed
lastSignReq *validatorpb.SignRequest
}

var errMockKeyExists = errors.New("key already in mockKeymanager map")
Expand Down Expand Up @@ -140,6 +141,9 @@ func (m *mockKeymanager) FetchValidatingPublicKeys(_ context.Context) ([][fieldp
}

func (m *mockKeymanager) Sign(_ context.Context, req *validatorpb.SignRequest) (bls.Signature, error) {
m.lock.Lock()
m.lastSignReq = req
m.lock.Unlock()
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], req.PublicKey)
privKey, ok := m.keysMap[pubKey]
Expand All @@ -150,6 +154,12 @@ func (m *mockKeymanager) Sign(_ context.Context, req *validatorpb.SignRequest) (
return sig, nil
}

func (m *mockKeymanager) lastSignRequest() *validatorpb.SignRequest {
m.lock.RLock()
defer m.lock.RUnlock()
return m.lastSignReq
}

func (m *mockKeymanager) SubscribeAccountChanges(pubKeysChan chan [][fieldparams.BLSPubkeyLength]byte) event.Subscription {
if m.accountsChangedFeed == nil {
m.accountsChangedFeed = &event.Feed{}
Expand Down
1 change: 1 addition & 0 deletions validator/keymanager/remote-web3signer/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ go_test(
embed = [":go_default_library"],
deps = [
"//async/event:go_default_library",
"//config/params:go_default_library",
"//crypto/bls:go_default_library",
"//encoding/bytesutil:go_default_library",
"//io/file:go_default_library",
Expand Down
75 changes: 66 additions & 9 deletions validator/keymanager/remote-web3signer/keymanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,18 +261,15 @@ func getSignRequestJson(ctx context.Context, validator *validator.Validate, requ
case *validatorpb.SignRequest_BlindedBlockFulu:
return handleBlindedBlockFulu(ctx, validator, request, genesisValidatorsRoot)
case *validatorpb.SignRequest_BlockGloas:
// TODO: Implement Gloas block signing for web3signer.
return nil, fmt.Errorf("web3signer Gloas block signing not yet implemented")
return handleBlockGloas(ctx, validator, request, genesisValidatorsRoot)
case *validatorpb.SignRequest_ExecutionPayloadEnvelope:
// TODO: Implement execution payload envelope signing for web3signer.
return nil, fmt.Errorf("web3signer execution payload envelope signing not yet implemented")
return handleExecutionPayloadEnvelope(ctx, ver, validator, request, genesisValidatorsRoot)
case *validatorpb.SignRequest_PayloadAttestationData:
return handlePayloadAttestationMessage(ctx, ver, validator, request, genesisValidatorsRoot)
case *validatorpb.SignRequest_ProposerPreference:
// TODO: Implement proposer preferences signing for web3signer.
return nil, fmt.Errorf("web3signer proposer preferences signing not yet implemented")
return handleProposerPreferences(ctx, ver, validator, request, genesisValidatorsRoot)
case *validatorpb.SignRequest_BuilderRequestAuth:
// TODO: Implement builder request auth signing for web3signer.
return nil, fmt.Errorf("web3signer builder request auth signing not yet implemented")

return handleBuilderRequestAuth(ctx, ver, validator, request)
// We do not support "DEPOSIT" type.
/*
case *validatorpb.:
Expand Down Expand Up @@ -477,6 +474,66 @@ func handleBlindedBlockFulu(ctx context.Context, validator *validator.Validate,
return json.Marshal(blindedBlockv2FuluSignRequest)
}

func handleBlockGloas(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
signReq, err := types.GetBlockV2BlindedSignRequest(request, genesisValidatorsRoot)
if err != nil {
return nil, err
}
if err = validator.StructCtx(ctx, signReq); err != nil {
return nil, err
}
remoteBlockSignRequestsTotal.WithLabelValues("gloas", "false").Inc()
return json.Marshal(signReq)
}

func handleBuilderRequestAuth(ctx context.Context, fork int, validator *validator.Validate, request *validatorpb.SignRequest) ([]byte, error) {
signReq, err := types.GetBuilderRequestAuthSignRequest(fork, request)
if err != nil {
return nil, err
}
if err = validator.StructCtx(ctx, signReq); err != nil {
return nil, err
}
builderRequestAuthSignRequestsTotal.Inc()
return json.Marshal(signReq)
}

func handleExecutionPayloadEnvelope(ctx context.Context, fork int, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
signReq, err := types.GetExecutionPayloadEnvelopeSignRequest(fork, request, genesisValidatorsRoot)
if err != nil {
return nil, err
}
if err = validator.StructCtx(ctx, signReq); err != nil {
return nil, err
}
executionPayloadEnvelopeSignRequestsTotal.Inc()
return json.Marshal(signReq)
}

func handlePayloadAttestationMessage(ctx context.Context, fork int, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
signReq, err := types.GetPayloadAttestationMessageSignRequest(fork, request, genesisValidatorsRoot)
if err != nil {
return nil, err
}
if err = validator.StructCtx(ctx, signReq); err != nil {
return nil, err
}
payloadAttestationMessageSignRequestsTotal.Inc()
return json.Marshal(signReq)
}

func handleProposerPreferences(ctx context.Context, fork int, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
signReq, err := types.GetProposerPreferencesSignRequest(fork, request, genesisValidatorsRoot)
if err != nil {
return nil, err
}
if err = validator.StructCtx(ctx, signReq); err != nil {
return nil, err
}
proposerPreferencesSignRequestsTotal.Inc()
return json.Marshal(signReq)
}

func handleRandaoReveal(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
randaoRevealSignRequest, err := types.GetRandaoRevealSignRequest(request, genesisValidatorsRoot)
if err != nil {
Expand Down
45 changes: 45 additions & 0 deletions validator/keymanager/remote-web3signer/keymanager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"testing"
"time"

"github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/crypto/bls"
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
"github.com/OffchainLabs/prysm/v7/io/file"
Expand Down Expand Up @@ -237,6 +238,10 @@ func TestNewKeyManager_FileAndFlagsWithDifferentKeys(t *testing.T) {
}

func TestKeymanager_Sign(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.GloasForkEpoch = cfg.FuluForkEpoch + 1
params.OverrideBeaconConfig(cfg)
client := &MockClient{
Signature: "0xb3baa751d0a9132cfe93e4e3d5ff9075111100e3789dca219ade5a24d27e19d16b3353149da1833e9b691bb38634e8dc04469be7032132906c927d7e1a49b414730612877bc6b2810c8f202daf793d1ab0d6b5cb21d52f9e52e883859887a5d9",
}
Expand Down Expand Up @@ -352,6 +357,46 @@ func TestKeymanager_Sign(t *testing.T) {
want: desiredSig,
wantErr: false,
},
{
name: "BLOCK_V2_GLOAS",
args: args{
request: mock.GetMockSignRequest("BLOCK_V2_GLOAS"),
},
want: desiredSig,
wantErr: false,
},
{
name: "BUILDER_REQUEST_AUTH",
args: args{
request: mock.GetMockSignRequest("BUILDER_REQUEST_AUTH"),
},
want: desiredSig,
wantErr: false,
},
{
name: "EXECUTION_PAYLOAD_ENVELOPE",
args: args{
request: mock.GetMockSignRequest("EXECUTION_PAYLOAD_ENVELOPE"),
},
want: desiredSig,
wantErr: false,
},
{
name: "PAYLOAD_ATTESTATION_MESSAGE",
args: args{
request: mock.GetMockSignRequest("PAYLOAD_ATTESTATION_MESSAGE"),
},
want: desiredSig,
wantErr: false,
},
{
name: "PROPOSER_PREFERENCES",
args: args{
request: mock.GetMockSignRequest("PROPOSER_PREFERENCES"),
},
want: desiredSig,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
16 changes: 16 additions & 0 deletions validator/keymanager/remote-web3signer/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,20 @@ var (
Name: "remote_web3signer_validator_registration_sign_requests_total",
Help: "Total number of validator registration sign requests",
})
builderRequestAuthSignRequestsTotal = promauto.NewCounter(prometheus.CounterOpts{
Name: "remote_web3signer_builder_request_auth_sign_requests_total",
Help: "Total number of builder request auth sign requests",
})
executionPayloadEnvelopeSignRequestsTotal = promauto.NewCounter(prometheus.CounterOpts{
Name: "remote_web3signer_execution_payload_envelope_sign_requests_total",
Help: "Total number of execution payload envelope sign requests",
})
payloadAttestationMessageSignRequestsTotal = promauto.NewCounter(prometheus.CounterOpts{
Name: "remote_web3signer_payload_attestation_message_sign_requests_total",
Help: "Total number of payload attestation message sign requests",
})
proposerPreferencesSignRequestsTotal = promauto.NewCounter(prometheus.CounterOpts{
Name: "remote_web3signer_proposer_preferences_sign_requests_total",
Help: "Total number of proposer preferences sign requests",
})
)
6 changes: 6 additions & 0 deletions validator/keymanager/remote-web3signer/types/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ go_library(
"//consensus-types/blocks:go_default_library",
"//consensus-types/interfaces:go_default_library",
"//consensus-types/primitives:go_default_library",
"//encoding/bytesutil:go_default_library",
"//math:go_default_library",
"//proto/engine/v1:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//proto/prysm/v1alpha1/validator-client:go_default_library",
"//runtime/version:go_default_library",
Expand All @@ -32,7 +35,10 @@ go_test(
deps = [
":go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//consensus-types/primitives:go_default_library",
"//encoding/bytesutil:go_default_library",
"//proto/engine/v1:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//proto/prysm/v1alpha1/validator-client:go_default_library",
"//runtime/version:go_default_library",
Expand Down
Loading
Loading