diff --git a/x/dsmr/block.go b/x/dsmr/block.go index b980624848..cb6ccb49a6 100644 --- a/x/dsmr/block.go +++ b/x/dsmr/block.go @@ -4,6 +4,9 @@ package dsmr import ( + "errors" + "fmt" + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/wrappers" @@ -16,6 +19,8 @@ import ( const InitialChunkSize = 250 * 1024 +var ErrFailedChunkSigVerification = errors.New("failed to verify bls chunk signature") + type Tx interface { GetID() ids.ID GetExpiry() int64 @@ -82,6 +87,35 @@ func signChunk[T Tx]( return newChunk(chunk, pkBytes, signature) } +func (c *Chunk[T]) Verify(networkID uint32, chainID ids.ID) error { + signature, err := bls.SignatureFromBytes(c.Signature[:]) + if err != nil { + return err + } + + pk, err := bls.PublicKeyFromCompressedBytes(c.Signer[:]) + if err != nil { + return err + } + + // Construct the unsigned message from the UnsignedChunk (stripping the signature fields) + packer := wrappers.Packer{Bytes: make([]byte, 0, InitialChunkSize), MaxSize: consts.NetworkSizeLimit} + if err := codec.LinearCodec.MarshalInto(c.UnsignedChunk, &packer); err != nil { + return err + } + + msg, err := warp.NewUnsignedMessage(networkID, chainID, packer.Bytes) + if err != nil { + return fmt.Errorf("failed to create unsigned warp message from chunk: %w", err) + } + + if !bls.Verify(pk, signature, msg.Bytes()) { + return ErrFailedChunkSigVerification + } + + return nil +} + // newChunk signs a chunk func newChunk[T Tx]( unsignedChunk UnsignedChunk[T], diff --git a/x/dsmr/node_test.go b/x/dsmr/node_test.go index 918097cd20..c3ccbafde0 100644 --- a/x/dsmr/node_test.go +++ b/x/dsmr/node_test.go @@ -439,8 +439,11 @@ func TestNode_GetChunkSignature_SignValidChunk(t *testing.T) { wantErr: ErrInvalidChunk, }, { - name: "valid chunk", - verifier: NoVerifier[dsmrtest.Tx]{}, + name: "valid chunk", + verifier: ChunkVerifier[dsmrtest.Tx]{ + networkID: networkID, + chainID: chainID, + }, }, } @@ -514,22 +517,19 @@ func TestNode_GetChunkSignature_SignValidChunk(t *testing.T) { ) r.NoError(err) - chunk, err := newChunk[dsmrtest.Tx]( - UnsignedChunk[dsmrtest.Tx]{ - Producer: ids.GenerateTestNodeID(), - Beneficiary: codec.Address{123}, - Expiry: 123, - Txs: []dsmrtest.Tx{ - { - ID: ids.GenerateTestID(), - Expiry: 456, - Sponsor: codec.Address{4, 5, 6}, - }, + unsignedChunk := UnsignedChunk[dsmrtest.Tx]{ + Producer: ids.GenerateTestNodeID(), + Beneficiary: codec.Address{123}, + Expiry: 123, + Txs: []dsmrtest.Tx{ + { + ID: ids.GenerateTestID(), + Expiry: 456, + Sponsor: codec.Address{4, 5, 6}, }, }, - [48]byte{}, - [96]byte{}, - ) + } + chunk, err := signChunk[dsmrtest.Tx](unsignedChunk, networkID, chainID, pk, signer) r.NoError(err) packer := wrappers.Packer{MaxSize: MaxMessageSize} @@ -1261,16 +1261,19 @@ func newTestNodes(t *testing.T, n int) []*Node[dsmrtest.Tx] { require.NoError(t, err) pk := bls.PublicFromSecretKey(sk) signer := warp.NewSigner(sk, networkID, chainID) - - chunkStorage, err := NewChunkStorage[dsmrtest.Tx](NoVerifier[dsmrtest.Tx]{}, memdb.New()) + verifier := ChunkVerifier[dsmrtest.Tx]{networkID: networkID, chainID: chainID} + chunkStorage, err := NewChunkStorage[dsmrtest.Tx](verifier, memdb.New()) require.NoError(t, err) getChunkHandler := &GetChunkHandler[dsmrtest.Tx]{ storage: chunkStorage, } chunkSignatureRequestHandler := acp118.NewHandler(ChunkSignatureRequestVerifier[dsmrtest.Tx]{ - verifier: NoVerifier[dsmrtest.Tx]{}, - storage: chunkStorage, + verifier: ChunkVerifier[dsmrtest.Tx]{ + networkID: networkID, + chainID: chainID, + }, + storage: chunkStorage, }, signer) chunkCertificateGossipHandler := ChunkCertificateGossipHandler[dsmrtest.Tx]{ storage: chunkStorage, diff --git a/x/dsmr/p2p.go b/x/dsmr/p2p.go index f4f9baf484..da20fe18b9 100644 --- a/x/dsmr/p2p.go +++ b/x/dsmr/p2p.go @@ -131,6 +131,7 @@ func (c ChunkSignatureRequestVerifier[T]) Verify( return ErrInvalidChunk } + // check to see if this chunk was already accepted. _, accepted, err := c.storage.GetChunkBytes(chunk.Expiry, chunk.id) if err != nil && !errors.Is(err, database.ErrNotFound) { return &common.AppError{ diff --git a/x/dsmr/storage.go b/x/dsmr/storage.go index c03069a3f4..360a7a61ba 100644 --- a/x/dsmr/storage.go +++ b/x/dsmr/storage.go @@ -32,12 +32,23 @@ type Verifier[T Tx] interface { Verify(chunk Chunk[T]) error } -var _ Verifier[Tx] = (*NoVerifier[Tx])(nil) +var _ Verifier[Tx] = (*ChunkVerifier[Tx])(nil) -type NoVerifier[T Tx] struct{} +type ChunkVerifier[T Tx] struct { + networkID uint32 + chainID ids.ID +} -func (NoVerifier[T]) Verify(Chunk[T]) error { - return nil +func (c ChunkVerifier[T]) Verify(chunk Chunk[T]) error { + // TODO: + // check if the expiry of this chunk isn't in the past or too far into the future. + + // TODO: + // check if the producer was expected to produce this chunk. + + // TODO: + // add rate limiting for a given producer. + return chunk.Verify(c.networkID, c.chainID) } type StoredChunkSignature[T Tx] struct {