From 96a9a6fc16145f9ada4bcbd8925c8b741fdda564 Mon Sep 17 00:00:00 2001 From: terencechain Date: Mon, 14 Aug 2023 08:27:21 -0700 Subject: [PATCH] feat: add validator blob signing (#12730) --- consensus-types/blocks/testing/factory.go | 4 ++ validator/client/BUILD.bazel | 3 ++ validator/client/blob.go | 39 ++++++++++++++++ validator/client/blob_test.go | 51 +++++++++++++++++++++ validator/client/propose.go | 46 +++++++++++++++---- validator/client/propose_test.go | 54 ++++++++++++++++++++++- 6 files changed, 187 insertions(+), 10 deletions(-) create mode 100644 validator/client/blob.go create mode 100644 validator/client/blob_test.go diff --git a/consensus-types/blocks/testing/factory.go b/consensus-types/blocks/testing/factory.go index d920be0141..236e4cb452 100644 --- a/consensus-types/blocks/testing/factory.go +++ b/consensus-types/blocks/testing/factory.go @@ -26,6 +26,10 @@ func NewSignedBeaconBlockFromGeneric(gb *eth.GenericSignedBeaconBlock) (interfac return blocks.NewSignedBeaconBlock(bb.Capella) case *eth.GenericSignedBeaconBlock_BlindedCapella: return blocks.NewSignedBeaconBlock(bb.BlindedCapella) + case *eth.GenericSignedBeaconBlock_Deneb: + return blocks.NewSignedBeaconBlock(bb.Deneb.Block) + case *eth.GenericSignedBeaconBlock_BlindedDeneb: + return blocks.NewSignedBeaconBlock(bb.BlindedDeneb.Block) // Generic Signed Beacon Block Deneb can't be used here as it is not a block, but block content with blobs default: return nil, errors.Wrapf(blocks.ErrUnsupportedSignedBeaconBlock, "unable to create block from type %T", gb) diff --git a/validator/client/BUILD.bazel b/validator/client/BUILD.bazel index 6d85f48163..8889572f90 100644 --- a/validator/client/BUILD.bazel +++ b/validator/client/BUILD.bazel @@ -6,6 +6,7 @@ go_library( "aggregate.go", "attest.go", "attest_protect.go", + "blob.go", "key_reload.go", "log.go", "metrics.go", @@ -101,6 +102,7 @@ go_test( "aggregate_test.go", "attest_protect_test.go", "attest_test.go", + "blob_test.go", "key_reload_test.go", "metrics_test.go", "propose_protect_test.go", @@ -138,6 +140,7 @@ go_test( "//proto/prysm/v1alpha1:go_default_library", "//proto/prysm/v1alpha1/validator-client:go_default_library", "//runtime:go_default_library", + "//runtime/version:go_default_library", "//testing/assert:go_default_library", "//testing/mock:go_default_library", "//testing/require:go_default_library", diff --git a/validator/client/blob.go b/validator/client/blob.go new file mode 100644 index 0000000000..ab391ce042 --- /dev/null +++ b/validator/client/blob.go @@ -0,0 +1,39 @@ +package client + +import ( + "context" + + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/signing" + fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams" + "github.com/prysmaticlabs/prysm/v4/config/params" + ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" + validatorpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/validator-client" + "github.com/prysmaticlabs/prysm/v4/time/slots" +) + +func (v *validator) signBlob(ctx context.Context, blob *ethpb.BlobSidecar, pubKey [fieldparams.BLSPubkeyLength]byte) ([]byte, error) { + epoch := slots.ToEpoch(blob.Slot) + domain, err := v.domainData(ctx, epoch, params.BeaconConfig().DomainBlobSidecar[:]) + if err != nil { + return nil, errors.Wrap(err, domainDataErr) + } + if domain == nil { + return nil, errors.New(domainDataErr) + } + sr, err := signing.ComputeSigningRoot(blob, domain.SignatureDomain) + if err != nil { + return nil, errors.Wrap(err, signingRootErr) + } + sig, err := v.keyManager.Sign(ctx, &validatorpb.SignRequest{ + PublicKey: pubKey[:], + SigningRoot: sr[:], + SignatureDomain: domain.SignatureDomain, + Object: &validatorpb.SignRequest_Blob{Blob: blob}, + SigningSlot: blob.Slot, + }) + if err != nil { + return nil, errors.Wrap(err, "could not sign block proposal") + } + return sig.Marshal(), nil +} diff --git a/validator/client/blob_test.go b/validator/client/blob_test.go new file mode 100644 index 0000000000..9ca3f1c844 --- /dev/null +++ b/validator/client/blob_test.go @@ -0,0 +1,51 @@ +package client + +import ( + "context" + "testing" + + "github.com/golang/mock/gomock" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/signing" + fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams" + "github.com/prysmaticlabs/prysm/v4/config/params" + "github.com/prysmaticlabs/prysm/v4/crypto/bls" + "github.com/prysmaticlabs/prysm/v4/encoding/bytesutil" + ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v4/testing/require" +) + +func Test_validator_signBlob(t *testing.T) { + v, m, vk, finish := setup(t) + defer finish() + + m.validatorClient.EXPECT(). + DomainData(gomock.Any(), // ctx + ðpb.DomainRequest{ + Domain: params.BeaconConfig().DomainBlobSidecar[:], + }). // epoch + Return(ðpb.DomainResponse{ + SignatureDomain: bytesutil.PadTo([]byte("signatureDomain"), 32), + }, nil) + + blob := ðpb.BlobSidecar{ + BlockRoot: bytesutil.PadTo([]byte("blockRoot"), 32), + Index: 1, + Slot: 2, + BlockParentRoot: bytesutil.PadTo([]byte("blockParentRoot"), 32), + ProposerIndex: 3, + Blob: bytesutil.PadTo([]byte("blob"), fieldparams.BlobLength), + KzgCommitment: bytesutil.PadTo([]byte("kzgCommitment"), 48), + KzgProof: bytesutil.PadTo([]byte("kzgPRoof"), 48), + } + ctx := context.Background() + sig, err := v.signBlob(ctx, blob, [48]byte(vk.PublicKey().Marshal())) + require.NoError(t, err) + pb, err := bls.PublicKeyFromBytes(vk.PublicKey().Marshal()) + require.NoError(t, err) + signature, err := bls.SignatureFromBytes(sig) + require.NoError(t, err) + sr, err := signing.ComputeSigningRoot(blob, bytesutil.PadTo([]byte("signatureDomain"), 32)) + require.NoError(t, err) + + require.Equal(t, true, signature.Verify(pb, sr[:])) +} diff --git a/validator/client/propose.go b/validator/client/propose.go index 2a3c075e75..1975eb9dba 100644 --- a/validator/client/propose.go +++ b/validator/client/propose.go @@ -121,16 +121,46 @@ func (v *validator) ProposeBlock(ctx context.Context, slot primitives.Slot, pubK return } - // Propose and broadcast block via beacon node - proposal, err := blk.PbGenericBlock() - if err != nil { - log.WithError(err).Error("Failed to create proposal request") - if v.emitAccountMetrics { - ValidatorProposeFailVec.WithLabelValues(fmtKey).Inc() + var genericSignedBlock *ethpb.GenericSignedBeaconBlock + if blk.Version() >= version.Deneb && !blk.IsBlinded() { + signedBlobs := make([]*ethpb.SignedBlobSidecar, len(b.GetDeneb().Blobs)) + for _, blob := range b.GetDeneb().Blobs { + blobSig, err := v.signBlob(ctx, blob, pubKey) + if err != nil { + log.WithError(err).Error("Failed to sign blob") + return + } + signedBlobs = append(signedBlobs, ðpb.SignedBlobSidecar{ + Message: blob, + Signature: blobSig, + }) + } + denebBlock, err := blk.PbDenebBlock() + if err != nil { + log.WithError(err).Error("Failed to get deneb block") + return + } + genericSignedBlock = ðpb.GenericSignedBeaconBlock{ + Block: ðpb.GenericSignedBeaconBlock_Deneb{ + Deneb: ðpb.SignedBeaconBlockAndBlobsDeneb{ + Block: denebBlock, + Blobs: signedBlobs, + }, + }, + } + } else { + // Propose and broadcast block via beacon node + genericSignedBlock, err = blk.PbGenericBlock() + if err != nil { + log.WithError(err).Error("Failed to create proposal request") + if v.emitAccountMetrics { + ValidatorProposeFailVec.WithLabelValues(fmtKey).Inc() + } + return } - return } - blkResp, err := v.validatorClient.ProposeBeaconBlock(ctx, proposal) + + blkResp, err := v.validatorClient.ProposeBeaconBlock(ctx, genericSignedBlock) if err != nil { log.WithField("blockSlot", slot).WithError(err).Error("Failed to propose block") if v.emitAccountMetrics { diff --git a/validator/client/propose_test.go b/validator/client/propose_test.go index 151c228e79..a8b8f7c54c 100644 --- a/validator/client/propose_test.go +++ b/validator/client/propose_test.go @@ -20,6 +20,7 @@ import ( "github.com/prysmaticlabs/prysm/v4/encoding/bytesutil" ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" validatorpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/validator-client" + "github.com/prysmaticlabs/prysm/v4/runtime/version" "github.com/prysmaticlabs/prysm/v4/testing/assert" "github.com/prysmaticlabs/prysm/v4/testing/require" "github.com/prysmaticlabs/prysm/v4/testing/util" @@ -508,8 +509,9 @@ func TestProposeBlock_BroadcastsBlock_WithGraffiti(t *testing.T) { func testProposeBlock(t *testing.T, graffiti []byte) { tests := []struct { - name string - block *ethpb.GenericBeaconBlock + name string + block *ethpb.GenericBeaconBlock + version int }{ { name: "phase0", @@ -583,6 +585,43 @@ func testProposeBlock(t *testing.T, graffiti []byte) { }, }, }, + { + name: "deneb block and blobs", + version: version.Deneb, + block: ðpb.GenericBeaconBlock{ + Block: ðpb.GenericBeaconBlock_Deneb{ + Deneb: func() *ethpb.BeaconBlockAndBlobsDeneb { + blk := util.NewBeaconBlockDeneb() + blk.Block.Body.Graffiti = graffiti + return ðpb.BeaconBlockAndBlobsDeneb{ + Block: blk.Block, + Blobs: []*ethpb.BlobSidecar{ + { + BlockRoot: bytesutil.PadTo([]byte("blockRoot"), 32), + Index: 1, + Slot: 2, + BlockParentRoot: bytesutil.PadTo([]byte("blockParentRoot"), 32), + ProposerIndex: 3, + Blob: bytesutil.PadTo([]byte("blob"), fieldparams.BlobLength), + KzgCommitment: bytesutil.PadTo([]byte("kzgCommitment"), 48), + KzgProof: bytesutil.PadTo([]byte("kzgPRoof"), 48), + }, + { + BlockRoot: bytesutil.PadTo([]byte("blockRoot1"), 32), + Index: 4, + Slot: 5, + BlockParentRoot: bytesutil.PadTo([]byte("blockParentRoot1"), 32), + ProposerIndex: 6, + Blob: bytesutil.PadTo([]byte("blob1"), fieldparams.BlobLength), + KzgCommitment: bytesutil.PadTo([]byte("kzgCommitment1"), 48), + KzgProof: bytesutil.PadTo([]byte("kzgPRoof1"), 48), + }, + }, + } + }(), + }, + }, + }, } for _, tt := range tests { @@ -617,6 +656,17 @@ func testProposeBlock(t *testing.T, graffiti []byte) { var sentBlock interfaces.ReadOnlySignedBeaconBlock var err error + if tt.version == version.Deneb { + m.validatorClient.EXPECT().DomainData( + gomock.Any(), // ctx + gomock.Any(), // epoch + ).Return(ðpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/) + m.validatorClient.EXPECT().DomainData( + gomock.Any(), // ctx + gomock.Any(), // epoch + ).Return(ðpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/) + } + m.validatorClient.EXPECT().ProposeBeaconBlock( gomock.Any(), // ctx gomock.AssignableToTypeOf(ðpb.GenericSignedBeaconBlock{}),