Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v2] Disperser request validation #915

Merged
merged 2 commits into from
Nov 21, 2024
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
44 changes: 25 additions & 19 deletions disperser/apiserver/disperse_blob_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,33 +70,40 @@ func (s *DispersalServerV2) StoreBlob(ctx context.Context, data []byte, blobHead
func (s *DispersalServerV2) validateDispersalRequest(req *pb.DisperseBlobRequest) error {
data := req.GetData()
blobSize := len(data)
if uint64(blobSize) > s.maxNumSymbolsPerBlob*encoding.BYTES_PER_SYMBOL {
return api.NewErrorInvalidArg(fmt.Sprintf("blob size cannot exceed %v bytes", s.maxNumSymbolsPerBlob*encoding.BYTES_PER_SYMBOL))
}
if blobSize == 0 {
return api.NewErrorInvalidArg("blob size must be greater than 0")
}
blobLength := encoding.GetBlobLengthPowerOf2(uint(blobSize))
if blobLength > uint(s.maxNumSymbolsPerBlob) {
return api.NewErrorInvalidArg("blob size too big")
}

blobHeaderProto := req.GetBlobHeader()
if blobHeaderProto.GetCommitment() == nil {
return api.NewErrorInvalidArg("blob header must contain commitments")
}

if len(blobHeaderProto.GetQuorumNumbers()) == 0 {
return api.NewErrorInvalidArg("blob header must contain at least one quorum number")
}

if len(blobHeaderProto.GetQuorumNumbers()) > int(s.onchainState.QuorumCount) {
return api.NewErrorInvalidArg(fmt.Sprintf("too many quorum numbers specified: maximum is %d", s.onchainState.QuorumCount))
}

for _, quorum := range blobHeaderProto.GetQuorumNumbers() {
if quorum > corev2.MaxQuorumID || uint8(quorum) >= s.onchainState.QuorumCount {
return api.NewErrorInvalidArg(fmt.Sprintf("invalid quorum number %d; maximum is %d", quorum, s.onchainState.QuorumCount))
}
}

// validate every 32 bytes is a valid field element
_, err := rs.ToFrArray(data)
if err != nil {
s.logger.Error("failed to convert a 32bytes as a field element", "err", err)
return api.NewErrorInvalidArg("encountered an error to convert a 32-bytes into a valid field element, please use the correct format where every 32bytes(big-endian) is less than 21888242871839275222246405745257275088548364400416034343698204186575808495617")
}

if !containsRequiredQuorum(s.onchainState.RequiredQuorums, blobHeaderProto.GetQuorumNumbers()) {
return api.NewErrorInvalidArg(fmt.Sprintf("request must contain at least one required quorum: %v does not specify any of %v", blobHeaderProto.GetQuorumNumbers(), s.onchainState.RequiredQuorums))
}

if _, ok := s.onchainState.BlobVersionParameters[corev2.BlobVersion(blobHeaderProto.GetVersion())]; !ok {
validVersions := make([]int32, 0, len(s.onchainState.BlobVersionParameters))
for version := range s.onchainState.BlobVersionParameters {
Expand All @@ -113,18 +120,17 @@ func (s *DispersalServerV2) validateDispersalRequest(req *pb.DisperseBlobRequest
return api.NewErrorInvalidArg(fmt.Sprintf("authentication failed: %s", err.Error()))
}

// TODO(ian-shim): validate commitment, length is power of 2 and less than maxNumSymbolsPerBlob, payment metadata

return nil
}
if len(blobHeader.PaymentMetadata.AccountID) == 0 || blobHeader.PaymentMetadata.BinIndex == 0 || blobHeader.PaymentMetadata.CumulativePayment == nil {
return api.NewErrorInvalidArg("invalid payment metadata")
}

func containsRequiredQuorum(requiredQuorums []uint8, quorumNumbers []uint32) bool {
for _, required := range requiredQuorums {
for _, quorum := range quorumNumbers {
if uint8(quorum) == required {
return true
}
}
commitments, err := s.prover.GetCommitmentsForPaddedLength(data)
if err != nil {
return api.NewErrorInternal(fmt.Sprintf("failed to get commitments: %v", err))
}
if !commitments.Equal(&blobHeader.BlobCommitments) {
return api.NewErrorInvalidArg("invalid blob commitment")
}
return false

return nil
}
87 changes: 82 additions & 5 deletions disperser/apiserver/server_v2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func TestV2DisperseBlobRequestValidation(t *testing.T) {
data := make([]byte, 50)
_, err := rand.Read(data)
assert.NoError(t, err)

signer := auth.NewLocalBlobRequestSigner(privateKeyHex)
data = codec.ConvertByPaddingEmptyByte(data)
commitments, err := prover.GetCommitmentsForPaddedLength(data)
assert.NoError(t, err)
Expand Down Expand Up @@ -150,10 +150,10 @@ func TestV2DisperseBlobRequestValidation(t *testing.T) {
})
assert.ErrorContains(t, err, "too many quorum numbers specified")

// request without required quorums
// request with invalid quorum
invalidReqProto = &pbcommonv2.BlobHeader{
Version: 0,
QuorumNumbers: []uint32{2},
QuorumNumbers: []uint32{2, 54},
Commitment: commitmentProto,
PaymentHeader: &pbcommon.PaymentHeader{
AccountId: accountID,
Expand All @@ -165,7 +165,7 @@ func TestV2DisperseBlobRequestValidation(t *testing.T) {
Data: data,
BlobHeader: invalidReqProto,
})
assert.ErrorContains(t, err, "request must contain at least one required quorum")
assert.ErrorContains(t, err, "invalid quorum")

// request with invalid blob version
invalidReqProto = &pbcommonv2.BlobHeader{
Expand Down Expand Up @@ -201,6 +201,83 @@ func TestV2DisperseBlobRequestValidation(t *testing.T) {
BlobHeader: invalidReqProto,
})
assert.ErrorContains(t, err, "authentication failed")

// request with invalid payment metadata
invalidReqProto = &pbcommonv2.BlobHeader{
Version: 0,
QuorumNumbers: []uint32{0, 1},
Commitment: commitmentProto,
PaymentHeader: &pbcommon.PaymentHeader{
AccountId: accountID,
BinIndex: 0,
CumulativePayment: big.NewInt(100).Bytes(),
},
}
blobHeader, err := corev2.BlobHeaderFromProtobuf(invalidReqProto)
assert.NoError(t, err)
sig, err := signer.SignBlobRequest(blobHeader)
assert.NoError(t, err)
invalidReqProto.Signature = sig

_, err = c.DispersalServerV2.DisperseBlob(context.Background(), &pbv2.DisperseBlobRequest{
Data: data,
BlobHeader: invalidReqProto,
})
assert.ErrorContains(t, err, "invalid payment metadata")

// request with invalid commitment
invalidCommitment := commitmentProto
invalidCommitment.Length = commitmentProto.Length - 1
invalidReqProto = &pbcommonv2.BlobHeader{
Version: 0,
QuorumNumbers: []uint32{0, 1},
Commitment: invalidCommitment,
PaymentHeader: &pbcommon.PaymentHeader{
AccountId: accountID,
BinIndex: 5,
CumulativePayment: big.NewInt(100).Bytes(),
},
}
blobHeader, err = corev2.BlobHeaderFromProtobuf(invalidReqProto)
assert.NoError(t, err)
sig, err = signer.SignBlobRequest(blobHeader)
assert.NoError(t, err)
invalidReqProto.Signature = sig
_, err = c.DispersalServerV2.DisperseBlob(context.Background(), &pbv2.DisperseBlobRequest{
Data: data,
BlobHeader: invalidReqProto,
})
assert.ErrorContains(t, err, "invalid blob commitment")

// request with blob size exceeding the limit
data = make([]byte, 321)
_, err = rand.Read(data)
assert.NoError(t, err)
data = codec.ConvertByPaddingEmptyByte(data)
commitments, err = prover.GetCommitmentsForPaddedLength(data)
assert.NoError(t, err)
commitmentProto, err = commitments.ToProtobuf()
assert.NoError(t, err)
validHeader := &pbcommonv2.BlobHeader{
Version: 0,
QuorumNumbers: []uint32{0, 1},
Commitment: commitmentProto,
PaymentHeader: &pbcommon.PaymentHeader{
AccountId: accountID,
BinIndex: 5,
CumulativePayment: big.NewInt(100).Bytes(),
},
}
blobHeader, err = corev2.BlobHeaderFromProtobuf(validHeader)
assert.NoError(t, err)
sig, err = signer.SignBlobRequest(blobHeader)
assert.NoError(t, err)
validHeader.Signature = sig
_, err = c.DispersalServerV2.DisperseBlob(context.Background(), &pbv2.DisperseBlobRequest{
Data: data,
BlobHeader: validHeader,
})
assert.ErrorContains(t, err, "blob size too big")
}

func TestV2GetBlobStatus(t *testing.T) {
Expand Down Expand Up @@ -362,7 +439,7 @@ func newTestServerV2(t *testing.T) *testComponents {
s := apiserver.NewDispersalServerV2(disperser.ServerConfig{
GrpcPort: "51002",
GrpcTimeout: 1 * time.Second,
}, rateConfig, blobStore, blobMetadataStore, chainReader, nil, auth.NewAuthenticator(), prover, 100, time.Hour, logger)
}, rateConfig, blobStore, blobMetadataStore, chainReader, nil, auth.NewAuthenticator(), prover, 10, time.Hour, logger)

err = s.RefreshOnchainState(context.Background())
assert.NoError(t, err)
Expand Down
3 changes: 2 additions & 1 deletion disperser/cmd/apiserver/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/Layr-Labs/eigenda/common/geth"
"github.com/Layr-Labs/eigenda/common/ratelimit"
"github.com/Layr-Labs/eigenda/disperser/apiserver"
"github.com/Layr-Labs/eigenda/encoding"
"github.com/Layr-Labs/eigenda/encoding/kzg"
"github.com/urfave/cli"
)
Expand Down Expand Up @@ -150,7 +151,7 @@ var (
MaxNumSymbolsPerBlob = cli.UintFlag{
Name: common.PrefixFlag(FlagPrefix, "max-num-symbols-per-blob"),
Usage: "max number of symbols per blob. This flag is only relevant in v2",
Value: 65_536,
Value: 16 * 1024 * 1024 / encoding.BYTES_PER_SYMBOL, // this should allow for 16MiB blobs
EnvVar: common.PrefixEnvVar(envVarPrefix, "MAX_NUM_SYMBOLS_PER_BLOB"),
Required: false,
}
Expand Down
47 changes: 47 additions & 0 deletions encoding/data.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package encoding

import (
"bytes"

pbcommon "github.com/Layr-Labs/eigenda/api/grpc/common"
"github.com/consensys/gnark-crypto/ecc/bn254"
"github.com/consensys/gnark-crypto/ecc/bn254/fr"
Expand Down Expand Up @@ -54,6 +56,51 @@ func (c *BlobCommitments) ToProtobuf() (*pbcommon.BlobCommitment, error) {
}, nil
}

// Equal checks if two BlobCommitments are equal
func (c *BlobCommitments) Equal(c1 *BlobCommitments) bool {
if c.Length != c1.Length {
return false
}

cCommitment, err := c.Commitment.Serialize()
if err != nil {
return false
}
c1Commitment, err := c1.Commitment.Serialize()
if err != nil {
return false
}
if !bytes.Equal(cCommitment, c1Commitment) {
return false
}

cLengthCommitment, err := c.LengthCommitment.Serialize()
if err != nil {
return false
}
c1LengthCommitment, err := c1.LengthCommitment.Serialize()
if err != nil {
return false
}
if !bytes.Equal(cLengthCommitment, c1LengthCommitment) {
return false
}

cLengthProof, err := c.LengthProof.Serialize()
if err != nil {
return false
}
c1LengthProof, err := c1.LengthProof.Serialize()
if err != nil {
return false
}
if !bytes.Equal(cLengthProof, c1LengthProof) {
return false
}

return true
}

func BlobCommitmentsFromProtobuf(c *pbcommon.BlobCommitment) (*BlobCommitments, error) {
commitment, err := new(G1Commitment).Deserialize(c.Commitment)
if err != nil {
Expand Down
Loading