diff --git a/api/clients/v2/disperser_client.go b/api/clients/v2/disperser_client.go index 4f4415fea..923579156 100644 --- a/api/clients/v2/disperser_client.go +++ b/api/clients/v2/disperser_client.go @@ -213,9 +213,47 @@ func (c *disperserClient) DisperseBlob( return nil, [32]byte{}, err } + if verifyReceivedBlobKey(blobHeader, reply) != nil { + return nil, [32]byte{}, fmt.Errorf("verify received blob key: %w", err) + } + return &blobStatus, corev2.BlobKey(reply.GetBlobKey()), nil } +// verifyReceivedBlobKey computes the BlobKey from the BlobHeader which was sent to the disperser, and compares it with +// the BlobKey which was returned by the disperser in the DisperseBlobReply +// +// A successful verification guarantees that the disperser didn't make any modifications to the BlobHeader that it +// received from this client. +// +// This function returns nil if the verification succeeds, and otherwise returns an error describing the failure +func verifyReceivedBlobKey( + // the blob header which was constructed locally and sent to the disperser + blobHeader *corev2.BlobHeader, + // the reply received back from the disperser + disperserReply *disperser_rpc.DisperseBlobReply, +) error { + + actualBlobKey, err := blobHeader.BlobKey() + if err != nil { + // this shouldn't be possible, since the blob key has already been used when signing dispersal + return fmt.Errorf("computing blob key: %w", err) + } + + blobKeyFromDisperser, err := corev2.BytesToBlobKey(disperserReply.GetBlobKey()) + if err != nil { + return fmt.Errorf("converting returned bytes to blob key: %w", err) + } + + if actualBlobKey != blobKeyFromDisperser { + return fmt.Errorf( + "blob key returned by disperser (%v) doesn't match blob which was dispersed (%v)", + blobKeyFromDisperser, actualBlobKey) + } + + return nil +} + // GetBlobStatus returns the status of a blob with the given blob key. func (c *disperserClient) GetBlobStatus(ctx context.Context, blobKey corev2.BlobKey) (*disperser_rpc.BlobStatusReply, error) { err := c.initOnceGrpcConnection() diff --git a/api/clients/v2/disperser_client_test.go b/api/clients/v2/disperser_client_test.go new file mode 100644 index 000000000..8d8b08b0e --- /dev/null +++ b/api/clients/v2/disperser_client_test.go @@ -0,0 +1,51 @@ +package clients + +import ( + "math/big" + "testing" + + v2 "github.com/Layr-Labs/eigenda/api/grpc/disperser/v2" + "github.com/Layr-Labs/eigenda/core" + corev2 "github.com/Layr-Labs/eigenda/core/v2" + "github.com/Layr-Labs/eigenda/encoding" + "github.com/stretchr/testify/require" +) + +func TestVerifyReceivedBlobKey(t *testing.T) { + blobCommitments := encoding.BlobCommitments{ + Commitment: &encoding.G1Commitment{}, + LengthCommitment: &encoding.G2Commitment{}, + LengthProof: &encoding.LengthProof{}, + Length: 4, + } + + quorumNumbers := make([]core.QuorumID, 1) + quorumNumbers[0] = 8 + + paymentMetadata := core.PaymentMetadata{ + AccountID: "asdf", + ReservationPeriod: 5, + CumulativePayment: big.NewInt(6), + Salt: 9, + } + + blobHeader := &corev2.BlobHeader{ + BlobVersion: 0, + BlobCommitments: blobCommitments, + QuorumNumbers: quorumNumbers, + PaymentMetadata: paymentMetadata, + } + + realKey, err := blobHeader.BlobKey() + require.NoError(t, err) + + reply := v2.DisperseBlobReply{ + BlobKey: realKey[:], + } + + require.NoError(t, verifyReceivedBlobKey(blobHeader, &reply)) + + blobHeader.BlobVersion = 1 + require.Error(t, verifyReceivedBlobKey(blobHeader, &reply), + "Any modification to the header should cause verification to fail") +}