diff --git a/CHANGELOG.md b/CHANGELOG.md index c91183f3c7eb..03eae5766d80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,7 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve - Improvements to HTTP response handling. [pr](https://github.com/prysmaticlabs/prysm/pull/14673) - Updated `Blobs` endpoint to return additional metadata fields. - Made QUIC the default method to connect with peers. +- Check kzg commitments align with blobs and proofs for beacon api end point. - Increase Max Payload Size in Gossip. ### Deprecated diff --git a/beacon-chain/blockchain/kzg/BUILD.bazel b/beacon-chain/blockchain/kzg/BUILD.bazel index 82c77fb7ca40..57f8c9e30a83 100644 --- a/beacon-chain/blockchain/kzg/BUILD.bazel +++ b/beacon-chain/blockchain/kzg/BUILD.bazel @@ -26,8 +26,7 @@ go_test( deps = [ "//consensus-types/blocks:go_default_library", "//testing/require:go_default_library", - "@com_github_consensys_gnark_crypto//ecc/bls12-381/fr:go_default_library", + "//testing/util:go_default_library", "@com_github_crate_crypto_go_kzg_4844//:go_default_library", - "@com_github_sirupsen_logrus//:go_default_library", ], ) diff --git a/beacon-chain/blockchain/kzg/validation_test.go b/beacon-chain/blockchain/kzg/validation_test.go index 832c6f5d79cc..62bb21bb5b74 100644 --- a/beacon-chain/blockchain/kzg/validation_test.go +++ b/beacon-chain/blockchain/kzg/validation_test.go @@ -1,51 +1,14 @@ package kzg import ( - "bytes" - "crypto/sha256" - "encoding/binary" "testing" - "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" GoKZG "github.com/crate-crypto/go-kzg-4844" "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks" "github.com/prysmaticlabs/prysm/v5/testing/require" - "github.com/sirupsen/logrus" + "github.com/prysmaticlabs/prysm/v5/testing/util" ) -func deterministicRandomness(seed int64) [32]byte { - // Converts an int64 to a byte slice - buf := new(bytes.Buffer) - err := binary.Write(buf, binary.BigEndian, seed) - if err != nil { - logrus.WithError(err).Error("Failed to write int64 to bytes buffer") - return [32]byte{} - } - bytes := buf.Bytes() - - return sha256.Sum256(bytes) -} - -// Returns a serialized random field element in big-endian -func GetRandFieldElement(seed int64) [32]byte { - bytes := deterministicRandomness(seed) - var r fr.Element - r.SetBytes(bytes[:]) - - return GoKZG.SerializeScalar(r) -} - -// Returns a random blob using the passed seed as entropy -func GetRandBlob(seed int64) GoKZG.Blob { - var blob GoKZG.Blob - bytesPerBlob := GoKZG.ScalarsPerBlob * GoKZG.SerializedScalarSize - for i := 0; i < bytesPerBlob; i += GoKZG.SerializedScalarSize { - fieldElementBytes := GetRandFieldElement(seed + int64(i)) - copy(blob[i:i+GoKZG.SerializedScalarSize], fieldElementBytes[:]) - } - return blob -} - func GenerateCommitmentAndProof(blob GoKZG.Blob) (GoKZG.KZGCommitment, GoKZG.KZGProof, error) { commitment, err := kzgContext.BlobToKZGCommitment(blob, 0) if err != nil { @@ -74,7 +37,7 @@ func TestBytesToAny(t *testing.T) { } func TestGenerateCommitmentAndProof(t *testing.T) { - blob := GetRandBlob(123) + blob := util.GetRandBlob(123) commitment, proof, err := GenerateCommitmentAndProof(blob) require.NoError(t, err) expectedCommitment := GoKZG.KZGCommitment{180, 218, 156, 194, 59, 20, 10, 189, 186, 254, 132, 93, 7, 127, 104, 172, 238, 240, 237, 70, 83, 89, 1, 152, 99, 0, 165, 65, 143, 62, 20, 215, 230, 14, 205, 95, 28, 245, 54, 25, 160, 16, 178, 31, 232, 207, 38, 85} diff --git a/beacon-chain/rpc/eth/beacon/BUILD.bazel b/beacon-chain/rpc/eth/beacon/BUILD.bazel index 3b44d87bb904..e042fe22106f 100644 --- a/beacon-chain/rpc/eth/beacon/BUILD.bazel +++ b/beacon-chain/rpc/eth/beacon/BUILD.bazel @@ -58,6 +58,7 @@ go_library( "//runtime/version:go_default_library", "//time/slots:go_default_library", "@com_github_ethereum_go_ethereum//common/hexutil:go_default_library", + "@com_github_ethereum_go_ethereum//crypto/kzg4844:go_default_library", "@com_github_pkg_errors//:go_default_library", "@com_github_prysmaticlabs_fastssz//:go_default_library", "@com_github_sirupsen_logrus//:go_default_library", @@ -118,6 +119,7 @@ go_test( "//testing/require:go_default_library", "//testing/util:go_default_library", "//time/slots:go_default_library", + "@com_github_crate_crypto_go_kzg_4844//:go_default_library", "@com_github_ethereum_go_ethereum//common/hexutil:go_default_library", "@com_github_pkg_errors//:go_default_library", "@com_github_prysmaticlabs_go_bitfield//:go_default_library", diff --git a/beacon-chain/rpc/eth/beacon/handlers.go b/beacon-chain/rpc/eth/beacon/handlers.go index f6a8a3a273e0..04ace1525715 100644 --- a/beacon-chain/rpc/eth/beacon/handlers.go +++ b/beacon-chain/rpc/eth/beacon/handlers.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/pkg/errors" ssz "github.com/prysmaticlabs/fastssz" "github.com/prysmaticlabs/prysm/v5/api" @@ -1032,21 +1033,17 @@ func unmarshalStrict(data []byte, v interface{}) error { func (s *Server) validateBroadcast(ctx context.Context, r *http.Request, blk *eth.GenericSignedBeaconBlock) error { switch r.URL.Query().Get(broadcastValidationQueryParam) { case broadcastValidationConsensus: - b, err := blocks.NewSignedBeaconBlock(blk.Block) - if err != nil { - return errors.Wrapf(err, "could not create signed beacon block") - } - if err = s.validateConsensus(ctx, b); err != nil { + if err := s.validateConsensus(ctx, blk); err != nil { return errors.Wrap(err, "consensus validation failed") } case broadcastValidationConsensusAndEquivocation: + if err := s.validateConsensus(r.Context(), blk); err != nil { + return errors.Wrap(err, "consensus validation failed") + } b, err := blocks.NewSignedBeaconBlock(blk.Block) if err != nil { return errors.Wrapf(err, "could not create signed beacon block") } - if err = s.validateConsensus(r.Context(), b); err != nil { - return errors.Wrap(err, "consensus validation failed") - } if err = s.validateEquivocation(b.Block()); err != nil { return errors.Wrap(err, "equivocation validation failed") } @@ -1056,7 +1053,12 @@ func (s *Server) validateBroadcast(ctx context.Context, r *http.Request, blk *et return nil } -func (s *Server) validateConsensus(ctx context.Context, blk interfaces.ReadOnlySignedBeaconBlock) error { +func (s *Server) validateConsensus(ctx context.Context, b *eth.GenericSignedBeaconBlock) error { + blk, err := blocks.NewSignedBeaconBlock(b.Block) + if err != nil { + return errors.Wrapf(err, "could not create signed beacon block") + } + parentBlockRoot := blk.Block().ParentRoot() parentBlock, err := s.Blocker.Block(ctx, parentBlockRoot[:]) if err != nil { @@ -1076,6 +1078,24 @@ func (s *Server) validateConsensus(ctx context.Context, blk interfaces.ReadOnlyS if err != nil { return errors.Wrap(err, "could not execute state transition") } + + var blobs [][]byte + var proofs [][]byte + switch { + case blk.Version() == version.Electra: + blobs = b.GetElectra().Blobs + proofs = b.GetElectra().KzgProofs + case blk.Version() == version.Deneb: + blobs = b.GetDeneb().Blobs + proofs = b.GetDeneb().KzgProofs + default: + return nil + } + + if err := s.validateBlobSidecars(blk, blobs, proofs); err != nil { + return err + } + return nil } @@ -1086,6 +1106,25 @@ func (s *Server) validateEquivocation(blk interfaces.ReadOnlyBeaconBlock) error return nil } +func (s *Server) validateBlobSidecars(blk interfaces.SignedBeaconBlock, blobs [][]byte, proofs [][]byte) error { + if blk.Version() < version.Deneb { + return nil + } + kzgs, err := blk.Block().Body().BlobKzgCommitments() + if err != nil { + return errors.Wrap(err, "could not get blob kzg commitments") + } + if len(blobs) != len(proofs) || len(blobs) != len(kzgs) { + return errors.New("number of blobs, proofs, and commitments do not match") + } + for i, blob := range blobs { + if err := kzg4844.VerifyBlobProof(kzg4844.Blob(blob), kzg4844.Commitment(kzgs[i]), kzg4844.Proof(proofs[i])); err != nil { + return errors.Wrap(err, "could not verify blob proof") + } + } + return nil +} + // GetBlockRoot retrieves the root of a block. func (s *Server) GetBlockRoot(w http.ResponseWriter, r *http.Request) { ctx, span := trace.StartSpan(r.Context(), "beacon.GetBlockRoot") diff --git a/beacon-chain/rpc/eth/beacon/handlers_test.go b/beacon-chain/rpc/eth/beacon/handlers_test.go index 76eeefe5a5ab..7defd71f55c9 100644 --- a/beacon-chain/rpc/eth/beacon/handlers_test.go +++ b/beacon-chain/rpc/eth/beacon/handlers_test.go @@ -12,6 +12,7 @@ import ( "testing" "time" + GoKZG "github.com/crate-crypto/go-kzg-4844" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/pkg/errors" "github.com/prysmaticlabs/go-bitfield" @@ -33,6 +34,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks" "github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + "github.com/prysmaticlabs/prysm/v5/crypto/bls" "github.com/prysmaticlabs/prysm/v5/encoding/bytesutil" "github.com/prysmaticlabs/prysm/v5/network/httputil" eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" @@ -2922,8 +2924,6 @@ func TestValidateConsensus(t *testing.T) { require.NoError(t, err) block, err := util.GenerateFullBlock(st, privs, util.DefaultBlockGenConfig(), st.Slot()) require.NoError(t, err) - sbb, err := blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) parentRoot, err := parentSbb.Block().HashTreeRoot() require.NoError(t, err) server := &Server{ @@ -2931,7 +2931,11 @@ func TestValidateConsensus(t *testing.T) { Stater: &testutil.MockStater{StatesByRoot: map[[32]byte]state.BeaconState{bytesutil.ToBytes32(parentBlock.Block.StateRoot): parentState}}, } - require.NoError(t, server.validateConsensus(ctx, sbb)) + require.NoError(t, server.validateConsensus(ctx, ð.GenericSignedBeaconBlock{ + Block: ð.GenericSignedBeaconBlock_Phase0{ + Phase0: block, + }, + })) } func TestValidateEquivocation(t *testing.T) { @@ -4152,3 +4156,24 @@ func TestServer_broadcastBlobSidecars(t *testing.T) { require.NoError(t, server.broadcastSeenBlockSidecars(context.Background(), blk, b.GetDeneb().Blobs, b.GetDeneb().KzgProofs)) require.LogsContain(t, hook, "Broadcasted blob sidecar for already seen block") } + +func Test_validateBlobSidecars(t *testing.T) { + blob := util.GetRandBlob(123) + commitment := GoKZG.KZGCommitment{180, 218, 156, 194, 59, 20, 10, 189, 186, 254, 132, 93, 7, 127, 104, 172, 238, 240, 237, 70, 83, 89, 1, 152, 99, 0, 165, 65, 143, 62, 20, 215, 230, 14, 205, 95, 28, 245, 54, 25, 160, 16, 178, 31, 232, 207, 38, 85} + proof := GoKZG.KZGProof{128, 110, 116, 170, 56, 111, 126, 87, 229, 234, 211, 42, 110, 150, 129, 206, 73, 142, 167, 243, 90, 149, 240, 240, 236, 204, 143, 182, 229, 249, 81, 27, 153, 171, 83, 70, 144, 250, 42, 1, 188, 215, 71, 235, 30, 7, 175, 86} + blk := util.NewBeaconBlockDeneb() + blk.Block.Body.BlobKzgCommitments = [][]byte{commitment[:]} + b, err := blocks.NewSignedBeaconBlock(blk) + require.NoError(t, err) + s := &Server{} + require.NoError(t, s.validateBlobSidecars(b, [][]byte{blob[:]}, [][]byte{proof[:]})) + + require.ErrorContains(t, "number of blobs, proofs, and commitments do not match", s.validateBlobSidecars(b, [][]byte{blob[:]}, [][]byte{})) + + sk, err := bls.RandKey() + require.NoError(t, err) + blk.Block.Body.BlobKzgCommitments = [][]byte{sk.PublicKey().Marshal()} + b, err = blocks.NewSignedBeaconBlock(blk) + require.NoError(t, err) + require.ErrorContains(t, "could not verify blob proof: can't verify opening proof", s.validateBlobSidecars(b, [][]byte{blob[:]}, [][]byte{proof[:]})) +} diff --git a/testing/util/BUILD.bazel b/testing/util/BUILD.bazel index 16154398cf7e..1603f4f3506f 100644 --- a/testing/util/BUILD.bazel +++ b/testing/util/BUILD.bazel @@ -64,6 +64,8 @@ go_library( "//testing/assertions:go_default_library", "//testing/require:go_default_library", "//time/slots:go_default_library", + "@com_github_consensys_gnark_crypto//ecc/bls12-381/fr:go_default_library", + "@com_github_crate_crypto_go_kzg_4844//:go_default_library", "@com_github_ethereum_go_ethereum//common:go_default_library", "@com_github_ethereum_go_ethereum//common/hexutil:go_default_library", "@com_github_ethereum_go_ethereum//core/types:go_default_library", diff --git a/testing/util/deneb.go b/testing/util/deneb.go index 12a888bf9d11..8899c7e25472 100644 --- a/testing/util/deneb.go +++ b/testing/util/deneb.go @@ -1,10 +1,14 @@ package util import ( + "bytes" + "crypto/sha256" "encoding/binary" "math/big" "testing" + "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" + GoKZG "github.com/crate-crypto/go-kzg-4844" "github.com/ethereum/go-ethereum/common" gethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/signing" @@ -19,6 +23,7 @@ import ( ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v5/testing/require" "github.com/prysmaticlabs/prysm/v5/time/slots" + "github.com/sirupsen/logrus" ) type DenebBlockGeneratorOption func(*denebBlockGenerator) @@ -197,3 +202,36 @@ func ExtendBlocksPlusBlobs(t *testing.T, blks []blocks.ROBlock, size int) ([]blo return blks, blobs } + +func deterministicRandomness(seed int64) [32]byte { + // Converts an int64 to a byte slice + buf := new(bytes.Buffer) + err := binary.Write(buf, binary.BigEndian, seed) + if err != nil { + logrus.WithError(err).Error("Failed to write int64 to bytes buffer") + return [32]byte{} + } + bytes := buf.Bytes() + + return sha256.Sum256(bytes) +} + +// Returns a serialized random field element in big-endian +func GetRandFieldElement(seed int64) [32]byte { + bytes := deterministicRandomness(seed) + var r fr.Element + r.SetBytes(bytes[:]) + + return GoKZG.SerializeScalar(r) +} + +// Returns a random blob using the passed seed as entropy +func GetRandBlob(seed int64) GoKZG.Blob { + var blob GoKZG.Blob + bytesPerBlob := GoKZG.ScalarsPerBlob * GoKZG.SerializedScalarSize + for i := 0; i < bytesPerBlob; i += GoKZG.SerializedScalarSize { + fieldElementBytes := GetRandFieldElement(seed + int64(i)) + copy(blob[i:i+GoKZG.SerializedScalarSize], fieldElementBytes[:]) + } + return blob +}