Skip to content

Commit

Permalink
feat: support encoding and decoding of v1 shares
Browse files Browse the repository at this point in the history
  • Loading branch information
cmwaters committed Jun 19, 2024
1 parent 46e9e0b commit a5ae4fe
Show file tree
Hide file tree
Showing 16 changed files with 178 additions and 222 deletions.
35 changes: 28 additions & 7 deletions blob/blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ const ProtoIndexWrapperTypeID = "INDX"
// MaxShareVersion is the maximum value a share version can be. See: [shares.MaxShareVersion].
const MaxShareVersion = 127

// SignerSize is the size of the signer in bytes.
const SignerSize = 20

// Blob (stands for binary large object) is a core type that represents data
// to be submitted to the Celestia network alongside an accompanying namespace
// and optional signer (for proving the author of the blob)
Expand All @@ -37,13 +40,31 @@ type Blob struct {

// New creates a new coretypes.Blob from the provided data after performing
// basic stateless checks over it.
func New(ns ns.Namespace, data []byte, shareVersion uint8, signer []byte) *Blob {
func New(ns ns.Namespace, data []byte, shareVersion uint8, signer []byte) (*Blob, error) {
if shareVersion == 0 && signer != nil {
return nil, errors.New("share version 0 does not support signer")
}
if shareVersion == 1 && len(signer) != SignerSize {
return nil, errors.New("share version 1 requires signer of size 20 bytes")
}
return &Blob{
namespace: ns,
data: data,
shareVersion: shareVersion,
signer: signer,
}, nil
}

func NewV0(ns ns.Namespace, data []byte) *Blob {
blob, err := New(ns, data, 0, nil)
if err != nil {
panic(err)
}
return blob
}

func NewV1(ns ns.Namespace, data []byte, signer []byte) (*Blob, error) {
return New(ns, data, 1, signer)
}

// NewFromProto creates a Blob from the proto format and performs
Expand All @@ -62,12 +83,12 @@ func NewFromProto(pb *BlobProto) (*Blob, error) {
if err != nil {
return nil, fmt.Errorf("invalid namespace: %w", err)
}
return &Blob{
namespace: ns,
data: pb.Data,
shareVersion: uint8(pb.ShareVersion),
signer: pb.Signer,
}, nil
return New(
ns,
pb.Data,
uint8(pb.ShareVersion),
pb.Signer,
)
}

// Namespace returns the namespace of the blob
Expand Down
12 changes: 11 additions & 1 deletion inclusion/commitment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ func TestCreateCommitment(t *testing.T) {
expected []byte
expectErr bool
shareVersion uint8
signer []byte
}
tests := []test{
{
Expand All @@ -81,6 +82,14 @@ func TestCreateCommitment(t *testing.T) {
expected: []byte{0x31, 0xf5, 0x15, 0x6d, 0x5d, 0xb9, 0xa7, 0xf5, 0xb4, 0x3b, 0x29, 0x7a, 0x14, 0xc0, 0x70, 0xc2, 0xcc, 0x4e, 0xf3, 0xd6, 0x9d, 0x87, 0xed, 0x8, 0xad, 0xdd, 0x21, 0x6d, 0x9b, 0x9f, 0xa1, 0x18},
shareVersion: shares.ShareVersionZero,
},
{
name: "blob of one share with signer succeeds",
namespace: ns1,
blob: bytes.Repeat([]byte{0xFF}, shares.AvailableBytesFromSparseShares(2)-blob.SignerSize),
expected: []byte{0x88, 0x3c, 0x74, 0x6, 0x4e, 0x8e, 0x26, 0x27, 0xad, 0x58, 0x8, 0x38, 0x9f, 0x1f, 0x19, 0x24, 0x19, 0x4c, 0x1a, 0xe2, 0x3c, 0x7d, 0xf9, 0x62, 0xc8, 0xd5, 0x6d, 0xf0, 0x62, 0xa9, 0x2b, 0x2b},
shareVersion: shares.ShareVersionOne,
signer: bytes.Repeat([]byte{1}, blob.SignerSize),
},
{
name: "blob with unsupported share version should return error",
namespace: ns1,
Expand All @@ -91,7 +100,8 @@ func TestCreateCommitment(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
blob := blob.New(tt.namespace, tt.blob, tt.shareVersion, nil)
blob, err := blob.New(tt.namespace, tt.blob, tt.shareVersion, tt.signer)
require.NoError(t, err)
res, err := inclusion.CreateCommitment(blob, twoLeafMerkleRoot, defaultSubtreeRootThreshold)
if tt.expectErr {
assert.Error(t, err)
Expand Down
14 changes: 11 additions & 3 deletions internal/test/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,21 @@ func RandomBytes(size int) []byte {
return b
}

func GenerateBlobTxWithNamespace(namespaces []namespace.Namespace, blobSizes []int) []byte {
func GenerateBlobTxWithNamespace(namespaces []namespace.Namespace, blobSizes []int, version uint8) []byte {
blobs := make([]*blob.Blob, len(blobSizes))
if len(namespaces) != len(blobSizes) {
panic("number of namespaces should match number of blob sizes")
}
var err error
var signer []byte
if version == shares.ShareVersionOne {
signer = RandomBytes(blob.SignerSize)
}
for i, size := range blobSizes {
blobs[i] = blob.New(namespaces[i], RandomBytes(size), shares.DefaultShareVersion, nil)
blobs[i], err = blob.New(namespaces[i], RandomBytes(size), version, signer)
if err != nil {
panic(err)
}
}
blobTx, err := blob.MarshalBlobTx(MockPFB(toUint32(blobSizes)), blobs...)
if err != nil {
Expand All @@ -54,7 +62,7 @@ func GenerateBlobTxWithNamespace(namespaces []namespace.Namespace, blobSizes []i
}

func GenerateBlobTx(blobSizes []int) []byte {
return GenerateBlobTxWithNamespace(Repeat(DefaultTestNamespace, len(blobSizes)), blobSizes)
return GenerateBlobTxWithNamespace(Repeat(DefaultTestNamespace, len(blobSizes)), blobSizes, shares.DefaultShareVersion)
}

func GenerateBlobTxs(numTxs, blobsPerPfb, blobSize int) [][]byte {
Expand Down
8 changes: 1 addition & 7 deletions shares/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,7 @@ func ParseShares(shares []Share, ignorePadding bool) ([]ShareSequence, error) {
currentSequence := ShareSequence{}

for _, share := range shares {
if err := share.Validate(); err != nil {
return sequences, err
}
isStart, err := share.IsSequenceStart()
if err != nil {
return sequences, err
}
isStart := share.IsSequenceStart()
ns, err := share.Namespace()
if err != nil {
return sequences, err
Expand Down
25 changes: 10 additions & 15 deletions shares/parse_sparse_shares.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type sequence struct {
shareVersion uint8
data []byte
sequenceLen uint32
signer []byte
}

// parseSparseShares iterates through rawShares and parses out individual
Expand All @@ -25,10 +26,7 @@ func parseSparseShares(shares []Share, supportedShareVersions []uint8) (blobs []
sequences := make([]sequence, 0)

for _, share := range shares {
version, err := share.Version()
if err != nil {
return nil, err
}
version := share.Version()
if !bytes.Contains(supportedShareVersions, []byte{version}) {
return nil, fmt.Errorf("unsupported share version %v is not present in supported share versions %v", version, supportedShareVersions)
}
Expand All @@ -41,16 +39,8 @@ func parseSparseShares(shares []Share, supportedShareVersions []uint8) (blobs []
continue
}

isStart, err := share.IsSequenceStart()
if err != nil {
return nil, err
}

if isStart {
sequenceLen, err := share.SequenceLen()
if err != nil {
return nil, err
}
if share.IsSequenceStart() {
sequenceLen := share.SequenceLen()
data, err := share.RawData()
if err != nil {
return nil, err
Expand All @@ -64,6 +54,7 @@ func parseSparseShares(shares []Share, supportedShareVersions []uint8) (blobs []
shareVersion: version,
data: data,
sequenceLen: sequenceLen,
signer: GetSigner(share),
})
} else { // continuation share
if len(sequences) == 0 {
Expand All @@ -81,7 +72,11 @@ func parseSparseShares(shares []Share, supportedShareVersions []uint8) (blobs []
for _, sequence := range sequences {
// trim any padding from the end of the sequence
sequence.data = sequence.data[:sequence.sequenceLen]
blobs = append(blobs, blob.New(sequence.ns, sequence.data, sequence.shareVersion, nil))
blob, err := blob.New(sequence.ns, sequence.data, sequence.shareVersion, sequence.signer)
if err != nil {
return nil, err
}
blobs = append(blobs, blob)
}

return blobs, nil
Expand Down
14 changes: 13 additions & 1 deletion shares/parse_sparse_shares_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,13 +155,25 @@ func Test_parseSparseSharesWithNamespacedPadding(t *testing.T) {
require.Equal(t, blobs, pblobs)
}

func Test_parseShareVersionOne(t *testing.T) {
v1blob, err := blob.NewV1(ns.MustNewV0(bytes.Repeat([]byte{1}, ns.NamespaceVersionZeroIDSize)), []byte("data"), bytes.Repeat([]byte{1}, blob.SignerSize))
require.NoError(t, err)
v1shares, err := SplitBlobs(v1blob)
require.NoError(t, err)

parsedBlobs, err := parseSparseShares(v1shares, SupportedShareVersions)
require.NoError(t, err)
require.Equal(t, v1blob, parsedBlobs[0])
require.Len(t, parsedBlobs, 1)
}

func generateRandomBlobWithNamespace(namespace ns.Namespace, size int) *blob.Blob {
data := make([]byte, size)
_, err := crand.Read(data)
if err != nil {
panic(err)
}
return blob.New(namespace, data, ShareVersionZero, nil)
return blob.NewV0(namespace, data)
}

func generateRandomBlob(dataSize int) *blob.Blob {
Expand Down
10 changes: 0 additions & 10 deletions shares/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ func TestParseShares(t *testing.T) {
blobTwoStart := blobTwoShares[0]
blobTwoContinuation := blobTwoShares[1]

// invalidShare is longer than the length of a valid share
invalidShare := Share{data: append(generateRawShare(t, ns1, true, 1), []byte{0}...)}

// tooLargeSequenceLen is a single share with too large of a sequence len
// because it takes more than one share to store a sequence of 1000 bytes
tooLargeSequenceLen := generateRawShare(t, ns1, true, uint32(1000))
Expand Down Expand Up @@ -116,13 +113,6 @@ func TestParseShares(t *testing.T) {
},
expectErr: false,
},
{
name: "one share with invalid size",
shares: []Share{invalidShare},
ignorePadding: false,
want: []ShareSequence{},
expectErr: true,
},
{
name: "blob one start followed by blob two continuation",
shares: []Share{blobOneStart, blobTwoContinuation},
Expand Down
11 changes: 11 additions & 0 deletions shares/share_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,17 @@ func (b *Builder) WriteSequenceLen(sequenceLen uint32) error {
return nil
}

// WriteSigner writes the signer's information to the share.
func (b *Builder) WriteSigner(signer []byte) {
// only write the signer if it is the first share and the share version is 1
if b == nil || !b.isFirstShare || b.shareVersion != ShareVersionOne {
return
}
// NOTE: we don't check whether previous data has already been expected
// like the sequence length (we just assume it has)
b.rawShareData = append(b.rawShareData, signer...)
}

// FlipSequenceStart flips the sequence start indicator of the share provided
func (b *Builder) FlipSequenceStart() {
infoByteIndex := b.indexOfInfoBytes()
Expand Down
4 changes: 1 addition & 3 deletions shares/share_builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,7 @@ func TestShareBuilderWriteSequenceLen(t *testing.T) {
share, err := tc.builder.Build()
require.NoError(t, err)

length, err := share.SequenceLen()
require.NoError(t, err)

length := share.SequenceLen()
assert.Equal(t, tc.wantLen, length)
})
}
Expand Down
7 changes: 2 additions & 5 deletions shares/share_sequence.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func (s ShareSequence) SequenceLen() (uint32, error) {
return 0, fmt.Errorf("invalid sequence length because share sequence %v has no shares", s)
}
firstShare := s.Shares[0]
return firstShare.SequenceLen()
return firstShare.SequenceLen(), nil
}

// validSequenceLen extracts the sequenceLen written to the first share
Expand Down Expand Up @@ -84,10 +84,7 @@ func (s ShareSequence) isPadding() (bool, error) {
// firstShare and returns the number of shares needed to store a sequence of
// that length.
func numberOfSharesNeeded(firstShare Share) (sharesUsed int, err error) {
sequenceLen, err := firstShare.SequenceLen()
if err != nil {
return 0, err
}
sequenceLen := firstShare.SequenceLen()

isCompact, err := firstShare.IsCompactShare()
if err != nil {
Expand Down
Loading

0 comments on commit a5ae4fe

Please sign in to comment.