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

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
Draft
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
85 changes: 85 additions & 0 deletions api/clients/codecs/blob.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package codecs

import (
"fmt"
)

// Blob is data that is dispersed on eigenDA.
//
// A Blob will contain either an encodedPayload, or a coeffPoly. Whether the Blob contains the former or the latter
// is determined by how the dispersing client has been configured.
type Blob struct {
encodedPayload *encodedPayload
coeffPoly *coeffPoly
}

// BlobFromEncodedPayload creates a Blob containing an encodedPayload
func blobFromEncodedPayload(encodedPayload *encodedPayload) *Blob {
return &Blob{encodedPayload: encodedPayload}
}

// blobFromCoeffPoly creates a Blob containing a coeffPoly
func blobFromCoeffPoly(poly *coeffPoly) *Blob {
return &Blob{coeffPoly: poly}
}

// NewBlob initializes a Blob from raw bytes, and the expected BlobForm
//
// This function will return an error if the input bytes cannot be successfully interpreted as the claimed BlobForm
func NewBlob(bytes []byte, blobForm BlobForm) (*Blob, error) {
switch blobForm {
case Eval:
encodedPayload, err := newEncodedPayload(bytes)
if err != nil {
return nil, fmt.Errorf("new encoded payload: %v", err)
}

return blobFromEncodedPayload(encodedPayload), nil
case Coeff:
coeffPoly, err := coeffPolyFromBytes(bytes)
if err != nil {
return nil, fmt.Errorf("new coeff poly: %v", err)
}

return blobFromCoeffPoly(coeffPoly), nil
default:
return nil, fmt.Errorf("unsupported blob form type: %v", blobForm)
}
}

// GetBytes gets the raw bytes of the Blob
func (b *Blob) GetBytes() []byte {
if b.encodedPayload == nil {
return b.encodedPayload.getBytes()
} else {
return b.coeffPoly.getBytes()
}
}

// ToPayload converts the Blob into a Payload
func (b *Blob) ToPayload() (*Payload, error) {
var encodedPayload *encodedPayload
var err error
if b.encodedPayload != nil {
encodedPayload = b.encodedPayload
} else if b.coeffPoly != nil {
evalPoly, err := b.coeffPoly.toEvalPoly()
if err != nil {
return nil, fmt.Errorf("coeff poly to eval poly: %v", err)
}

encodedPayload, err = evalPoly.toEncodedPayload()
if err != nil {
return nil, fmt.Errorf("eval poly to encoded payload: %v", err)
}
} else {
return nil, fmt.Errorf("blob has no contents")
}

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

return payload, nil
}
7 changes: 4 additions & 3 deletions api/clients/codecs/blob_codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import (
type BlobEncodingVersion byte

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
// NOTE: this encoding will soon be updated, such that the result will be padded to align to 32 bytes
PayloadEncodingVersion0 BlobEncodingVersion = 0x0
)

type BlobCodec interface {
Expand All @@ -19,7 +20,7 @@ type BlobCodec interface {

func BlobEncodingVersionToCodec(version BlobEncodingVersion) (BlobCodec, error) {
switch version {
case DefaultBlobEncoding:
case PayloadEncodingVersion0:
return DefaultBlobCodec{}, nil
default:
return nil, fmt.Errorf("unsupported blob encoding version: %x", version)
Expand Down
12 changes: 12 additions & 0 deletions api/clients/codecs/blob_form.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package codecs

// BlobForm is an enum that represents the different ways that a blob may be represented
type BlobForm uint

const (
// Eval is short for "evaluation form". The field elements represent the evaluation at the polynomial's expanded
// roots of unity
Eval BlobForm = iota
// Coeff is short for "coefficient form". The field elements represent the coefficients of the polynomial
Coeff
)
57 changes: 57 additions & 0 deletions api/clients/codecs/coeff_poly.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package codecs

import (
"fmt"
"math"

"github.com/Layr-Labs/eigenda/encoding"
"github.com/Layr-Labs/eigenda/encoding/fft"
"github.com/Layr-Labs/eigenda/encoding/rs"
"github.com/consensys/gnark-crypto/ecc/bn254/fr"
)

// coeffPoly is a polynomial in coefficient form.
//
// The underlying bytes represent 32 byte field elements, and each field element represents a coefficient
type coeffPoly struct {
fieldElements []fr.Element
}

// coeffPolyFromBytes creates a new coeffPoly from bytes. This function performs the necessary checks to guarantee that the
// bytes are well-formed, and returns a new object if they are
func coeffPolyFromBytes(bytes []byte) (*coeffPoly, error) {
paddedBytes := encoding.PadToPowerOfTwo(bytes)

fieldElements, err := rs.BytesToFieldElements(paddedBytes)
if err != nil {
return nil, fmt.Errorf("deserialize field elements: %w", err)
}

return &coeffPoly{fieldElements: fieldElements}, nil
}

// coeffPolyFromElements creates a new coeffPoly from field elements.
func coeffPolyFromElements(elements []fr.Element) (*coeffPoly, error) {
return &coeffPoly{fieldElements: elements}, nil
}

// toEvalPoly converts a coeffPoly to an evalPoly, using the FFT operation
func (cp *coeffPoly) toEvalPoly() (*evalPoly, error) {
maxScale := uint8(math.Log2(float64(len(cp.fieldElements))))
fftedElements, err := fft.NewFFTSettings(maxScale).FFT(cp.fieldElements, false)
if err != nil {
return nil, fmt.Errorf("perform FFT: %w", err)
}

evalPoly, err := evalPolyFromElements(fftedElements)
if err != nil {
return nil, fmt.Errorf("construct eval poly: %w", err)
}

return evalPoly, nil
}

// GetBytes returns the bytes that underlie the polynomial
func (cp *coeffPoly) getBytes() []byte {
return rs.FieldElementsToBytes(cp.fieldElements)
}
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
64 changes: 64 additions & 0 deletions api/clients/codecs/encoded_payload.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package codecs

import (
"encoding/binary"
"fmt"

"github.com/Layr-Labs/eigenda/encoding/utils/codec"
)

// encodedPayload represents a payload that has had an encoding applied to it
type encodedPayload struct {
bytes []byte
}

// newEncodedPayload accepts an array of bytes which represent an encodedPayload. It performs the checks necessary
// to guarantee that the bytes are well-formed, and returns a newly constructed object if they are.
//
// Note that this function does not decode the input bytes to perform additional checks, so it is possible to construct
// an encodedPayload, where an attempt to decode will fail.
func newEncodedPayload(encodedPayloadBytes []byte) (*encodedPayload, error) {
inputLen := len(encodedPayloadBytes)
if inputLen < 32 {
return nil, fmt.Errorf(
"input bytes have length %d, which is smaller than the required 32 header bytes", inputLen)
}

return &encodedPayload{
bytes: encodedPayloadBytes,
}, nil
}

// decode applies the inverse of DefaultBlobEncoding to an encodedPayload, and returns the decoded Payload
func (ep *encodedPayload) decode() (*Payload, error) {
claimedLength := binary.BigEndian.Uint32(ep.bytes[2:6])

// decode raw data modulo bn254
nonPaddedData, err := codec.RemoveInternalPadding(ep.bytes[32:])
if err != nil {
return nil, fmt.Errorf("remove internal padding: %w", err)
}

if uint32(len(nonPaddedData)) < claimedLength {
return nil, fmt.Errorf(
"data length %d is less than length claimed in encoded payload header %d",
len(nonPaddedData), claimedLength)
}

return NewPayload(nonPaddedData[0:claimedLength]), nil
}

// toEvalPoly converts an encodedPayload into an evalPoly
func (ep *encodedPayload) toEvalPoly() (*evalPoly, error) {
evalPoly, err := evalPolyFromBytes(ep.bytes)
if err != nil {
return nil, fmt.Errorf("new eval poly: %w", err)
}

return evalPoly, nil
}

// getBytes returns the raw bytes that underlie the encodedPayload
func (ep *encodedPayload) getBytes() []byte {
return ep.bytes
}
84 changes: 84 additions & 0 deletions api/clients/codecs/eval_poly.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package codecs

import (
"encoding/binary"
"fmt"
"math"

"github.com/Layr-Labs/eigenda/encoding"
"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"
)

// evalPoly is a polynomial in evaluation form.
//
// The underlying bytes represent 32 byte field elements, and the field elements represent the evaluation at the
// polynomial's roots of unity
type evalPoly struct {
fieldElements []fr.Element
}

// evalPolyFromBytes creates a new evalPoly from bytes. This function performs the necessary checks to guarantee that the
// bytes are well-formed, and returns a new object if they are
func evalPolyFromBytes(bytes []byte) (*evalPoly, error) {
paddedBytes := encoding.PadToPowerOfTwo(bytes)

fieldElements, err := rs.BytesToFieldElements(paddedBytes)
if err != nil {
return nil, fmt.Errorf("deserialize field elements: %w", err)
}

return &evalPoly{fieldElements: fieldElements}, nil
}

// evalPolyFromElements creates a new evalPoly from field elements.
func evalPolyFromElements(fieldElements []fr.Element) (*evalPoly, error) {
return &evalPoly{fieldElements: fieldElements}, nil
}

// toCoeffPoly converts an evalPoly to a coeffPoly, using the IFFT operation
func (ep *evalPoly) toCoeffPoly() (*coeffPoly, error) {
maxScale := uint8(math.Log2(float64(len(ep.fieldElements))))
ifftedElements, err := fft.NewFFTSettings(maxScale).FFT(ep.fieldElements, true)
if err != nil {
return nil, fmt.Errorf("perform IFFT: %w", err)
}

coeffPoly, err := coeffPolyFromElements(ifftedElements)
if err != nil {
return nil, fmt.Errorf("construct coeff poly: %w", err)
}

return coeffPoly, nil
}

// toEncodedPayload converts an evalPoly into an encoded payload
//
// This conversion entails removing the power-of-2 padding which is added to an encodedPayload when originally creating
// an evalPoly.
func (ep *evalPoly) toEncodedPayload() (*encodedPayload, error) {
polynomialBytes := rs.FieldElementsToBytes(ep.fieldElements)

payloadLength := binary.BigEndian.Uint32(polynomialBytes[2:6])

// add 32 to the padded data length, since the encoded payload includes an encoded payload header
encodedPayloadLength := codec.GetPaddedDataLength(payloadLength) + 32

if uint32(len(polynomialBytes)) < payloadLength {
return nil, fmt.Errorf(
"polynomial contains fewer bytes (%d) than expected encoded payload (%d), as determined by claimed length in encoded payload header (%d)",
len(polynomialBytes), encodedPayloadLength, payloadLength)
}

encodedPayloadBytes := make([]byte, encodedPayloadLength)
copy(encodedPayloadBytes, polynomialBytes[:encodedPayloadLength])

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

return encodedPayload, nil
}
Loading
Loading