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

Create payload->blob type system #1120

Merged
merged 43 commits into from
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
9b222e0
Create type system
litt3 Jan 16, 2025
bae8843
Rename encoding
litt3 Jan 16, 2025
ebf9979
Merge branch 'master' into payload-blob-type-system
litt3 Jan 16, 2025
d66ddf2
Fix padding logic
litt3 Jan 16, 2025
f6021ea
Pad upon poly construction
litt3 Jan 16, 2025
422c2a8
Change name to ProtoBlob
litt3 Jan 16, 2025
a3d184b
Update comments
litt3 Jan 16, 2025
856f5fc
Revert "Change name to ProtoBlob"
litt3 Jan 17, 2025
1eeec85
Rename PayloadHeader to EncodedPayloadHeader
litt3 Jan 21, 2025
a30a4ad
Merge branch 'master' into payload-blob-type-system
litt3 Feb 7, 2025
5eab8dd
Rename to PayloadEncodingVersion
litt3 Feb 7, 2025
132e8de
Do a bunch of cleanup
litt3 Feb 7, 2025
da63536
Fix PayloadVersion naming
litt3 Feb 7, 2025
3016cac
Merge branch 'master' into payload-blob-type-system
litt3 Feb 7, 2025
9f98462
Use new conversion method
litt3 Feb 7, 2025
58b76c1
Add better descriptions to codec utils
litt3 Feb 10, 2025
916573f
Do test work
litt3 Feb 10, 2025
7eed618
Add length checks
litt3 Feb 11, 2025
c3a6291
Merge branch 'master' into payload-blob-type-system
litt3 Feb 11, 2025
b604c82
Write test for power of 2 util
litt3 Feb 11, 2025
2ef8e99
Write more tests
litt3 Feb 12, 2025
13d16c7
Finish utils tests
litt3 Feb 12, 2025
22e8134
Do FFT work
litt3 Feb 12, 2025
23b49c4
Clean up encoded payload
litt3 Feb 12, 2025
2add571
Merge branch 'master' into payload-blob-type-system
litt3 Feb 12, 2025
e5363e4
Make encoding version uint8 instead of byte
litt3 Feb 13, 2025
7801044
Add explanation for 0x00 padding
litt3 Feb 13, 2025
c70fad8
Use fuzz testing
litt3 Feb 13, 2025
617faa7
Simplify class structure
litt3 Feb 18, 2025
e22238b
Improve docs and naming
litt3 Feb 18, 2025
db45645
Improve test var name
litt3 Feb 18, 2025
c70db76
Use smarter power of 2 calc
litt3 Feb 18, 2025
5a0350f
Merge branch 'master' into payload-blob-type-system
litt3 Feb 18, 2025
3c239cc
Collapse fuzz tests
litt3 Feb 18, 2025
15805a9
Fix borked alignment
litt3 Feb 18, 2025
c028c00
Rename blobLength to blobLengthSymbols
litt3 Feb 18, 2025
693d513
Fix test
litt3 Feb 18, 2025
f1a4e85
Use serialze/deserialize nomenclature
litt3 Feb 18, 2025
3db9274
Separate v2 logic into new package
litt3 Feb 19, 2025
9ac68ac
Merge branch 'master' into payload-blob-type-system
litt3 Feb 19, 2025
b8d43f5
Use better package scheme
litt3 Feb 19, 2025
266e33a
Merge branch 'master' into payload-blob-type-system
litt3 Feb 19, 2025
781515c
Fix merge
litt3 Feb 19, 2025
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
16 changes: 9 additions & 7 deletions api/clients/codecs/blob_codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,24 @@ import (
"fmt"
)

type BlobEncodingVersion byte
type PayloadEncodingVersion uint8

const (
// This minimal blob encoding contains a 32 byte header = [0x00, version byte, uint32 len of data, 0x00, 0x00,...]
// PayloadEncodingVersion0 entails a 32 byte header = [0x00, version byte, big-endian uint32 len of payload, 0x00, 0x00,...]
// followed by the encoded data [0x00, 31 bytes of data, 0x00, 31 bytes of data,...]
DefaultBlobEncoding BlobEncodingVersion = 0x0
//
// Each group of 32 bytes starts with a 0x00 byte so that they can be parsed as valid bn254 field elements.
PayloadEncodingVersion0 PayloadEncodingVersion = 0x0
)

type BlobCodec interface {
DecodeBlob(encodedData []byte) ([]byte, error)
EncodeBlob(rawData []byte) ([]byte, error)
}

func BlobEncodingVersionToCodec(version BlobEncodingVersion) (BlobCodec, error) {
func BlobEncodingVersionToCodec(version PayloadEncodingVersion) (BlobCodec, error) {
switch version {
case DefaultBlobEncoding:
case PayloadEncodingVersion0:
return DefaultBlobCodec{}, nil
default:
return nil, fmt.Errorf("unsupported blob encoding version: %x", version)
Expand All @@ -33,7 +35,7 @@ func GenericDecodeBlob(data []byte) ([]byte, error) {
// version byte is stored in [1], because [0] is always 0 to ensure the codecBlobHeader is a valid bn254 element
// see https://github.com/Layr-Labs/eigenda/blob/master/api/clients/codecs/default_blob_codec.go#L21
// TODO: we should prob be working over a struct with methods such as GetBlobEncodingVersion() to prevent index errors
version := BlobEncodingVersion(data[1])
version := PayloadEncodingVersion(data[1])
codec, err := BlobEncodingVersionToCodec(version)
if err != nil {
return nil, err
Expand All @@ -49,7 +51,7 @@ func GenericDecodeBlob(data []byte) ([]byte, error) {

// CreateCodec creates a new BlobCodec based on the defined polynomial form of payloads, and the desired
// BlobEncodingVersion
func CreateCodec(payloadPolynomialForm PolynomialForm, version BlobEncodingVersion) (BlobCodec, error) {
func CreateCodec(payloadPolynomialForm PolynomialForm, version PayloadEncodingVersion) (BlobCodec, error) {
lowLevelCodec, err := BlobEncodingVersionToCodec(version)
if err != nil {
return nil, fmt.Errorf("create low level codec: %w", err)
Expand Down
2 changes: 1 addition & 1 deletion api/clients/codecs/default_blob_codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func (v DefaultBlobCodec) EncodeBlob(rawData []byte) ([]byte, error) {
codecBlobHeader := make([]byte, 32)
// first byte is always 0 to ensure the codecBlobHeader is a valid bn254 element
// encode version byte
codecBlobHeader[1] = byte(DefaultBlobEncoding)
codecBlobHeader[1] = byte(PayloadEncodingVersion0)

// encode length as uint32
binary.BigEndian.PutUint32(codecBlobHeader[2:6], uint32(len(rawData))) // uint32 should be more than enough to store the length (approx 4gb)
Expand Down
2 changes: 1 addition & 1 deletion api/clients/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ type EigenDAClientConfig struct {
DisableTLS bool

// The blob encoding version to use when writing blobs from the high level interface.
PutBlobEncodingVersion codecs.BlobEncodingVersion
PutBlobEncodingVersion codecs.PayloadEncodingVersion

// Point verification mode does an IFFT on data before it is written, and does an FFT on data after it is read.
// This makes it possible to open points on the KZG commitment to prove that the field elements correspond to
Expand Down
20 changes: 10 additions & 10 deletions api/clients/eigenda_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func TestPutRetrieveBlobIFFTSuccess(t *testing.T) {
CustomQuorumIDs: []uint{},
SignerPrivateKeyHex: "75f9e29cac7f5774d106adb355ef294987ce39b7863b75bb3f2ea42ca160926d",
DisableTLS: false,
PutBlobEncodingVersion: codecs.DefaultBlobEncoding,
PutBlobEncodingVersion: codecs.PayloadEncodingVersion0,
DisablePointVerificationMode: false,
WaitForFinalization: true,
},
Expand Down Expand Up @@ -132,7 +132,7 @@ func TestPutRetrieveBlobIFFTNoDecodeSuccess(t *testing.T) {
CustomQuorumIDs: []uint{},
SignerPrivateKeyHex: "75f9e29cac7f5774d106adb355ef294987ce39b7863b75bb3f2ea42ca160926d",
DisableTLS: false,
PutBlobEncodingVersion: codecs.DefaultBlobEncoding,
PutBlobEncodingVersion: codecs.PayloadEncodingVersion0,
DisablePointVerificationMode: false,
WaitForFinalization: true,
},
Expand Down Expand Up @@ -202,7 +202,7 @@ func TestPutRetrieveBlobNoIFFTSuccess(t *testing.T) {
CustomQuorumIDs: []uint{},
SignerPrivateKeyHex: "75f9e29cac7f5774d106adb355ef294987ce39b7863b75bb3f2ea42ca160926d",
DisableTLS: false,
PutBlobEncodingVersion: codecs.DefaultBlobEncoding,
PutBlobEncodingVersion: codecs.PayloadEncodingVersion0,
DisablePointVerificationMode: true,
WaitForFinalization: true,
},
Expand Down Expand Up @@ -234,7 +234,7 @@ func TestPutBlobFailDispersal(t *testing.T) {
CustomQuorumIDs: []uint{},
SignerPrivateKeyHex: "75f9e29cac7f5774d106adb355ef294987ce39b7863b75bb3f2ea42ca160926d",
DisableTLS: false,
PutBlobEncodingVersion: codecs.DefaultBlobEncoding,
PutBlobEncodingVersion: codecs.PayloadEncodingVersion0,
WaitForFinalization: true,
},
Client: disperserClient,
Expand Down Expand Up @@ -266,7 +266,7 @@ func TestPutBlobFailureInsufficentSignatures(t *testing.T) {
CustomQuorumIDs: []uint{},
SignerPrivateKeyHex: "75f9e29cac7f5774d106adb355ef294987ce39b7863b75bb3f2ea42ca160926d",
DisableTLS: false,
PutBlobEncodingVersion: codecs.DefaultBlobEncoding,
PutBlobEncodingVersion: codecs.PayloadEncodingVersion0,
WaitForFinalization: true,
},
Client: disperserClient,
Expand Down Expand Up @@ -298,7 +298,7 @@ func TestPutBlobFailureGeneral(t *testing.T) {
CustomQuorumIDs: []uint{},
SignerPrivateKeyHex: "75f9e29cac7f5774d106adb355ef294987ce39b7863b75bb3f2ea42ca160926d",
DisableTLS: false,
PutBlobEncodingVersion: codecs.DefaultBlobEncoding,
PutBlobEncodingVersion: codecs.PayloadEncodingVersion0,
WaitForFinalization: true,
},
Client: disperserClient,
Expand Down Expand Up @@ -330,7 +330,7 @@ func TestPutBlobFailureUnknown(t *testing.T) {
CustomQuorumIDs: []uint{},
SignerPrivateKeyHex: "75f9e29cac7f5774d106adb355ef294987ce39b7863b75bb3f2ea42ca160926d",
DisableTLS: false,
PutBlobEncodingVersion: codecs.DefaultBlobEncoding,
PutBlobEncodingVersion: codecs.PayloadEncodingVersion0,
WaitForFinalization: true,
},
Client: disperserClient,
Expand Down Expand Up @@ -364,7 +364,7 @@ func TestPutBlobFinalizationTimeout(t *testing.T) {
CustomQuorumIDs: []uint{},
SignerPrivateKeyHex: "75f9e29cac7f5774d106adb355ef294987ce39b7863b75bb3f2ea42ca160926d",
DisableTLS: false,
PutBlobEncodingVersion: codecs.DefaultBlobEncoding,
PutBlobEncodingVersion: codecs.PayloadEncodingVersion0,
WaitForFinalization: true,
},
Client: disperserClient,
Expand Down Expand Up @@ -423,7 +423,7 @@ func TestPutBlobIndividualRequestTimeout(t *testing.T) {
CustomQuorumIDs: []uint{},
SignerPrivateKeyHex: "75f9e29cac7f5774d106adb355ef294987ce39b7863b75bb3f2ea42ca160926d",
DisableTLS: false,
PutBlobEncodingVersion: codecs.DefaultBlobEncoding,
PutBlobEncodingVersion: codecs.PayloadEncodingVersion0,
WaitForFinalization: true,
},
Client: disperserClient,
Expand Down Expand Up @@ -485,7 +485,7 @@ func TestPutBlobTotalTimeout(t *testing.T) {
CustomQuorumIDs: []uint{},
SignerPrivateKeyHex: "75f9e29cac7f5774d106adb355ef294987ce39b7863b75bb3f2ea42ca160926d",
DisableTLS: false,
PutBlobEncodingVersion: codecs.DefaultBlobEncoding,
PutBlobEncodingVersion: codecs.PayloadEncodingVersion0,
WaitForFinalization: true,
},
Client: disperserClient,
Expand Down
118 changes: 118 additions & 0 deletions api/clients/v2/codecs/blob.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package codecs

import (
"fmt"

"github.com/Layr-Labs/eigenda/api/clients/codecs"
"github.com/Layr-Labs/eigenda/encoding/fft"
"github.com/Layr-Labs/eigenda/encoding/rs"
"github.com/Layr-Labs/eigenda/encoding/utils/codec"
"github.com/consensys/gnark-crypto/ecc/bn254/fr"
)

// Blob is data that is dispersed on eigenDA.
//
// A Blob is represented under the hood by an array of field elements, which represent a polynomial in coefficient form
type Blob struct {
coeffPolynomial []fr.Element
// blobLengthSymbols must be a power of 2, and should match the blobLength claimed in the BlobCommitment
//
// This value must be specified, rather than computed from the length of the coeffPolynomial, due to an edge case
// illustrated by the following example: imagine a user disperses a very small blob, only 64 bytes, and the last 40
// bytes are trailing zeros. When a different user fetches the blob from a relay, it's possible that the relay could
// truncate the trailing zeros. If we were to say that blobLengthSymbols = nextPowerOf2(len(coeffPolynomial)), then the
// user fetching and reconstructing this blob would determine that the blob length is 1 symbol, when it's actually 2.
blobLengthSymbols uint32
}

// DeserializeBlob initializes a Blob from bytes
func DeserializeBlob(bytes []byte, blobLengthSymbols uint32) (*Blob, error) {
coeffPolynomial, err := rs.ToFrArray(bytes)
if err != nil {
return nil, fmt.Errorf("bytes to field elements: %w", err)
}

return BlobFromPolynomial(coeffPolynomial, blobLengthSymbols)
}

// BlobFromPolynomial initializes a blob from a polynomial
func BlobFromPolynomial(coeffPolynomial []fr.Element, blobLengthSymbols uint32) (*Blob, error) {
return &Blob{
coeffPolynomial: coeffPolynomial,
blobLengthSymbols: blobLengthSymbols,
}, nil
}

// Serialize gets the raw bytes of the Blob
func (b *Blob) Serialize() []byte {
return rs.SerializeFieldElements(b.coeffPolynomial)
}

// ToPayload converts the Blob into a Payload
//
// The payloadForm indicates how payloads are interpreted. The way that payloads are interpreted dictates what
// conversion, if any, must be performed when creating a payload from the blob.
func (b *Blob) ToPayload(payloadForm codecs.PolynomialForm) (*Payload, error) {
encodedPayload, err := b.toEncodedPayload(payloadForm)
if err != nil {
return nil, fmt.Errorf("to encoded payload: %w", err)
}

payload, err := encodedPayload.decode()
if err != nil {
return nil, fmt.Errorf("decode payload: %w", err)
}

return payload, nil
}

// toEncodedPayload creates an encodedPayload from the blob
//
// The payloadForm indicates how payloads are interpreted. The way that payloads are interpreted dictates what
// conversion, if any, must be performed when creating an encoded payload from the blob.
func (b *Blob) toEncodedPayload(payloadForm codecs.PolynomialForm) (*encodedPayload, error) {
maxPermissiblePayloadLength, err := codec.GetMaxPermissiblePayloadLength(b.blobLengthSymbols)
if err != nil {
return nil, fmt.Errorf("get max permissible payload length: %w", err)
}

var payloadElements []fr.Element
switch payloadForm {
case codecs.PolynomialFormCoeff:
// the payload is interpreted as coefficients of the polynomial, so no conversion needs to be done, given that
// eigenda also interprets blobs as coefficients
payloadElements = b.coeffPolynomial
case codecs.PolynomialFormEval:
// the payload is interpreted as evaluations of the polynomial, so the coefficient representation contained
// in the blob must be converted to the evaluation form
payloadElements, err = b.computeEvalPoly()
if err != nil {
return nil, fmt.Errorf("compute eval poly: %w", err)
}
default:
return nil, fmt.Errorf("invalid polynomial form")
}

encodedPayload, err := encodedPayloadFromElements(payloadElements, maxPermissiblePayloadLength)
if err != nil {
return nil, fmt.Errorf("encoded payload from elements %w", err)
}

return encodedPayload, nil
}

// computeEvalPoly converts a blob's coeffPoly to an evalPoly, using the FFT operation
func (b *Blob) computeEvalPoly() ([]fr.Element, error) {
// TODO (litt3): this could conceivably be optimized, so that multiple objects share an instance of FFTSettings,
// which has enough roots of unity for general use. If the following construction of FFTSettings ever proves
// to present a computational burden, consider making this change.
fftSettings := fft.FFTSettingsFromBlobLengthSymbols(b.blobLengthSymbols)

// the FFT method pads to the next power of 2, so we don't need to do that manually
fftedElements, err := fftSettings.FFT(b.coeffPolynomial, false)
if err != nil {
return nil, fmt.Errorf("perform FFT: %w", err)
}

return fftedElements, nil
}
40 changes: 40 additions & 0 deletions api/clients/v2/codecs/blob_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package codecs

import (
"bytes"
"testing"

"github.com/Layr-Labs/eigenda/api/clients/codecs"
"github.com/stretchr/testify/require"
)

// TestBlobConversion checks that internal blob conversion methods produce consistent results
func FuzzBlobConversion(f *testing.F) {
for _, seed := range [][]byte{{}, {0x00}, {0xFF}, {0x00, 0x00}, {0xFF, 0xFF}, bytes.Repeat([]byte{0x55}, 1000)} {
f.Add(seed)
}

f.Fuzz(
func(t *testing.T, originalData []byte) {
testBlobConversionForForm(t, originalData, codecs.PolynomialFormEval)
testBlobConversionForForm(t, originalData, codecs.PolynomialFormCoeff)
})

}

func testBlobConversionForForm(t *testing.T, payloadBytes []byte, payloadForm codecs.PolynomialForm) {
blob, err := NewPayload(payloadBytes).ToBlob(payloadForm)
require.NoError(t, err)

blobDeserialized, err := DeserializeBlob(blob.Serialize(), blob.blobLengthSymbols)
require.NoError(t, err)

payloadFromBlob, err := blob.ToPayload(payloadForm)
require.NoError(t, err)

payloadFromDeserializedBlob, err := blobDeserialized.ToPayload(payloadForm)
require.NoError(t, err)

require.Equal(t, payloadFromBlob.Serialize(), payloadFromDeserializedBlob.Serialize())
require.Equal(t, payloadBytes, payloadFromBlob.Serialize())
}
Loading
Loading