From 5246cf5016645ed588d91a8593c749f48bb1f4cc Mon Sep 17 00:00:00 2001 From: Giuseppe Cocomazzi <62541231+sbudella-gco@users.noreply.github.com> Date: Tue, 17 Dec 2024 10:50:07 +0100 Subject: [PATCH] Support for signed beacon block header and signature verification (#2245) Co-authored-by: Giuseppe Cocomazzi --- .gitignore | 1 + beacon/blockchain/process_proposal.go | 24 +++- beacon/blockchain/service.go | 6 +- beacon/blockchain/types.go | 7 + beacon/validator/block_builder.go | 49 +++++-- beacon/validator/types.go | 5 + consensus-types/types/signed_header.go | 130 ++++++++++++++++++ consensus-types/types/signed_header_test.go | 112 +++++++++++++++ da/blob/factory.go | 30 +++- da/blob/processor.go | 20 ++- da/blob/types.go | 3 +- da/blob/verifier.go | 27 +++- da/da/types.go | 13 +- da/store/store.go | 2 +- da/types/sidecar.go | 38 ++--- da/types/sidecar_test.go | 30 ++-- da/types/sidecars.go | 4 +- da/types/sidecars_test.go | 25 ++-- node-core/components/interfaces.go | 16 ++- .../core/state_processor_sidecar_verifier.go | 75 ++++++++++ 20 files changed, 549 insertions(+), 68 deletions(-) create mode 100644 consensus-types/types/signed_header.go create mode 100644 consensus-types/types/signed_header_test.go create mode 100644 state-transition/core/state_processor_sidecar_verifier.go diff --git a/.gitignore b/.gitignore index bc97033da0..05475dbfa9 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ site/ contracts/cache contracts/out tmp/ +cscope.files ########## # Golang # diff --git a/beacon/blockchain/process_proposal.go b/beacon/blockchain/process_proposal.go index 5a253b3da1..575837d1a4 100644 --- a/beacon/blockchain/process_proposal.go +++ b/beacon/blockchain/process_proposal.go @@ -72,6 +72,13 @@ func (s *Service[ return createProcessProposalResponse(errors.WrapNonFatal(err)) } + // In theory, swapping the order of verification between the sidecars + // and the incoming block should not introduce any inconsistencies + // in the state on which the sidecar verification depends on (notably + // the currently active fork). ProcessProposal should only + // keep the state changes as candidates (which is what we do in + // VerifyIncomingBlock). + // Process the blob sidecars, if any if !sidecars.IsNil() && sidecars.Len() > 0 { var consensusSidecars *types.ConsensusSidecars[BlobSidecarsT] @@ -83,13 +90,26 @@ func (s *Service[ s.logger.Info("Received incoming blob sidecars") // TODO: Clean this up once we remove generics. - c := convertConsensusSidecars[ + cs := convertConsensusSidecars[ ConsensusSidecarsT, BlobSidecarsT, ](consensusSidecars) + // Get the sidecar verification function from the state processor + //nolint:govet // err shadowing + sidecarVerifierFn, err := s.stateProcessor.GetSidecarVerifierFn( + s.storageBackend.StateFromContext(ctx), + ) + if err != nil { + s.logger.Error( + "an error incurred while calculating the sidecar verifier", + "reason", err, + ) + return createProcessProposalResponse(errors.WrapNonFatal(err)) + } + // Verify the blobs and ensure they match the local state. - err = s.blobProcessor.VerifySidecars(c) + err = s.blobProcessor.VerifySidecars(cs, sidecarVerifierFn) if err != nil { s.logger.Error( "rejecting incoming blob sidecars", diff --git a/beacon/blockchain/service.go b/beacon/blockchain/service.go index 566906eb69..4f95b1c08f 100644 --- a/beacon/blockchain/service.go +++ b/beacon/blockchain/service.go @@ -70,7 +70,8 @@ type Service[ ] blobProcessor da.BlobProcessor[ AvailabilityStoreT, - ConsensusSidecarsT, BlobSidecarsT, + ConsensusSidecarsT, + BlobSidecarsT, ] // store is the block store for the service. // TODO: Remove this and use the block store from the storage backend. @@ -143,7 +144,8 @@ func NewService[ ], blobProcessor da.BlobProcessor[ AvailabilityStoreT, - ConsensusSidecarsT, BlobSidecarsT, + ConsensusSidecarsT, + BlobSidecarsT, ], blockStore BlockStoreT, depositStore deposit.Store, diff --git a/beacon/blockchain/types.go b/beacon/blockchain/types.go index 09fe151b07..2bd71f20f7 100644 --- a/beacon/blockchain/types.go +++ b/beacon/blockchain/types.go @@ -28,6 +28,7 @@ import ( engineprimitives "github.com/berachain/beacon-kit/engine-primitives/engine-primitives" "github.com/berachain/beacon-kit/primitives/common" "github.com/berachain/beacon-kit/primitives/constraints" + "github.com/berachain/beacon-kit/primitives/crypto" "github.com/berachain/beacon-kit/primitives/math" "github.com/berachain/beacon-kit/primitives/transition" cmtabci "github.com/cometbft/cometbft/abci/types" @@ -209,6 +210,12 @@ type StateProcessor[ BeaconStateT, BeaconBlockT, ) (transition.ValidatorUpdates, error) + GetSidecarVerifierFn(BeaconStateT) ( + func( + blkHeader *ctypes.BeaconBlockHeader, + signature crypto.BLSSignature) error, + error, + ) } // StorageBackend defines an interface for accessing various storage components diff --git a/beacon/validator/block_builder.go b/beacon/validator/block_builder.go index a485d4e39c..78a85bf069 100644 --- a/beacon/validator/block_builder.go +++ b/beacon/validator/block_builder.go @@ -46,6 +46,12 @@ func (s *Service[ ctx context.Context, slotData types.SlotData[ctypes.SlashingInfo], ) ([]byte, []byte, error) { + var ( + blk BeaconBlockT + sidecars BlobSidecarsT + forkData *ctypes.ForkData + ) + startTime := time.Now() defer s.metrics.measureRequestBlockForProposalTime(startTime) @@ -65,15 +71,21 @@ func (s *Service[ return nil, nil, err } + // Build forkdata used for the signing root of the reveal and the sidecars + forkData, err := s.buildForkData(st, slotData.GetSlot()) + if err != nil { + return nil, nil, err + } + // Build the reveal for the current slot. // TODO: We can optimize to pre-compute this in parallel? - reveal, err := s.buildRandaoReveal(st, slotData.GetSlot()) + reveal, err := s.buildRandaoReveal(forkData, slotData.GetSlot()) if err != nil { return nil, nil, err } // Create a new empty block from the current state. - blk, err := s.getEmptyBeaconBlockForSlot(st, slotData.GetSlot()) + blk, err = s.getEmptyBeaconBlockForSlot(st, slotData.GetSlot()) if err != nil { return nil, nil, err } @@ -107,8 +119,12 @@ func (s *Service[ } // Produce blob sidecars with new StateRoot - sidecars, err := s.blobFactory.BuildSidecars( - blk, envelope.GetBlobsBundle()) + sidecars, err = s.blobFactory.BuildSidecars( + blk, + envelope.GetBlobsBundle(), + s.signer, + forkData, + ) if err != nil { return nil, nil, err } @@ -164,27 +180,38 @@ func (s *Service[ ) } -// buildRandaoReveal builds a randao reveal for the given slot. func (s *Service[ _, _, BeaconStateT, _, _, _, _, _, _, _, -]) buildRandaoReveal( +]) buildForkData( st BeaconStateT, slot math.Slot, -) (crypto.BLSSignature, error) { +) (*ctypes.ForkData, error) { var ( epoch = s.chainSpec.SlotToEpoch(slot) ) genesisValidatorsRoot, err := st.GetGenesisValidatorsRoot() if err != nil { - return crypto.BLSSignature{}, err + return nil, err } - signingRoot := ctypes.NewForkData( + return ctypes.NewForkData( version.FromUint32[common.Version]( s.chainSpec.ActiveForkVersionForEpoch(epoch), - ), genesisValidatorsRoot, - ).ComputeRandaoSigningRoot( + ), + genesisValidatorsRoot, + ), nil +} + +// buildRandaoReveal builds a randao reveal for the given slot. +func (s *Service[ + _, _, BeaconStateT, _, _, _, _, _, _, _, +]) buildRandaoReveal( + forkData *ctypes.ForkData, + slot math.Slot, +) (crypto.BLSSignature, error) { + var epoch = s.chainSpec.SlotToEpoch(slot) + signingRoot := forkData.ComputeRandaoSigningRoot( s.chainSpec.DomainTypeRandao(), epoch, ) diff --git a/beacon/validator/types.go b/beacon/validator/types.go index b222afbe3e..57586944e4 100644 --- a/beacon/validator/types.go +++ b/beacon/validator/types.go @@ -116,6 +116,9 @@ type BlobFactory[ BuildSidecars( blk BeaconBlockT, blobs engineprimitives.BlobsBundle, + signer crypto.BLSSigner, + forkData *ctypes.ForkData, + ) (BlobSidecarsT, error) } @@ -150,6 +153,8 @@ type ForkData[T any] interface { common.DomainType, math.Epoch, ) common.Root + // ComputeDomain computes the fork data domain for a given domain type. + ComputeDomain(common.DomainType) common.Domain } // PayloadBuilder represents a service that is responsible for diff --git a/consensus-types/types/signed_header.go b/consensus-types/types/signed_header.go new file mode 100644 index 0000000000..e051598581 --- /dev/null +++ b/consensus-types/types/signed_header.go @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: BUSL-1.1 +// +// Copyright (C) 2024, Berachain Foundation. All rights reserved. +// Use of this software is governed by the Business Source License included +// in the LICENSE file of this repository and at www.mariadb.com/bsl11. +// +// ANY USE OF THE LICENSED WORK IN VIOLATION OF THIS LICENSE WILL AUTOMATICALLY +// TERMINATE YOUR RIGHTS UNDER THIS LICENSE FOR THE CURRENT AND ALL OTHER +// VERSIONS OF THE LICENSED WORK. +// +// THIS LICENSE DOES NOT GRANT YOU ANY RIGHT IN ANY TRADEMARK OR LOGO OF +// LICENSOR OR ITS AFFILIATES (PROVIDED THAT YOU MAY USE A TRADEMARK OR LOGO OF +// LICENSOR AS EXPRESSLY REQUIRED BY THIS LICENSE). +// +// TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +// AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +// EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +// TITLE. + +package types + +import ( + "github.com/berachain/beacon-kit/primitives/common" + "github.com/berachain/beacon-kit/primitives/constraints" + "github.com/berachain/beacon-kit/primitives/crypto" + "github.com/karalabe/ssz" +) + +var ( + _ ssz.StaticObject = (*SignedBeaconBlockHeader)(nil) + _ constraints.SSZMarshallableRootable = (*SignedBeaconBlockHeader)(nil) +) + +type SignedBeaconBlockHeader struct { + Header *BeaconBlockHeader `json:"header"` + Signature crypto.BLSSignature `json:"signature"` +} + +/* -------------------------------------------------------------------------- */ +/* Constructor */ +/* -------------------------------------------------------------------------- */ + +// NewSignedBeaconBlockHeader creates a new BeaconBlockHeader. +func NewSignedBeaconBlockHeader( + header *BeaconBlockHeader, + signature crypto.BLSSignature, +) *SignedBeaconBlockHeader { + return &SignedBeaconBlockHeader{ + header, signature, + } +} + +// Empty creates an empty BeaconBlockHeader instance. +func (*SignedBeaconBlockHeader) Empty() *SignedBeaconBlockHeader { + return &SignedBeaconBlockHeader{} +} + +// New creates a new SignedBeaconBlockHeader. +func (b *SignedBeaconBlockHeader) New( + header *BeaconBlockHeader, + signature crypto.BLSSignature, +) *SignedBeaconBlockHeader { + return NewSignedBeaconBlockHeader( + header, signature, + ) +} + +/* -------------------------------------------------------------------------- */ +/* SSZ */ +/* -------------------------------------------------------------------------- */ + +// SizeSSZ returns the size of the SignedBeaconBlockHeader object +// in SSZ encoding. Total size: Header (112) + Signature (96). +func (b *SignedBeaconBlockHeader) SizeSSZ(sizer *ssz.Sizer) uint32 { + //nolint:mnd // no magic + size := (*BeaconBlockHeader)(nil).SizeSSZ(sizer) + 96 + return size +} + +// DefineSSZ defines the SSZ encoding for the SignedBeaconBlockHeader object. +func (b *SignedBeaconBlockHeader) DefineSSZ(codec *ssz.Codec) { + ssz.DefineStaticObject(codec, &b.Header) + ssz.DefineStaticBytes(codec, &b.Signature) +} + +// MarshalSSZ marshals the SignedBeaconBlockHeader object to SSZ format. +func (b *SignedBeaconBlockHeader) MarshalSSZ() ([]byte, error) { + buf := make([]byte, ssz.Size(b)) + return buf, ssz.EncodeToBytes(buf, b) +} + +// UnmarshalSSZ unmarshals the SignedBeaconBlockHeader object from SSZ format. +func (b *SignedBeaconBlockHeader) UnmarshalSSZ(buf []byte) error { + return ssz.DecodeFromBytes(buf, b) +} + +// HashTreeRoot computes the SSZ hash tree root of the +// SignedBeaconBlockHeader object. +func (b *SignedBeaconBlockHeader) HashTreeRoot() common.Root { + return ssz.HashSequential(b) +} + +/* -------------------------------------------------------------------------- */ +/* Getters and Setters */ +/* -------------------------------------------------------------------------- */ + +// Getheader retrieves the header of the SignedBeaconBlockHeader. +func (b *SignedBeaconBlockHeader) GetHeader() *BeaconBlockHeader { + return b.Header +} + +// Setheader sets the header of the BeaconBlockHeader. +func (b *SignedBeaconBlockHeader) SetHeader(header *BeaconBlockHeader) { + b.Header = header +} + +// GetSignature retrieves the Signature of the SignedBeaconBlockHeader. +func (b *SignedBeaconBlockHeader) GetSignature() crypto.BLSSignature { + return b.Signature +} + +// SetSignature sets the Signature of the BeaconBlockHeader. +func (b *SignedBeaconBlockHeader) SetSignature(signature crypto.BLSSignature) { + b.Signature = signature +} + +func (b *SignedBeaconBlockHeader) IsNil() bool { + return b == nil +} diff --git a/consensus-types/types/signed_header_test.go b/consensus-types/types/signed_header_test.go new file mode 100644 index 0000000000..8a70f5f3cf --- /dev/null +++ b/consensus-types/types/signed_header_test.go @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: BUSL-1.1 +// +// Copyright (C) 2024, Berachain Foundation. All rights reserved. +// Use of this software is governed by the Business Source License included +// in the LICENSE file of this repository and at www.mariadb.com/bsl11. +// +// ANY USE OF THE LICENSED WORK IN VIOLATION OF THIS LICENSE WILL AUTOMATICALLY +// TERMINATE YOUR RIGHTS UNDER THIS LICENSE FOR THE CURRENT AND ALL OTHER +// VERSIONS OF THE LICENSED WORK. +// +// THIS LICENSE DOES NOT GRANT YOU ANY RIGHT IN ANY TRADEMARK OR LOGO OF +// LICENSOR OR ITS AFFILIATES (PROVIDED THAT YOU MAY USE A TRADEMARK OR LOGO OF +// LICENSOR AS EXPRESSLY REQUIRED BY THIS LICENSE). +// +// TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +// AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +// EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +// TITLE. + +package types_test + +import ( + "testing" + + "github.com/berachain/beacon-kit/consensus-types/types" + "github.com/berachain/beacon-kit/primitives/common" + "github.com/berachain/beacon-kit/primitives/crypto" + "github.com/berachain/beacon-kit/primitives/math" + karalabessz "github.com/karalabe/ssz" + "github.com/stretchr/testify/require" +) + +func TestSignedBeaconBlockHeader_Serialization(t *testing.T) { + header := types.NewBeaconBlockHeader( + math.Slot(100), + math.ValidatorIndex(200), + common.Root{0xde, 0xad, 0xbe, 0xef}, + common.Root{0xca, 0xca, 0xca, 0xfe}, + common.Root{0xde, 0xad, 0xca, 0xfe}, + ) + sig := crypto.BLSSignature{0xde, 0xad, 0xc4, 0xc4} + orig := new(types.SignedBeaconBlockHeader).Empty() + orig.SetHeader(header) + orig.SetSignature(sig) + + data, err := orig.MarshalSSZ() + require.NoError(t, err) + require.NotNil(t, data) + var unmarshalled types.SignedBeaconBlockHeader + err = unmarshalled.UnmarshalSSZ(data) + require.NoError(t, err) + require.Equal(t, orig, &unmarshalled) + + buf := make([]byte, karalabessz.Size(orig)) + err = karalabessz.EncodeToBytes(buf, orig) + require.NoError(t, err) + + // The two byte slices should be equal + require.Equal(t, data, buf) +} + +func TestSignedBeaconBlockHeader_EmptySerialization(t *testing.T) { + orig := new(types.SignedBeaconBlockHeader) + data, err := orig.MarshalSSZ() + require.NoError(t, err) + require.NotNil(t, data) + + var unmarshalled types.SignedBeaconBlockHeader + err = unmarshalled.UnmarshalSSZ(data) + require.NoError(t, err) + require.NotNil(t, unmarshalled.GetHeader()) + require.NotNil(t, unmarshalled.GetSignature()) + require.Equal(t, types.BeaconBlockHeader{}, *unmarshalled.GetHeader()) + + buf := make([]byte, karalabessz.Size(orig)) + err = karalabessz.EncodeToBytes(buf, orig) + require.NoError(t, err) + + // The two byte slices should be equal + require.Equal(t, data, buf) +} + +func TestSignedBeaconBlockHeader_SizeSSZ(t *testing.T) { + sigHeader := types.NewSignedBeaconBlockHeader( + types.NewBeaconBlockHeader( + math.Slot(100), + math.ValidatorIndex(200), + common.Root{0xaa}, + common.Root{0xbb}, + common.Root{0xcc}, + ), + crypto.BLSSignature{0xff}, + ) + + size := karalabessz.Size(sigHeader) + require.Equal(t, uint32(208), size) +} + +func TestSignedBeaconBlockHeader_HashTreeRoot(_ *testing.T) { + sigHeader := types.NewSignedBeaconBlockHeader( + types.NewBeaconBlockHeader( + math.Slot(100), + math.ValidatorIndex(200), + common.Root{0xaa}, + common.Root{0xbb}, + common.Root{0xcc}, + ), + crypto.BLSSignature{0xff}, + ) + _ = sigHeader.HashTreeRoot() +} diff --git a/da/blob/factory.go b/da/blob/factory.go index 199a8fa485..501c58418c 100644 --- a/da/blob/factory.go +++ b/da/blob/factory.go @@ -23,9 +23,11 @@ package blob import ( "time" + ctypes "github.com/berachain/beacon-kit/consensus-types/types" "github.com/berachain/beacon-kit/da/types" engineprimitives "github.com/berachain/beacon-kit/engine-primitives/engine-primitives" "github.com/berachain/beacon-kit/primitives/common" + "github.com/berachain/beacon-kit/primitives/crypto" "github.com/berachain/beacon-kit/primitives/math" "github.com/berachain/beacon-kit/primitives/merkle" "golang.org/x/sync/errgroup" @@ -72,6 +74,8 @@ func NewSidecarFactory[ func (f *SidecarFactory[BeaconBlockT, _]) BuildSidecars( blk BeaconBlockT, bundle engineprimitives.BlobsBundle, + signer crypto.BLSSigner, + forkData *ctypes.ForkData, ) (*types.BlobSidecars, error) { var ( blobs = bundle.GetBlobs() @@ -81,14 +85,37 @@ func (f *SidecarFactory[BeaconBlockT, _]) BuildSidecars( sidecars = make([]*types.BlobSidecar, numBlobs) body = blk.GetBody() g = errgroup.Group{} + //nolint:errcheck // should be safe + header = any(blk.GetHeader()).(*ctypes.BeaconBlockHeader) ) startTime := time.Now() defer f.metrics.measureBuildSidecarsDuration( startTime, math.U64(numBlobs), ) + + // Contrary to the spec, we do not need to sign the full + // block, because the header embeds the body's hash tree root + // already. We just need a bond between the block signer (already + // tied to CometBFT's ProposerAddress) and the sidecars. + + //nolint:errcheck // should be safe + domain := any(forkData).(*ctypes.ForkData).ComputeDomain( + f.chainSpec.DomainTypeProposer(), + ) + signingRoot := ctypes.ComputeSigningRoot( + header, + domain, + ) + signature, err := signer.Sign(signingRoot[:]) + if err != nil { + return nil, err + } + sigHeader := ctypes.NewSignedBeaconBlockHeader(header, signature) + for i := range numBlobs { g.Go(func() error { + //nolint:govet // shadow inclusionProof, err := f.BuildKZGInclusionProof( body, math.U64(i), ) @@ -96,7 +123,8 @@ func (f *SidecarFactory[BeaconBlockT, _]) BuildSidecars( return err } sidecars[i] = types.BuildBlobSidecar( - math.U64(i), blk.GetHeader(), + math.U64(i), + sigHeader, blobs[i], commitments[i], proofs[i], diff --git a/da/blob/processor.go b/da/blob/processor.go index 5a816f8cf5..f773165341 100644 --- a/da/blob/processor.go +++ b/da/blob/processor.go @@ -23,9 +23,11 @@ package blob import ( "time" + ctypes "github.com/berachain/beacon-kit/consensus-types/types" "github.com/berachain/beacon-kit/da/kzg" "github.com/berachain/beacon-kit/log" "github.com/berachain/beacon-kit/primitives/common" + "github.com/berachain/beacon-kit/primitives/crypto" "github.com/berachain/beacon-kit/primitives/math" ) @@ -45,7 +47,10 @@ type Processor[ // chainSpec defines the specifications of the blockchain. chainSpec common.ChainSpec // verifier is responsible for verifying the blobs. - verifier *verifier[BlobSidecarT, BlobSidecarsT] + verifier *verifier[ + BlobSidecarT, + BlobSidecarsT, + ] // blockBodyOffsetFn is a function that calculates the block body offset // based on the slot and chain specifications. blockBodyOffsetFn func(math.Slot, common.ChainSpec) (uint64, error) @@ -90,9 +95,13 @@ func NewProcessor[ // VerifySidecars verifies the blobs and ensures they match the local state. func (sp *Processor[ - AvailabilityStoreT, _, ConsensusSidecarsT, _, _, + AvailabilityStoreT, _, ConsensusSidecarsT, _, BlobSidecarsT, ]) VerifySidecars( cs ConsensusSidecarsT, + verifierFn func( + blkHeader *ctypes.BeaconBlockHeader, + signature crypto.BLSSignature, + ) error, ) error { var ( sidecars = cs.GetSidecars() @@ -116,7 +125,10 @@ func (sp *Processor[ // Verify the blobs and ensure they match the local state. return sp.verifier.verifySidecars( - sidecars, kzgOffset, blkHeader, + sidecars, + kzgOffset, + blkHeader, + verifierFn, ) } @@ -139,7 +151,7 @@ func (sp *Processor[ // If we have reached this point, we can safely assume that the blobs are // valid and can be persisted, as well as that index 0 is filled. return avs.Persist( - sidecars.Get(0).GetBeaconBlockHeader().GetSlot(), + sidecars.Get(0).GetSignedBeaconBlockHeader().GetHeader().GetSlot(), sidecars, ) } diff --git a/da/blob/types.go b/da/blob/types.go index 6da7dc8122..10759b93c8 100644 --- a/da/blob/types.go +++ b/da/blob/types.go @@ -61,10 +61,10 @@ type ConsensusSidecars[BlobSidecarsT any] interface { } type Sidecar interface { - GetBeaconBlockHeader() *ctypes.BeaconBlockHeader GetBlob() eip4844.Blob GetKzgProof() eip4844.KZGProof GetKzgCommitment() eip4844.KZGCommitment + GetSignedBeaconBlockHeader() *ctypes.SignedBeaconBlockHeader } type Sidecars[SidecarT any] interface { @@ -78,6 +78,7 @@ type Sidecars[SidecarT any] interface { // ChainSpec represents a chain spec. type ChainSpec interface { MaxBlobCommitmentsPerBlock() uint64 + DomainTypeProposer() common.DomainType } // TelemetrySink is an interface for sending metrics to a telemetry backend. diff --git a/da/blob/verifier.go b/da/blob/verifier.go index 194cc840f1..ebebdcff21 100644 --- a/da/blob/verifier.go +++ b/da/blob/verifier.go @@ -27,6 +27,7 @@ import ( ctypes "github.com/berachain/beacon-kit/consensus-types/types" "github.com/berachain/beacon-kit/da/kzg" + "github.com/berachain/beacon-kit/primitives/crypto" "github.com/berachain/beacon-kit/primitives/math" "golang.org/x/sync/errgroup" ) @@ -63,22 +64,38 @@ func (bv *verifier[_, BlobSidecarsT]) verifySidecars( sidecars BlobSidecarsT, kzgOffset uint64, blkHeader *ctypes.BeaconBlockHeader, + verifierFn func( + blkHeader *ctypes.BeaconBlockHeader, + signature crypto.BLSSignature, + ) error, ) error { defer bv.metrics.measureVerifySidecarsDuration( time.Now(), math.U64(sidecars.Len()), bv.proofVerifier.GetImplementation(), ) - // check that sideracs block headers match with header of the + g, _ := errgroup.WithContext(context.Background()) + + // check that sidecars block headers match with header of the // corresponding block for i, s := range sidecars.GetSidecars() { - if !s.GetBeaconBlockHeader().Equals(blkHeader) { + if !s.GetSignedBeaconBlockHeader().GetHeader().Equals(blkHeader) { return fmt.Errorf("unequal block header: idx: %d", i) } + g.Go(func() error { + var sigHeader = s.GetSignedBeaconBlockHeader() + err := verifierFn( + blkHeader, + sigHeader.GetSignature(), + ) + if err != nil { + return err + } + return nil + }) } // Verify the inclusion proofs on the blobs concurrently. - g, _ := errgroup.WithContext(context.Background()) g.Go(func() error { // TODO: KZGOffset needs to be configurable and not // passed in. @@ -92,10 +109,6 @@ func (bv *verifier[_, BlobSidecarsT]) verifySidecars( return bv.verifyKZGProofs(sidecars) }) - g.Go(func() error { - return sidecars.ValidateBlockRoots() - }) - // Wait for all goroutines to finish and return the result. return g.Wait() } diff --git a/da/da/types.go b/da/da/types.go index cb60421e29..3792f72a54 100644 --- a/da/da/types.go +++ b/da/da/types.go @@ -22,12 +22,15 @@ package da import ( ctypes "github.com/berachain/beacon-kit/consensus-types/types" + "github.com/berachain/beacon-kit/primitives/crypto" ) // BlobProcessor is the interface for the blobs processor. type BlobProcessor[ AvailabilityStoreT, - ConsensusSidecarsT, BlobSidecarsT any, + ConsensusSidecarsT, + BlobSidecarsT any, + ] interface { // ProcessSidecars processes the blobs and ensures they match the local // state. @@ -36,7 +39,13 @@ type BlobProcessor[ sidecars BlobSidecarsT, ) error // VerifySidecars verifies the blobs and ensures they match the local state. - VerifySidecars(sidecars ConsensusSidecarsT) error + VerifySidecars( + sidecars ConsensusSidecarsT, + verifierFn func( + blkHeader *ctypes.BeaconBlockHeader, + signature crypto.BLSSignature, + ) error, + ) error } type ConsensusSidecars[BlobSidecarsT any] interface { diff --git a/da/store/store.go b/da/store/store.go index 14a055357a..408ce98076 100644 --- a/da/store/store.go +++ b/da/store/store.go @@ -85,7 +85,7 @@ func (s *Store[BeaconBlockT]) Persist( if !s.chainSpec.WithinDAPeriod( // slot in which the sidecar was included. // (Safe to assume all sidecars are in same slot at this point). - sidecars.Sidecars[0].BeaconBlockHeader.GetSlot(), + sidecars.Sidecars[0].SignedBeaconBlockHeader.Header.GetSlot(), // current slot slot, ) { diff --git a/da/types/sidecar.go b/da/types/sidecar.go index 976aa90a4f..dbda4699a0 100644 --- a/da/types/sidecar.go +++ b/da/types/sidecar.go @@ -30,7 +30,7 @@ import ( ) // BlobSidecar as per the Ethereum 2.0 specification: -// https://github.com/ethereum/consensus-specs/blob/dev/specs/deneb/p2p-interface.md?ref=bankless.ghost.io#blobsidecar +// https://github.com/ethereum/consensus-specs/blob/dev/specs/deneb/p2p-interface.md#blobsidecar type BlobSidecar struct { // Index represents the index of the blob in the block. Index uint64 @@ -42,7 +42,7 @@ type BlobSidecar struct { KzgProof eip4844.KZGProof // BeaconBlockHeader represents the beacon block header for which this blob // is being included. - BeaconBlockHeader *ctypes.BeaconBlockHeader + SignedBeaconBlockHeader *ctypes.SignedBeaconBlockHeader // InclusionProof is the inclusion proof of the blob in the beacon block // body. InclusionProof []common.Root @@ -52,20 +52,19 @@ type BlobSidecar struct { // beacon block. func BuildBlobSidecar( index math.U64, - header *ctypes.BeaconBlockHeader, + header *ctypes.SignedBeaconBlockHeader, blob *eip4844.Blob, commitment eip4844.KZGCommitment, proof eip4844.KZGProof, inclusionProof []common.Root, ) *BlobSidecar { - //nolint:errcheck // should be safe return &BlobSidecar{ - Index: index.Unwrap(), - Blob: *blob, - KzgCommitment: commitment, - KzgProof: proof, - BeaconBlockHeader: any(header).(*ctypes.BeaconBlockHeader), - InclusionProof: inclusionProof, + Index: index.Unwrap(), + Blob: *blob, + KzgCommitment: commitment, + KzgProof: proof, + SignedBeaconBlockHeader: header, + InclusionProof: inclusionProof, } } @@ -74,8 +73,8 @@ func BuildBlobSidecar( func (b *BlobSidecar) HasValidInclusionProof( kzgOffset uint64, ) bool { - // Verify the inclusion proof. - return merkle.IsValidMerkleBranch( + header := b.GetSignedBeaconBlockHeader().GetHeader() + return header != nil && merkle.IsValidMerkleBranch( b.KzgCommitment.HashTreeRoot(), b.InclusionProof, //#nosec:G701 // safe. @@ -83,7 +82,7 @@ func (b *BlobSidecar) HasValidInclusionProof( len(b.InclusionProof), ), // TODO: use KZG_INCLUSION_PROOF_DEPTH calculation. kzgOffset+b.Index, - b.BeaconBlockHeader.BodyRoot, + header.BodyRoot, ) } @@ -100,7 +99,7 @@ func (b *BlobSidecar) GetKzgCommitment() eip4844.KZGCommitment { } func (b *BlobSidecar) GetBeaconBlockHeader() *ctypes.BeaconBlockHeader { - return b.BeaconBlockHeader + return b.SignedBeaconBlockHeader.Header } // DefineSSZ defines the SSZ encoding for the BlobSidecar object. @@ -109,18 +108,19 @@ func (b *BlobSidecar) DefineSSZ(codec *ssz.Codec) { ssz.DefineStaticBytes(codec, &b.Blob) ssz.DefineStaticBytes(codec, &b.KzgCommitment) ssz.DefineStaticBytes(codec, &b.KzgProof) - ssz.DefineStaticObject(codec, &b.BeaconBlockHeader) + ssz.DefineStaticObject(codec, &b.SignedBeaconBlockHeader) //nolint:mnd // depth of 8 ssz.DefineCheckedArrayOfStaticBytes(codec, &b.InclusionProof, 8) } // SizeSSZ returns the size of the BlobSidecar object in SSZ encoding. -func (b *BlobSidecar) SizeSSZ(*ssz.Sizer) uint32 { +func (b *BlobSidecar) SizeSSZ(sizer *ssz.Sizer) uint32 { + ssize := (*ctypes.SignedBeaconBlockHeader)(nil).SizeSSZ(sizer) return 8 + // Index 131072 + // Blob 48 + // KzgCommitment 48 + // KzgProof - 112 + // BeaconBlockHeader + ssize + // SignedBeaconBlockHeader 8*32 // InclusionProof } @@ -145,3 +145,7 @@ func (b *BlobSidecar) MarshalSSZTo(buf []byte) ([]byte, error) { func (b *BlobSidecar) HashTreeRoot() common.Root { return ssz.HashSequential(b) } + +func (b *BlobSidecar) GetSignedBeaconBlockHeader() *ctypes.SignedBeaconBlockHeader { + return b.SignedBeaconBlockHeader +} diff --git a/da/types/sidecar_test.go b/da/types/sidecar_test.go index 20d0cf64b5..7223642daf 100644 --- a/da/types/sidecar_test.go +++ b/da/types/sidecar_test.go @@ -28,6 +28,7 @@ import ( "github.com/berachain/beacon-kit/da/types" byteslib "github.com/berachain/beacon-kit/primitives/bytes" "github.com/berachain/beacon-kit/primitives/common" + "github.com/berachain/beacon-kit/primitives/crypto" "github.com/berachain/beacon-kit/primitives/eip4844" "github.com/berachain/beacon-kit/primitives/math" "github.com/stretchr/testify/assert" @@ -49,7 +50,10 @@ func TestSidecarMarshalling(t *testing.T) { } sidecar := types.BuildBlobSidecar( 1, - &ctypes.BeaconBlockHeader{}, + &ctypes.SignedBeaconBlockHeader{ + Header: &ctypes.BeaconBlockHeader{}, + Signature: crypto.BLSSignature{}, + }, &blob, eip4844.KZGCommitment{}, eip4844.KZGProof{}, @@ -98,8 +102,11 @@ func TestHasValidInclusionProof(t *testing.T) { } return types.BuildBlobSidecar( math.U64(0), - &ctypes.BeaconBlockHeader{ - BodyRoot: [32]byte{3}, + &ctypes.SignedBeaconBlockHeader{ + Header: &ctypes.BeaconBlockHeader{ + BodyRoot: [32]byte{3}, + }, + Signature: crypto.BLSSignature{}, }, &eip4844.Blob{}, eip4844.KZGCommitment{}, @@ -115,7 +122,7 @@ func TestHasValidInclusionProof(t *testing.T) { sidecar: func(*testing.T) *types.BlobSidecar { return types.BuildBlobSidecar( math.U64(0), - &ctypes.BeaconBlockHeader{}, + &ctypes.SignedBeaconBlockHeader{}, &eip4844.Blob{}, eip4844.KZGCommitment{}, eip4844.KZGProof{}, @@ -160,8 +167,11 @@ func TestHashTreeRoot(t *testing.T) { } return types.BuildBlobSidecar( math.U64(1), - &ctypes.BeaconBlockHeader{ - BodyRoot: [32]byte{7, 8, 9}, + &ctypes.SignedBeaconBlockHeader{ + Header: &ctypes.BeaconBlockHeader{ + BodyRoot: [32]byte{7, 8, 9}, + }, + Signature: crypto.BLSSignature{0xde, 0xad}, }, &eip4844.Blob{0, 1, 2, 3, 4, 5, 6, 7}, eip4844.KZGCommitment{1, 2, 3}, @@ -170,9 +180,11 @@ func TestHashTreeRoot(t *testing.T) { ) }, expectedResult: [32]uint8{ - 0xce, 0x75, 0x41, 0x87, 0x48, 0x46, 0x6d, 0x26, 0x9e, 0x72, 0x5d, - 0xac, 0x5a, 0x6e, 0x36, 0xed, 0x8c, 0x2a, 0x98, 0x19, 0x6b, 0xe1, - 0xf1, 0xf7, 0xfa, 0xe1, 0x20, 0x5d, 0x2b, 0x3c, 0x57, 0x6a}, + 0xd8, 0xb2, 0x91, 0x39, 0x93, 0x75, 0x38, 0x1f, + 0xd4, 0xdf, 0xef, 0xa7, 0x16, 0x91, 0xd9, 0x9, + 0x3, 0x62, 0xee, 0x3a, 0x79, 0x96, 0x57, 0xc4, + 0xc4, 0x6d, 0x86, 0x79, 0x78, 0x1b, 0xb4, 0xe3, + }, expectError: false, }, } diff --git a/da/types/sidecars.go b/da/types/sidecars.go index 59ab055ffe..76836d97ae 100644 --- a/da/types/sidecars.go +++ b/da/types/sidecars.go @@ -61,9 +61,9 @@ func (bs *BlobSidecars) ValidateBlockRoots() error { // We only need to check if there is more than // a single blob in the sidecar. if sc := bs.Sidecars; len(sc) > 1 { - firstHtr := sc[0].BeaconBlockHeader.HashTreeRoot() + firstHtr := sc[0].SignedBeaconBlockHeader.HashTreeRoot() for i := 1; i < len(sc); i++ { - if firstHtr != sc[i].BeaconBlockHeader.HashTreeRoot() { + if firstHtr != sc[i].SignedBeaconBlockHeader.HashTreeRoot() { return ErrSidecarContainsDifferingBlockRoots } } diff --git a/da/types/sidecars_test.go b/da/types/sidecars_test.go index 3d0d9b895c..5f9bca3ebf 100644 --- a/da/types/sidecars_test.go +++ b/da/types/sidecars_test.go @@ -28,6 +28,7 @@ import ( "github.com/berachain/beacon-kit/da/types" byteslib "github.com/berachain/beacon-kit/primitives/bytes" "github.com/berachain/beacon-kit/primitives/common" + "github.com/berachain/beacon-kit/primitives/crypto" "github.com/berachain/beacon-kit/primitives/eip4844" "github.com/berachain/beacon-kit/primitives/math" "github.com/stretchr/testify/require" @@ -45,7 +46,10 @@ func TestEmptySidecarMarshalling(t *testing.T) { sidecar := types.BuildBlobSidecar( math.U64(0), - &ctypes.BeaconBlockHeader{}, + &ctypes.SignedBeaconBlockHeader{ + Header: &ctypes.BeaconBlockHeader{}, + Signature: crypto.BLSSignature{}, + }, &eip4844.Blob{}, eip4844.KZGCommitment{}, [48]byte{}, @@ -95,11 +99,13 @@ func TestValidateBlockRoots(t *testing.T) { validSidecar := types.BuildBlobSidecar( math.U64(0), - &ctypes.BeaconBlockHeader{ - StateRoot: [32]byte{1}, - BodyRoot: [32]byte{2}, + &ctypes.SignedBeaconBlockHeader{ + Header: &ctypes.BeaconBlockHeader{ + StateRoot: [32]byte{1}, + BodyRoot: [32]byte{2}, + }, + Signature: crypto.BLSSignature{}, }, - &eip4844.Blob{}, [48]byte{}, [48]byte{}, @@ -120,9 +126,12 @@ func TestValidateBlockRoots(t *testing.T) { // Create a sample BlobSidecar with invalid roots differentBlockRootSidecar := types.BuildBlobSidecar( math.U64(0), - &ctypes.BeaconBlockHeader{ - StateRoot: [32]byte{1}, - BodyRoot: [32]byte{3}, + &ctypes.SignedBeaconBlockHeader{ + Header: &ctypes.BeaconBlockHeader{ + StateRoot: [32]byte{1}, + BodyRoot: [32]byte{3}, + }, + Signature: crypto.BLSSignature{}, }, &eip4844.Blob{}, eip4844.KZGCommitment{}, diff --git a/node-core/components/interfaces.go b/node-core/components/interfaces.go index fd804bf04d..51e3630e79 100644 --- a/node-core/components/interfaces.go +++ b/node-core/components/interfaces.go @@ -199,11 +199,15 @@ type ( // state. VerifySidecars( sidecars ConsensusSidecarsT, + verifierFn func( + blkHeader *ctypes.BeaconBlockHeader, + signature crypto.BLSSignature, + ) error, ) error } BlobSidecar interface { - GetBeaconBlockHeader() *ctypes.BeaconBlockHeader + GetSignedBeaconBlockHeader() *ctypes.SignedBeaconBlockHeader GetBlob() eip4844.Blob GetKzgProof() eip4844.KZGProof GetKzgCommitment() eip4844.KZGCommitment @@ -235,6 +239,10 @@ type ( sidecars BlobSidecarsT, kzgOffset uint64, blkHeader *ctypes.BeaconBlockHeader, + verifierFn func( + blkHeader *ctypes.BeaconBlockHeader, + signature crypto.BLSSignature, + ) error, ) error } @@ -617,6 +625,10 @@ type ( st BeaconStateT, blk BeaconBlockT, ) (transition.ValidatorUpdates, error) + GetSidecarVerifierFn(st BeaconStateT) ( + func(blkHeader *ctypes.BeaconBlockHeader, signature crypto.BLSSignature) error, + error, + ) } SidecarFactory[BeaconBlockT any, BlobSidecarsT any] interface { @@ -624,6 +636,8 @@ type ( BuildSidecars( blk BeaconBlockT, blobs engineprimitives.BlobsBundle, + signer crypto.BLSSigner, + forkData *ctypes.ForkData, ) (BlobSidecarsT, error) } diff --git a/state-transition/core/state_processor_sidecar_verifier.go b/state-transition/core/state_processor_sidecar_verifier.go new file mode 100644 index 0000000000..a2352b8125 --- /dev/null +++ b/state-transition/core/state_processor_sidecar_verifier.go @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: BUSL-1.1 +// +// Copyright (C) 2024, Berachain Foundation. All rights reserved. +// Use of this software is governed by the Business Source License included +// in the LICENSE file of this repository and at www.mariadb.com/bsl11. +// +// ANY USE OF THE LICENSED WORK IN VIOLATION OF THIS LICENSE WILL AUTOMATICALLY +// TERMINATE YOUR RIGHTS UNDER THIS LICENSE FOR THE CURRENT AND ALL OTHER +// VERSIONS OF THE LICENSED WORK. +// +// THIS LICENSE DOES NOT GRANT YOU ANY RIGHT IN ANY TRADEMARK OR LOGO OF +// LICENSOR OR ITS AFFILIATES (PROVIDED THAT YOU MAY USE A TRADEMARK OR LOGO OF +// LICENSOR AS EXPRESSLY REQUIRED BY THIS LICENSE). +// +// TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +// AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +// EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +// TITLE. + +package core + +import ( + ctypes "github.com/berachain/beacon-kit/consensus-types/types" + "github.com/berachain/beacon-kit/primitives/common" + "github.com/berachain/beacon-kit/primitives/crypto" + "github.com/berachain/beacon-kit/primitives/version" +) + +func (sp *StateProcessor[ + _, _, BeaconStateT, _, _, _, _, +]) GetSidecarVerifierFn( + st BeaconStateT, +) ( + func(blkHeader *ctypes.BeaconBlockHeader, signature crypto.BLSSignature) error, + error, +) { + slot, err := st.GetSlot() + if err != nil { + return nil, err + } + epoch := sp.cs.SlotToEpoch(slot) + + genesisValidatorsRoot, err := st.GetGenesisValidatorsRoot() + if err != nil { + return nil, err + } + + fd := *ctypes.NewForkData( + version.FromUint32[common.Version]( + sp.cs.ActiveForkVersionForEpoch(epoch), + ), genesisValidatorsRoot, + ) + domain := fd.ComputeDomain(sp.cs.DomainTypeProposer()) + + return func( + blkHeader *ctypes.BeaconBlockHeader, + signature crypto.BLSSignature, + ) error { + //nolint:govet // shadow + proposer, err := st.ValidatorByIndex(blkHeader.GetProposerIndex()) + if err != nil { + return err + } + signingRoot := ctypes.ComputeSigningRoot( + blkHeader, + domain, + ) + return sp.signer.VerifySignature( + proposer.GetPubkey(), + signingRoot[:], + signature, + ) + }, nil +}