rsmt2d: 2D Reed-Solomon Merkle Tree

repository·main·Indexed 41 days ago

https://github.com/celestiaorg/rsmt2d

A Go implementation of a two-dimensional Reed-Solomon Merkle tree data availability scheme. It provides tools for computing parity shares for data squares, extracting row and column roots for verification, and repairing missing data shares. The library includes the LeoRSCodec, a Go port of the Leopard library, and supports the ExtendedDataSquare type for managing erasure coding and Byzantine data detection via ErrByzantineData.

Tokens
4.9K
Snippets
21
Records
25
Agent score
51%

What's inside rsmt2d

  1. Build, test, and lint rsmt2d

    main

    Use the provided Makefile to manage the development lifecycle of the project. Ensure you have Go 1.24+ and golangci-lint installed for full compatibility.

    Available commands:

    • make build: Compiles the project.
    • make test: Runs the unit test suite.
    • make bench: Runs performance benchmarks.
    • make lint: Runs the linter.
    # Build the project
    make build
    
    # Run unit tests
    make test
    
    # Run benchmarks
    make bench
    
    # Run linter
    make lint
  2. Use rsmt2d for 2D Reed-Solomon Merkle Tree data availability

    main

    rsmt2d is a Go implementation of a two-dimensional Reed-Solomon Merkle tree data availability scheme. It allows for computing parity shares for a data square, extracting row and column roots for verification, and repairing missing data shares using those roots.

    To use the library, you typically follow this lifecycle:

    1. Initialize a codec using rsmt2d.NewLeoRSCodec().
    2. Compute an Extended Data Square using rsmt2d.ComputeExtendedDataSquare from a 2D slice of bytes.
    3. Extract RowRoots() and ColRoots() for use in verification or repair.
    4. If data is lost, use rsmt2d.ImportExtendedDataSquare to re-import the (potentially incomplete) flattened data.
    5. Call Repair(rowRoots, colRoots) to reconstruct the missing shares.
    package main
    
    import (
        "bytes"
    
        "github.com/celestiaorg/rsmt2d"
    )
    
    func main() {
        // shareSize is the size of each share (in bytes).
        shareSize := 512
        // Init new codec
        codec := rsmt2d.NewLeoRSCodec()
    
        ones := bytes.Repeat([]byte{1}, shareSize)
        twos := bytes.Repeat([]byte{2}, shareSize)
        threes := bytes.Repeat([]byte{3}, shareSize)
        fours := bytes.Repeat([]byte{4}, shareSize)
    
        // Compute parity shares
        eds, err := rsmt2d.ComputeExtendedDataSquare(
            [][]byte{
                ones, twos,
                threes, fours,
            },
            codec,
            rsmt2d.NewDefaultTree,
        )
        if err != nil {
            // ComputeExtendedDataSquare failed
        }
    
        rowRoots, err := eds.RowRoots()
        if err != nil {
        // RowRoots failed
        }
        colRoots, err := eds.ColRoots()
        if err != nil {
        // ColRoots failed
        }
    
        flattened := eds.Flattened()
    
        // Delete some shares, just enough so that repairing is possible.
        flattened[0], flattened[2], flattened[3] = nil, nil, nil
        flattened[4], flattened[5], flattened[6], flattened[7] = nil, nil, nil, nil
        flattened[8], flattened[9], flattened[10] = nil, nil, nil
        flattened[12], flattened[13] = nil, nil
    
        // Re-import the data square.
        eds, err = rsmt2d.ImportExtendedDataSquare(flattened, codec, rsmt2d.NewDefaultTree)
        if err != nil {
            // ImportExtendedDataSquare failed
        }
    
        // Repair square.
        err = eds.Repair(
            rowRoots,
            colRoots,
        )
        if err != nil {
            // err contains information to construct a fraud proof
        }
    }
  3. Use ExtendedDataSquare for Reed-Solomon erasure coding

    main

    The ExtendedDataSquare type is the primary structure for managing the two-dimensional Reed-Solomon Merkle Tree (RSMT2D) data availability scheme. It represents an extended grid of data where an original data square is expanded with parity (erasure) shares to allow for data recovery.

    Key operations include:

    • Computing an extended square from existing data shares.
    • Importing an existing square from flattened data.
    • Marshaling/Unmarshaling to/from JSON for storage or transmission.
    • Extracting Merkle roots for both rows and columns to verify data integrity.
  4. Marshal and Unmarshal ExtendedDataSquare as JSON

    main

    The ExtendedDataSquare implements json.Marshaler and json.Unmarshaler.

    When marshaling, it produces a JSON object with the following structure:

    • data_square: A 2D array of byte slices ([][]byte) representing the flattened square.
    • codec: The name of the codec used.

    When unmarshaling, it uses the codec name from the JSON to look up the appropriate codec and reconstructs the square using ImportExtendedDataSquare.

    // Marshaling
    jsonData, err := eds.MarshalJSON()
    
    // Unmarshaling
    err := json.Unmarshal(jsonData, &eds)
  5. Compute an ExtendedDataSquare from data shares

    main

    Use ComputeExtendedDataSquare to take a set of data shares and generate a full ExtendedDataSquare including erasure parity. This function validates the chunk size against the provided Codec and uses the TreeConstructorFn to build the underlying Merkle trees.

    If you need to limit parallel operations and use buffered NMTs to avoid allocations during root computation, use ComputeExtendedDataSquareWithBuffer instead, passing a BufferedTreeConstructor.

    // Standard computation
    eds, err := rsmt2d.ComputeExtendedDataSquare(data, codec, treeCreatorFn)
    
    // Computation with buffer to limit parallel ops and reduce allocations
    eds, err := rsmt2d.ComputeExtendedDataSquareWithBuffer(data, codec, bufferedTreeConstructor)
  6. Import an existing ExtendedDataSquare

    main

    If you have a flattened slice of bytes representing an extended data square, use ImportExtendedDataSquare to reconstruct the ExtendedDataSquare object. This requires the Codec used to encode the data and a TreeConstructorFn to rebuild the Merkle trees.

    eds, err := rsmt2d.ImportExtendedDataSquare(flattenedData, codec, treeCreatorFn)
  7. Use BufferedTreeConstructor to manage multiple trees

    main
    The BufferedTreeConstructor interface is used to manage the lifecycle and creation of multiple Tree instances. It allows for the creation of tree constructors based on a specific squareSize and provides tracking of the total number of trees created.
  8. Use LeoRSCodec for Reed-Solomon encoding and decoding

    main

    The LeoRSCodec implements the Codec interface using the Leopard Galois Field. It is designed for 2D data squares where the number of parity shares equals the number of data shares (a 2:1 ratio).

    Encoding

    Encode(data [][]byte) takes a slice of data chunks and returns the generated parity shares. The input data must be a 2D square of chunks.

    Decoding

    Decode(data [][]byte) reconstructs missing shares. Important: The method performs in-place reconstruction. It fills nil entries in the provided data slice. If you need to preserve the original slice (with its nil values), you must copy the slice before calling Decode.

    Constraints

    • Chunk Size: The shareSize must be a multiple of 64 bytes. Use ValidateChunkSize(shareSize int) to check compatibility.
    • Max Chunks: The maximum number of shares supported in a 2D original data square is $32768 imes 32768$.
    codec := rsmt2d.NewLeoRSCodec()
    
    // Encoding
    data := [][]byte{...}
    parity, err := codec.Encode(data)
    
    // Decoding (In-place)
    // data must contain nil for missing shares
    err := codec.Decode(data)
  9. Get flattened data representations

    main

    The ExtendedDataSquare provides two ways to get flattened data:

    1. Flattened(): Returns a deep copy of the entire extended data square as a 2D slice ([][]byte).
    2. FlattenedODS(): Returns the Original Data Square (the data before erasure extension) as a flattened 2D slice. The size of this slice will be originalDataWidth * originalDataWidth.
    // Entire extended square
    allData := eds.Flattened()
    
    // Only the original data (pre-erasure)
    originalData := eds.FlattenedODS()
  10. Use the Tree interface to manage Merkle trees

    main

    The Tree interface is the primary abstraction for interacting with Merkle tree implementations within rsmt2d. It provides methods to ingest data and retrieve the computed Merkle root.

    Implementations of Tree are expected to handle the underlying cryptographic hashing and tree structure logic.

    // Example of using a Tree implementation
    var tree rsmt2d.Tree
    
    // Add data to the tree
    err := tree.Push([]byte("some data"))
    if err != nil {
    	// handle error
    }
    
    // Retrieve the Merkle root
    root, err := tree.Root()
    if err != nil {
    	// handle error
    }
  11. Use the Codec interface for Reed-Solomon encoding and decoding

    main

    The Codec interface defines the required behavior for 2D Reed-Solomon implementations in rsmt2d. It handles the transformation between original data and parity shares.

    Key methods:

    • Encode(data [][]byte) ([][]byte, error): Encodes original data into parity shares. It automatically extracts share size from the input. Note that there must be no missing shares for encoding, and it only returns the parity shares.
    • Decode(data [][]byte) ([][]byte, error): Decodes sparse data (original data + parity shares). Missing shares must be provided as nil. It returns the full set of original and parity data.
    • MaxChunks() int: Returns the maximum number of chunks (shares) supported by the codec in a 2D original data square.
    • ValidateChunkSize(chunkSize int) error: Validates if the codec supports a specific chunkSize. Returns nil if supported, or an error otherwise.
    • Name() string: Returns the identifier for the codec.
    // Example usage pattern for a Codec implementation
    var codec rsmt2d.Codec
    
    // Encoding original data to get parity shares
    parity, err := codec.Encode(originalData)
    if err != nil {
        // handle error
    }
    
    // Decoding data where some shares might be missing (nil)
    // data contains original + parity, with missing shares as nil
    fullData, err := codec.Decode(data)
    if err != nil {
        // handle error
    }
  12. Initialize a new empty ExtendedDataSquare

    main

    Use NewExtendedDataSquare to create a pre-allocated, empty ExtendedDataSquare. All cells are initialized to nil, allowing you to populate the square manually using subsequent SetCell calls (inherited from the underlying dataSquare). You must specify the desired edsWidth (which must be even) and the shareSize.

    // edsWidth must be even
    eds, err := rsmt2d.NewExtendedDataSquare(codec, treeCreatorFn, 16, 32)