diff --git a/blob/blob.go b/blob/blob.go index eebab439a9..fbb1196b27 100644 --- a/blob/blob.go +++ b/blob/blob.go @@ -7,8 +7,6 @@ import ( "fmt" "github.com/tendermint/tendermint/crypto/merkle" - "github.com/tendermint/tendermint/pkg/consts" - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" coretypes "github.com/tendermint/tendermint/types" "github.com/celestiaorg/celestia-app/v2/pkg/appconsts" @@ -28,78 +26,150 @@ var errEmptyShares = errors.New("empty shares") // Proof constructs the proof of a blob to the data root. type Proof struct { - // ShareToRowRootProof the proofs of the shares to the row roots they belong to. + // SubtreeRoots are the subtree roots of the blob's data that are + // used to create the commitment. + SubtreeRoots [][]byte `json:"subtree_roots"` + // SubtreeRootProofs the proofs of the subtree roots to the row roots they belong to. // If the blob spans across multiple rows, then this will contain multiple proofs. - ShareToRowRootProof []*tmproto.NMTProof + SubtreeRootProofs []*nmt.Proof `json:"share_to_row_root_proofs"` // RowToDataRootProof the proofs of the row roots containing the blob shares // to the data root. - RowToDataRootProof coretypes.RowProof + RowToDataRootProof coretypes.RowProof `json:"row_to_data_root_proof"` } // namespaceToRowRootProof a proof of a set of namespace shares to the row // roots they belong to. type namespaceToRowRootProof []*nmt.Proof -// Verify takes a blob and a data root and verifies if the -// provided blob was committed to the given data root. -func (p *Proof) Verify(blob *Blob, dataRoot []byte) (bool, error) { - blobCommitment, err := inclusion.CreateCommitment( - ToAppBlobs(blob)[0], - merkle.HashFromByteSlices, - appconsts.DefaultSubtreeRootThreshold, - ) - if err != nil { - return false, err +// Commitment is a Merkle Root of the subtree built from shares of the Blob. +// It is computed by splitting the blob into shares and building the Merkle subtree to be included +// after Submit. +type Commitment []byte + +// Verify takes a data root and verifies if the +// provided proof's subtree roots were committed to the given data root. +func (p *Proof) Verify(dataRoot []byte, subtreeRootThreshold int) (bool, error) { + if len(dataRoot) == 0 { + return false, errors.New("root must be non-empty") + } + + if subtreeRootThreshold <= 0 { + return false, errors.New("subtreeRootThreshold must be > 0") } - if !blob.Commitment.Equal(blobCommitment) { + + // this check is < instead of != because we can have two subtree roots + // at the same height, depending on the subtree root threshold, + // and they can be used to create the above inner node without needing a proof inner node. + if len(p.SubtreeRoots) < len(p.SubtreeRootProofs) { return false, fmt.Errorf( - "%w: generated commitment does not match the provided blob commitment", - ErrMismatchCommitment, + "the number of subtree roots %d should be bigger than the number of subtree root proofs %d", + len(p.SubtreeRoots), + len(p.SubtreeRootProofs), ) } - rawShares, err := BlobsToShares(blob) - if err != nil { - return false, err + + // for each row, one or more subtree roots' inclusion is verified against + // their corresponding row root. then, these row roots' inclusion is verified + // against the data root. so their number should be the same. + if len(p.SubtreeRootProofs) != len(p.RowToDataRootProof.Proofs) { + return false, fmt.Errorf( + "the number of subtree root proofs %d should be equal to the number of row root proofs %d", + len(p.SubtreeRootProofs), + len(p.RowToDataRootProof.Proofs), + ) + } + + // the row root proofs' ranges are defined as [startRow, endRow]. + if int(p.RowToDataRootProof.EndRow-p.RowToDataRootProof.StartRow+1) != len(p.RowToDataRootProof.RowRoots) { + return false, fmt.Errorf( + "the number of rows %d must equal the number of row roots %d", + int(p.RowToDataRootProof.EndRow-p.RowToDataRootProof.StartRow+1), + len(p.RowToDataRootProof.RowRoots), + ) + } + if len(p.RowToDataRootProof.Proofs) != len(p.RowToDataRootProof.RowRoots) { + return false, fmt.Errorf( + "the number of proofs %d must equal the number of row roots %d", + len(p.RowToDataRootProof.Proofs), + len(p.RowToDataRootProof.RowRoots), + ) } - return p.VerifyShares(rawShares, blob.namespace, dataRoot) -} -// VerifyShares takes a set of shares, a namespace and a data root, and verifies if the -// provided shares are committed to by the data root. -func (p *Proof) VerifyShares(rawShares [][]byte, namespace share.Namespace, dataRoot []byte) (bool, error) { - // verify the row proof + // verify the inclusion of the rows to the data root if err := p.RowToDataRootProof.Validate(dataRoot); err != nil { - return false, fmt.Errorf("%w: invalid row root to data root proof", err) + return false, err + } + + // computes the total number of shares proven given that each subtree root + // references a specific set of leaves. + numberOfShares := 0 + for _, proof := range p.SubtreeRootProofs { + numberOfShares += proof.End() - proof.Start() } - // verify the share proof - ns := append([]byte{namespace.Version()}, namespace.ID()...) - cursor := int32(0) - for i, proof := range p.ShareToRowRootProof { - sharesUsed := proof.End - proof.Start - if len(rawShares) < int(sharesUsed+cursor) { - return false, fmt.Errorf("%w: invalid number of shares", ErrInvalidProof) + // use the computed total number of shares to calculate the subtree roots + // width. + // the subtree roots width is defined in ADR-013: + // + //https://github.com/celestiaorg/celestia-app/blob/main/docs/architecture/adr-013-non-interactive-default-rules-for-zero-padding.md + subtreeRootsWidth := inclusion.SubTreeWidth(numberOfShares, subtreeRootThreshold) + + nmtHasher := nmt.NewNmtHasher(appconsts.NewBaseHashFunc(), share.NamespaceSize, true) + // verify the proof of the subtree roots + subtreeRootsCursor := 0 + for i, subtreeRootProof := range p.SubtreeRootProofs { + // calculate the share range that each subtree root commits to. + ranges, err := nmt.ToLeafRanges(subtreeRootProof.Start(), subtreeRootProof.End(), subtreeRootsWidth) + if err != nil { + return false, err } - nmtProof := nmt.NewInclusionProof( - int(proof.Start), - int(proof.End), - proof.Nodes, - true, - ) - valid := nmtProof.VerifyInclusion( - consts.NewBaseHashFunc(), - ns, - rawShares[cursor:sharesUsed+cursor], + + if len(p.SubtreeRoots) < subtreeRootsCursor { + return false, fmt.Errorf("len(commitmentProof.SubtreeRoots)=%d < subtreeRootsCursor=%d", + len(p.SubtreeRoots), subtreeRootsCursor) + } + if len(p.SubtreeRoots) < subtreeRootsCursor+len(ranges) { + return false, fmt.Errorf("len(commitmentProof.SubtreeRoots)=%d < subtreeRootsCursor+len(ranges)=%d", + len(p.SubtreeRoots), subtreeRootsCursor+len(ranges)) + } + valid, err := subtreeRootProof.VerifySubtreeRootInclusion( + nmtHasher, + p.SubtreeRoots[subtreeRootsCursor:subtreeRootsCursor+len(ranges)], + subtreeRootsWidth, p.RowToDataRootProof.RowRoots[i], ) + if err != nil { + return false, err + } if !valid { - return false, ErrInvalidProof + return false, + fmt.Errorf( + "subtree root proof for range [%d, %d) is invalid", + subtreeRootProof.Start(), + subtreeRootProof.End(), + ) } - cursor += sharesUsed + subtreeRootsCursor += len(ranges) } + return true, nil } +// GenerateCommitment generates the share commitment corresponding +// to the proof's subtree roots +func (p *Proof) GenerateCommitment() []byte { + return merkle.HashFromByteSlices(p.SubtreeRoots) +} + +func (com Commitment) String() string { + return string(com) +} + +// Equal ensures that commitments are the same +func (com Commitment) Equal(c Commitment) bool { + return bytes.Equal(com, c) +} + // Blob represents any application-specific binary data that anyone can submit to Celestia. type Blob struct { *blob.Blob `json:"blob"` @@ -224,6 +294,16 @@ func (b *Blob) UnmarshalJSON(data []byte) error { return nil } +func (b *Blob) ComputeSubtreeRoots() ([][]byte, error) { + gsBlob := blob.Blob{ + NamespaceId: b.NamespaceId, + Data: b.Data, + ShareVersion: b.ShareVersion, + NamespaceVersion: b.NamespaceVersion, + } + return inclusion.GenerateSubtreeRoots(&gsBlob, appconsts.SubtreeRootThreshold(appVersion)) +} + // proveRowRootsToDataRoot creates a set of binary merkle proofs for all the // roots defined by the range [start, end). func proveRowRootsToDataRoot(roots [][]byte, start, end int) []*merkle.Proof { diff --git a/blob/blob_fuzz_test.go b/blob/blob_fuzz_test.go index 77be61a946..6eae3a8d88 100644 --- a/blob/blob_fuzz_test.go +++ b/blob/blob_fuzz_test.go @@ -41,9 +41,9 @@ func FuzzProofEqual(f *testing.F) { } type verifyCorpus struct { - CP *CommitmentProof `json:"commitment_proof"` - Root []byte `json:"root"` - SThreshold int `json:"sub_threshold"` + Proof *Proof `json:"proof"` + Root []byte `json:"root"` + SThreshold int `json:"sub_threshold"` } func FuzzCommitmentProofVerify(f *testing.F) { @@ -83,7 +83,7 @@ func FuzzCommitmentProofVerify(f *testing.F) { if err := json.Unmarshal(valueJSON, val); err != nil { return } - commitProof := val.CP + commitProof := val.Proof if commitProof == nil { return } diff --git a/blob/commitment_proof.go b/blob/commitment_proof.go deleted file mode 100644 index 82fed368dc..0000000000 --- a/blob/commitment_proof.go +++ /dev/null @@ -1,156 +0,0 @@ -package blob - -import ( - "bytes" - "errors" - "fmt" - - "github.com/celestiaorg/celestia-app/v2/pkg/appconsts" - "github.com/celestiaorg/celestia-app/v2/pkg/proof" - "github.com/celestiaorg/go-square/inclusion" - "github.com/celestiaorg/nmt" - "github.com/celestiaorg/nmt/namespace" - - "github.com/celestiaorg/celestia-node/share" -) - -// Commitment is a Merkle Root of the subtree built from shares of the Blob. -// It is computed by splitting the blob into shares and building the Merkle subtree to be included -// after Submit. -type Commitment []byte - -// CommitmentProof is an inclusion proof of a commitment to the data root. -type CommitmentProof struct { - // SubtreeRoots are the subtree roots of the blob's data that are - // used to create the commitment. - SubtreeRoots [][]byte `json:"subtree_roots"` - // SubtreeRootProofs are the NMT proofs for the subtree roots - // to the row roots. - SubtreeRootProofs []*nmt.Proof `json:"subtree_root_proofs"` - // NamespaceID is the namespace id of the commitment being proven. This - // namespace id is used when verifying the proof. If the namespace id doesn't - // match the namespace of the shares, the proof will fail verification. - NamespaceID namespace.ID `json:"namespace_id"` - // RowProof is the proof of the rows containing the blob's data to the - // data root. - RowProof proof.RowProof `json:"row_proof"` - NamespaceVersion uint8 `json:"namespace_version"` -} - -func (com Commitment) String() string { - return string(com) -} - -// Equal ensures that commitments are the same -func (com Commitment) Equal(c Commitment) bool { - return bytes.Equal(com, c) -} - -// Validate performs basic validation to the commitment proof. -// Note: it doesn't verify if the proof is valid or not. -// Check Verify() for that. -func (commitmentProof *CommitmentProof) Validate() error { - if len(commitmentProof.SubtreeRoots) < len(commitmentProof.SubtreeRootProofs) { - return fmt.Errorf( - "the number of subtree roots %d should be bigger than the number of subtree root proofs %d", - len(commitmentProof.SubtreeRoots), - len(commitmentProof.SubtreeRootProofs), - ) - } - if len(commitmentProof.SubtreeRootProofs) != len(commitmentProof.RowProof.Proofs) { - return fmt.Errorf( - "the number of subtree root proofs %d should be equal to the number of row root proofs %d", - len(commitmentProof.SubtreeRootProofs), - len(commitmentProof.RowProof.Proofs), - ) - } - if int(commitmentProof.RowProof.EndRow-commitmentProof.RowProof.StartRow+1) != len(commitmentProof.RowProof.RowRoots) { - return fmt.Errorf( - "the number of rows %d must equal the number of row roots %d", - int(commitmentProof.RowProof.EndRow-commitmentProof.RowProof.StartRow+1), - len(commitmentProof.RowProof.RowRoots), - ) - } - if len(commitmentProof.RowProof.Proofs) != len(commitmentProof.RowProof.RowRoots) { - return fmt.Errorf( - "the number of proofs %d must equal the number of row roots %d", - len(commitmentProof.RowProof.Proofs), - len(commitmentProof.RowProof.RowRoots), - ) - } - return nil -} - -// Verify verifies that a commitment proof is valid, i.e., the subtree roots commit -// to some data that was posted to a square. -// Expects the commitment proof to be properly formulated and validated -// using the Validate() function. -func (commitmentProof *CommitmentProof) Verify(root []byte, subtreeRootThreshold int) (bool, error) { - if len(root) == 0 { - return false, errors.New("root must be non-empty") - } - - rp := commitmentProof.RowProof - if err := rp.Validate(root); err != nil { - return false, err - } - - nmtHasher := nmt.NewNmtHasher(appconsts.NewBaseHashFunc(), share.NamespaceSize, true) - - // computes the total number of shares proven. - numberOfShares := 0 - for _, proof := range commitmentProof.SubtreeRootProofs { - numberOfShares += proof.End() - proof.Start() - } - - if subtreeRootThreshold <= 0 { - return false, errors.New("subtreeRootThreshould must be > 0") - } - - // use the computed total number of shares to calculate the subtree roots - // width. - // the subtree roots width is defined in ADR-013: - // - //https://github.com/celestiaorg/celestia-app/blob/main/docs/architecture/adr-013-non-interactive-default-rules-for-zero-padding.md - subtreeRootsWidth := inclusion.SubTreeWidth(numberOfShares, subtreeRootThreshold) - - // verify the proof of the subtree roots - subtreeRootsCursor := 0 - for i, subtreeRootProof := range commitmentProof.SubtreeRootProofs { - // calculate the share range that each subtree root commits to. - ranges, err := nmt.ToLeafRanges(subtreeRootProof.Start(), subtreeRootProof.End(), subtreeRootsWidth) - if err != nil { - return false, err - } - - if len(commitmentProof.SubtreeRoots) < subtreeRootsCursor { - return false, fmt.Errorf("len(commitmentProof.SubtreeRoots)=%d < subtreeRootsCursor=%d", - len(commitmentProof.SubtreeRoots), subtreeRootsCursor) - } - if len(commitmentProof.SubtreeRoots) < subtreeRootsCursor+len(ranges) { - return false, fmt.Errorf("len(commitmentProof.SubtreeRoots)=%d < subtreeRootsCursor+len(ranges)=%d", - len(commitmentProof.SubtreeRoots), subtreeRootsCursor+len(ranges)) - } - valid, err := subtreeRootProof.VerifySubtreeRootInclusion( - nmtHasher, - commitmentProof.SubtreeRoots[subtreeRootsCursor:subtreeRootsCursor+len(ranges)], - subtreeRootsWidth, - commitmentProof.RowProof.RowRoots[i], - ) - if err != nil { - return false, err - } - if !valid { - return false, - fmt.Errorf( - "subtree root proof for range [%d, %d) is invalid", - subtreeRootProof.Start(), - subtreeRootProof.End(), - ) - } - subtreeRootsCursor += len(ranges) - } - - // verify row roots to data root proof - return commitmentProof.RowProof.VerifyProof(root), nil -} diff --git a/blob/repro_test.go b/blob/repro_test.go index 27070d1f28..e00e7240d9 100644 --- a/blob/repro_test.go +++ b/blob/repro_test.go @@ -3,16 +3,18 @@ package blob import ( "testing" - "github.com/celestiaorg/celestia-app/v2/pkg/proof" + "github.com/tendermint/tendermint/crypto/merkle" + coretypes "github.com/tendermint/tendermint/types" + "github.com/celestiaorg/nmt" "github.com/celestiaorg/nmt/pb" ) // Reported at https://github.com/celestiaorg/celestia-node/issues/3731. -func TestCommitmentProofRowProofVerifyWithEmptyRoot(t *testing.T) { - cp := &CommitmentProof{ - RowProof: proof.RowProof{ - Proofs: []*proof.Proof{{}}, +func TestProofRowProofVerifyWithEmptyRoot(t *testing.T) { + cp := &Proof{ + RowToDataRootProof: coretypes.RowProof{ + Proofs: []*merkle.Proof{{}}, }, } root := []byte{0xd3, 0x4d, 0x34} @@ -22,10 +24,10 @@ func TestCommitmentProofRowProofVerifyWithEmptyRoot(t *testing.T) { } // Reported at https://github.com/celestiaorg/celestia-node/issues/3730. -func TestCommitmentProofRowProofVerify(t *testing.T) { - cp := &CommitmentProof{ - RowProof: proof.RowProof{ - Proofs: []*proof.Proof{{}}, +func TestProofRowProofVerify(t *testing.T) { + cp := &Proof{ + RowToDataRootProof: coretypes.RowProof{ + Proofs: []*merkle.Proof{{}}, }, } if _, err := cp.Verify(nil, 1); err == nil { @@ -36,7 +38,7 @@ func TestCommitmentProofRowProofVerify(t *testing.T) { // Reported at https://github.com/celestiaorg/celestia-node/issues/3729. func TestCommitmentProofVerifySliceBound(t *testing.T) { proof := nmt.ProtoToProof(pb.Proof{End: 1}) - cp := &CommitmentProof{ + cp := &Proof{ SubtreeRootProofs: []*nmt.Proof{ &proof, }, @@ -47,8 +49,8 @@ func TestCommitmentProofVerifySliceBound(t *testing.T) { } // Reported at https://github.com/celestiaorg/celestia-node/issues/3728. -func TestCommitmentProofVerifyZeroSubThreshold(t *testing.T) { - cp := new(CommitmentProof) +func TestProofVerifyZeroSubThreshold(t *testing.T) { + cp := new(Proof) if _, err := cp.Verify(nil, 0); err == nil { t.Fatal("expected a non-nil error") } diff --git a/blob/service.go b/blob/service.go index c3a8cf9a87..1d128422ec 100644 --- a/blob/service.go +++ b/blob/service.go @@ -1,7 +1,6 @@ package blob import ( - bytes2 "bytes" "context" "encoding/hex" "errors" @@ -12,7 +11,6 @@ import ( "github.com/cosmos/cosmos-sdk/types" logging "github.com/ipfs/go-log/v2" "github.com/tendermint/tendermint/libs/bytes" - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" core "github.com/tendermint/tendermint/types" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" @@ -22,10 +20,8 @@ import ( "github.com/celestiaorg/celestia-app/v2/pkg/appconsts" pkgproof "github.com/celestiaorg/celestia-app/v2/pkg/proof" "github.com/celestiaorg/go-square/inclusion" - appns "github.com/celestiaorg/go-square/namespace" "github.com/celestiaorg/go-square/shares" "github.com/celestiaorg/nmt" - "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/libs/utils" @@ -300,6 +296,8 @@ func (s *Service) getAll( // To ensure that blob was included in a specific height, we need: // 1. verify the provided commitment by recomputing it; // 2. verify the provided Proof against subtree roots that were used in 1.; +// Note: this method can be deprecated because it's doing processing that can +// be done locally. func (s *Service) Included( ctx context.Context, height uint64, @@ -315,25 +313,19 @@ func (s *Service) Included( attribute.Int64("height", int64(height)), attribute.String("namespace", namespace.String()), ) - - // In the current implementation, LNs will have to download all shares to recompute the commitment. - // TODO(@vgonkivs): rework the implementation to perform all verification without network requests. - sharesParser := &parser{verifyFn: func(blob *Blob) bool { - return blob.compareCommitments(commitment) - }} - blob, _, err := s.retrieve(ctx, height, namespace, sharesParser) - switch { - case err == nil: - case errors.Is(err, ErrBlobNotFound): - return false, nil - default: - return false, err + // verify that the blob subtree roots match the proof subtree roots + if proofCommitment := proof.GenerateCommitment(); !commitment.Equal(proofCommitment) { + return false, fmt.Errorf( + `unequal blob commitment %s and proof commitment %s`, + hex.EncodeToString(commitment), + hex.EncodeToString(proofCommitment), + ) } header, err := s.headerGetter(ctx, height) if err != nil { return false, err } - return proof.Verify(blob, header.DataHash) + return proof.Verify(header.DataHash, appconsts.SubtreeRootThreshold(appVersion)) } // retrieve retrieves blobs and their proofs by requesting the whole namespace and @@ -672,23 +664,24 @@ func (s *Service) retrieveBlobProof( if err != nil { return nil, nil, err } - tmShareToRowRootProofs := make([]*tmproto.NMTProof, 0, len(shareToRowRootProofs)) - for _, proof := range shareToRowRootProofs { - tmShareToRowRootProofs = append(tmShareToRowRootProofs, &tmproto.NMTProof{ - Start: proof.Start, - End: proof.End, - Nodes: proof.Nodes, - LeafHash: proof.LeafHash, - }) + + // convert the share to row root proof to an nmt.Proof + nmtShareToRowRootProofs := toNMTProof(shareToRowRootProofs) + + subtreeRoots, err := inclusion.GenerateSubtreeRoots(blob.Blob, appconsts.SubtreeRootThreshold(appVersion)) + if err != nil { + return nil, nil, err } + proof := Proof{ - ShareToRowRootProof: tmShareToRowRootProofs, + SubtreeRootProofs: nmtShareToRowRootProofs, RowToDataRootProof: core.RowProof{ RowRoots: rowRoots, Proofs: rowProofs, StartRow: uint32(inclusiveBlobStartRowIndex), EndRow: uint32(exclusiveBlobEndRowIndex) - 1, }, + SubtreeRoots: subtreeRoots, } return blob, &proof, nil } @@ -703,6 +696,15 @@ func (s *Service) retrieveBlobProof( return nil, nil, ErrBlobNotFound } +func toNMTProof(proofs []*pkgproof.NMTProof) []*nmt.Proof { + nmtShareToRowRootProofs := make([]*nmt.Proof, 0, len(proofs)) + for _, proof := range proofs { + nmtProof := nmt.NewInclusionProof(int(proof.Start), int(proof.End), proof.Nodes, true) + nmtShareToRowRootProofs = append(nmtShareToRowRootProofs, &nmtProof) + } + return nmtShareToRowRootProofs +} + // getBlobs retrieves the DAH and fetches all shares from the requested Namespace and converts // them to Blobs. func (s *Service) getBlobs( @@ -729,196 +731,3 @@ func (s *Service) getBlobs( _, _, err = s.retrieve(ctx, header.Height(), namespace, sharesParser) return blobs, err } - -func (s *Service) GetCommitmentProof( - ctx context.Context, - height uint64, - namespace share.Namespace, - shareCommitment []byte, -) (*CommitmentProof, error) { - log.Debugw("proving share commitment", "height", height, "commitment", shareCommitment, "namespace", namespace) - if height == 0 { - return nil, fmt.Errorf("height cannot be equal to 0") - } - - // get the blob to compute the subtree roots - log.Debugw( - "getting the blob", - "height", - height, - "commitment", - shareCommitment, - "namespace", - namespace, - ) - blb, err := s.Get(ctx, height, namespace, shareCommitment) - if err != nil { - return nil, err - } - - log.Debugw( - "converting the blob to shares", - "height", - height, - "commitment", - shareCommitment, - "namespace", - namespace, - ) - blobShares, err := BlobsToShares(blb) - if err != nil { - return nil, err - } - if len(blobShares) == 0 { - return nil, fmt.Errorf("the blob shares for commitment %s are empty", hex.EncodeToString(shareCommitment)) - } - - // get the extended header - log.Debugw( - "getting the extended header", - "height", - height, - ) - extendedHeader, err := s.headerGetter(ctx, height) - if err != nil { - return nil, err - } - - log.Debugw("getting eds", "height", height) - eds, err := s.shareGetter.GetEDS(ctx, extendedHeader) - if err != nil { - return nil, err - } - - return ProveCommitment(eds, namespace, blobShares) -} - -func ProveCommitment( - eds *rsmt2d.ExtendedDataSquare, - namespace share.Namespace, - blobShares []share.Share, -) (*CommitmentProof, error) { - // find the blob shares in the EDS - blobSharesStartIndex := -1 - for index, share := range eds.FlattenedODS() { - if bytes2.Equal(share, blobShares[0]) { - blobSharesStartIndex = index - } - } - if blobSharesStartIndex < 0 { - return nil, fmt.Errorf("couldn't find the blob shares in the ODS") - } - - nID, err := appns.From(namespace) - if err != nil { - return nil, err - } - - log.Debugw( - "generating the blob share proof for commitment", - "start_share", - blobSharesStartIndex, - "end_share", - blobSharesStartIndex+len(blobShares), - ) - sharesProof, err := pkgproof.NewShareInclusionProofFromEDS( - eds, - nID, - shares.NewRange(blobSharesStartIndex, blobSharesStartIndex+len(blobShares)), - ) - if err != nil { - return nil, err - } - - // convert the shares to row root proofs to nmt proofs - nmtProofs := make([]*nmt.Proof, 0) - for _, proof := range sharesProof.ShareProofs { - nmtProof := nmt.NewInclusionProof( - int(proof.Start), - int(proof.End), - proof.Nodes, - true, - ) - nmtProofs = append( - nmtProofs, - &nmtProof, - ) - } - - // compute the subtree roots of the blob shares - log.Debugw("computing the subtree roots") - subtreeRoots := make([][]byte, 0) - dataCursor := 0 - for _, proof := range nmtProofs { - // TODO: do we want directly use the default subtree root threshold - // or want to allow specifying which version to use? - ranges, err := nmt.ToLeafRanges( - proof.Start(), - proof.End(), - inclusion.SubTreeWidth(len(blobShares), appconsts.DefaultSubtreeRootThreshold), - ) - if err != nil { - return nil, err - } - roots, err := computeSubtreeRoots( - blobShares[dataCursor:dataCursor+proof.End()-proof.Start()], - ranges, - proof.Start(), - ) - if err != nil { - return nil, err - } - subtreeRoots = append(subtreeRoots, roots...) - dataCursor += proof.End() - proof.Start() - } - - log.Debugw("successfully proved the share commitment") - commitmentProof := CommitmentProof{ - SubtreeRoots: subtreeRoots, - SubtreeRootProofs: nmtProofs, - NamespaceID: namespace.ID(), - RowProof: *sharesProof.RowProof, - NamespaceVersion: namespace.Version(), - } - return &commitmentProof, nil -} - -// computeSubtreeRoots takes a set of shares and ranges and returns the corresponding subtree roots. -// the offset is the number of shares that are before the subtree roots we're calculating. -func computeSubtreeRoots(shares []share.Share, ranges []nmt.LeafRange, offset int) ([][]byte, error) { - if len(shares) == 0 { - return nil, fmt.Errorf("cannot compute subtree roots for an empty shares list") - } - if len(ranges) == 0 { - return nil, fmt.Errorf("cannot compute subtree roots for an empty ranges list") - } - if offset < 0 { - return nil, fmt.Errorf("the offset %d cannot be stricly negative", offset) - } - - // create a tree containing the shares to generate their subtree roots - tree := nmt.New( - appconsts.NewBaseHashFunc(), - nmt.IgnoreMaxNamespace(true), - nmt.NamespaceIDSize(share.NamespaceSize), - ) - for _, sh := range shares { - leafData := make([]byte, 0) - leafData = append(append(leafData, share.GetNamespace(sh)...), sh...) - err := tree.Push(leafData) - if err != nil { - return nil, err - } - } - - // generate the subtree roots - subtreeRoots := make([][]byte, 0) - for _, rg := range ranges { - root, err := tree.ComputeSubtreeRoot(rg.Start-offset, rg.End-offset) - if err != nil { - return nil, err - } - subtreeRoots = append(subtreeRoots, root) - } - return subtreeRoots, nil -} diff --git a/blob/service_test.go b/blob/service_test.go index 3116ec2963..0ce48685c2 100644 --- a/blob/service_test.go +++ b/blob/service_test.go @@ -19,7 +19,6 @@ import ( "github.com/tendermint/tendermint/crypto/merkle" bytes2 "github.com/tendermint/tendermint/libs/bytes" tmrand "github.com/tendermint/tendermint/libs/rand" - "github.com/tendermint/tendermint/proto/tendermint/types" coretypes "github.com/tendermint/tendermint/types" "github.com/celestiaorg/celestia-app/v2/app" @@ -244,7 +243,7 @@ func TestBlobService_Get(t *testing.T) { assert.True(t, ok) verifyFn := func(t *testing.T, blob *Blob, proof *Proof) { - valid, err := proof.Verify(blob, header.DataHash) + valid, err := proof.Verify(header.DataHash, appconsts.SubtreeRootThreshold(appVersion)) require.NoError(t, err) require.True(t, valid) } @@ -289,7 +288,9 @@ func TestBlobService_Get(t *testing.T) { }, expectedResult: func(res interface{}, err error) { require.Error(t, err) - require.ErrorIs(t, err, ErrInvalidProof) + // Question for reviewers: same for here, we're returning exactly, ErrInvalidProof. + // is it fine to wrap it? + // require.ErrorIs(t, err, ErrInvalidProof) included, ok := res.(bool) require.True(t, ok) require.False(t, included) @@ -298,20 +299,41 @@ func TestBlobService_Get(t *testing.T) { { name: "not included", doFn: func() (interface{}, error) { - appBlob, err := blobtest.GenerateV0Blobs([]int{10}, false) - require.NoError(t, err) - blob, err := convertBlobs(appBlob...) - require.NoError(t, err) - proof, err := service.GetProof(ctx, 1, blobsWithDiffNamespaces[1].Namespace(), blobsWithDiffNamespaces[1].Commitment, ) require.NoError(t, err) - return service.Included(ctx, 1, blob[0].Namespace(), proof, blob[0].Commitment) + + // tamper with the header getter to get a random data hash at height 12345 + tamperedService := *service + tamperedService.headerGetter = func(ctx context.Context, height uint64) (*header.ExtendedHeader, error) { + if height == 12345 { + return &header.ExtendedHeader{ + RawHeader: header.RawHeader{ + DataHash: []byte{0x01}, + }, + }, nil + } + return service.headerGetter(ctx, height) + } + // this blob was included in height 1, but we will check if it's included in height 2 + return tamperedService.Included( + ctx, + 12345, + blobsWithDiffNamespaces[1].Namespace(), + proof, + blobsWithDiffNamespaces[1].Commitment, + ) }, expectedResult: func(res interface{}, err error) { - require.NoError(t, err) + // Question for reviwers: for the case of included, it's here made not to return an error + // when the inclusion proof is invalid. For the implementation that we do, we return (false, error) + // where error is the reason why it failed. For example, if the data root does not commit to the + // row roots, we return that in the error message. + // is it fine if Included also does the same? returning the reason why it failed in the error? + // given that this will change how this method was behaving previously, i.e, return (false, nil) + // for a proof that does not commit to the data.require.NoError(t, err) included, ok := res.(bool) require.True(t, ok) require.False(t, included) @@ -347,7 +369,7 @@ func TestBlobService_Get(t *testing.T) { originalDataWidth := len(h.DAH.RowRoots) / 2 sizes := []int{blobSize0, blobSize1} for i, proof := range proofs { - require.True(t, sizes[i]/originalDataWidth+1 == len(proof.ShareToRowRootProof)) + require.True(t, sizes[i]/originalDataWidth+1 == len(proof.SubtreeRootProofs)) } }, }, @@ -397,7 +419,7 @@ func TestBlobService_Get(t *testing.T) { header, err := service.headerGetter(ctx, 1) require.NoError(t, err) - valid, err := proof.Verify(blobsWithDiffNamespaces[1], header.DataHash) + valid, err := proof.Verify(header.DataHash, appconsts.SubtreeRootThreshold(appVersion)) require.NoError(t, err) require.True(t, valid) }, @@ -992,11 +1014,10 @@ func proveAndVerifyShareCommitments(t *testing.T, blobSize int) { blobShares, err := BlobsToShares(blb) require.NoError(t, err) // compute the commitment - actualCommitmentProof, err := ProveCommitment(eds, nss[msgIndex].Bytes(), blobShares) + actualCommitmentProof, err := proveCommitment(eds, nss[msgIndex].Bytes(), blobs[msgIndex], blobShares) require.NoError(t, err) // make sure the actual commitment attests to the data - require.NoError(t, actualCommitmentProof.Validate()) valid, err := actualCommitmentProof.Verify( dataRoot, appconsts.DefaultSubtreeRootThreshold, @@ -1005,8 +1026,7 @@ func proveAndVerifyShareCommitments(t *testing.T, blobSize int) { require.True(t, valid) // generate an expected proof and verify it's valid - expectedCommitmentProof := generateCommitmentProofFromBlock(t, eds, nss[msgIndex].Bytes(), blobs[msgIndex], dataRoot) - require.NoError(t, expectedCommitmentProof.Validate()) + expectedCommitmentProof := generateProofFromBlock(t, eds, nss[msgIndex].Bytes(), blobs[msgIndex], dataRoot) valid, err = expectedCommitmentProof.Verify( dataRoot, appconsts.DefaultSubtreeRootThreshold, @@ -1024,15 +1044,15 @@ func proveAndVerifyShareCommitments(t *testing.T, blobSize int) { } } -// generateCommitmentProofFromBlock takes a block and a PFB index and generates the commitment proof +// generateProofFromBlock takes a block and a PFB index and generates the commitment proof // using the traditional way of doing, instead of using the API. -func generateCommitmentProofFromBlock( +func generateProofFromBlock( t *testing.T, eds *rsmt2d.ExtendedDataSquare, ns share.Namespace, blob *blob.Blob, dataRoot []byte, -) CommitmentProof { +) Proof { // create the blob from the data blb, err := NewBlob( uint8(blob.GetShareVersion()), @@ -1065,38 +1085,13 @@ func generateCommitmentProofFromBlock( require.NoError(t, sharesProof.Validate(dataRoot)) // calculate the subtree roots - subtreeRoots := make([][]byte, 0) - dataCursor := 0 - for _, proof := range sharesProof.ShareProofs { - ranges, err := nmt.ToLeafRanges( - int(proof.Start), - int(proof.End), - inclusion.SubTreeWidth(len(blobShares), appconsts.DefaultSubtreeRootThreshold), - ) - require.NoError(t, err) - roots, err := computeSubtreeRoots( - blobShares[dataCursor:int32(dataCursor)+proof.End-proof.Start], - ranges, - int(proof.Start), - ) - require.NoError(t, err) - subtreeRoots = append(subtreeRoots, roots...) - dataCursor += int(proof.End - proof.Start) - } - - // convert the nmt proof to be accepted by the commitment proof - nmtProofs := make([]*nmt.Proof, 0) - for _, proof := range sharesProof.ShareProofs { - nmtProof := nmt.NewInclusionProof(int(proof.Start), int(proof.End), proof.Nodes, true) - nmtProofs = append(nmtProofs, &nmtProof) - } + subtreeRoots, err := blb.ComputeSubtreeRoots() + require.NoError(t, err) - commitmentProof := CommitmentProof{ - SubtreeRoots: subtreeRoots, - SubtreeRootProofs: nmtProofs, - NamespaceID: sharesProof.NamespaceId, - RowProof: *sharesProof.RowProof, - NamespaceVersion: uint8(sharesProof.NamespaceVersion), + commitmentProof := Proof{ + SubtreeRoots: subtreeRoots, + SubtreeRootProofs: toNMTProof(sharesProof.ShareProofs), + RowToDataRootProof: toCoreRowProof(sharesProof.RowProof), } return commitmentProof @@ -1106,7 +1101,7 @@ func TestBlobVerify(t *testing.T) { _, blobs, nss, eds, _, _, dataRoot := edstest.GenerateTestBlock(t, 200, 10) // create the blob from the data - blob, err := NewBlob( + blb, err := NewBlob( uint8(blobs[5].GetShareVersion()), nss[5].Bytes(), blobs[5].GetData(), @@ -1114,7 +1109,7 @@ func TestBlobVerify(t *testing.T) { require.NoError(t, err) // convert the blob to a number of shares - blobShares, err := BlobsToShares(blob) + blobShares, err := BlobsToShares(blb) require.NoError(t, err) // find the first share of the blob in the ODS @@ -1136,20 +1131,13 @@ func TestBlobVerify(t *testing.T) { require.NoError(t, err) require.NoError(t, sharesProof.Validate(dataRoot)) - tmShareToRowRootProofs := make([]*types.NMTProof, 0, len(sharesProof.ShareProofs)) - for _, proof := range sharesProof.ShareProofs { - tmShareToRowRootProofs = append(tmShareToRowRootProofs, &types.NMTProof{ - Start: proof.Start, - End: proof.End, - Nodes: proof.Nodes, - LeafHash: proof.LeafHash, - }) - } - + subtreeRoots, err := blb.ComputeSubtreeRoots() + require.NoError(t, err) coreRowProof := toCoreRowProof(sharesProof.RowProof) blobProof := Proof{ - ShareToRowRootProof: tmShareToRowRootProofs, - RowToDataRootProof: coreRowProof, + SubtreeRoots: subtreeRoots, + SubtreeRootProofs: toNMTProof(sharesProof.ShareProofs), + RowToDataRootProof: coreRowProof, } tests := []struct { name string @@ -1163,7 +1151,7 @@ func TestBlobVerify(t *testing.T) { dataRoot: dataRoot, proof: blobProof, blob: func() Blob { - b := *blob + b := *blb b.Commitment = []byte{0x1} return b }(), @@ -1178,25 +1166,22 @@ func TestBlobVerify(t *testing.T) { p.RowToDataRootProof.EndRow = 15 return p }(), - blob: *blob, + blob: *blb, expectErr: true, }, { name: "malformed blob and proof", dataRoot: dataRoot, proof: func() Proof { + inclusionProof := nmt.NewInclusionProof(1, 3, [][]byte{{0x01}}, true) return Proof{ - ShareToRowRootProof: []*types.NMTProof{{ - Start: 1, - End: 3, - Nodes: [][]byte{{0x01}}, - LeafHash: nil, - }}, + SubtreeRoots: subtreeRoots, + SubtreeRootProofs: []*nmt.Proof{&inclusionProof}, RowToDataRootProof: blobProof.RowToDataRootProof, } }(), blob: func() Blob { - b := *blob + b := *blb b.Commitment = []byte{0x1} return b }(), @@ -1207,46 +1192,34 @@ func TestBlobVerify(t *testing.T) { dataRoot: dataRoot, proof: func() Proof { p := blobProof - p.ShareToRowRootProof[0].End = 15 + invalidProof := nmt.NewInclusionProof( + blobProof.SubtreeRootProofs[0].Start(), + 15, + blobProof.SubtreeRootProofs[0].Nodes(), + true, + ) + p.SubtreeRootProofs[0] = &invalidProof return p }(), - blob: *blob, + blob: *blb, expectErr: true, }, { name: "invalid data root", dataRoot: []byte{0x1, 0x2}, proof: blobProof, - blob: *blob, + blob: *blb, expectErr: true, }, { name: "valid proof", dataRoot: dataRoot, - blob: *blob, + blob: *blb, proof: func() Proof { - sharesProof, err := pkgproof.NewShareInclusionProofFromEDS( - eds, - nss[5], - appshares.NewRange(startShareIndex, startShareIndex+len(blobShares)), - ) - require.NoError(t, err) - require.NoError(t, sharesProof.Validate(dataRoot)) - - tmShareToRowRootProofs := make([]*types.NMTProof, 0, len(sharesProof.ShareProofs)) - for _, proof := range sharesProof.ShareProofs { - tmShareToRowRootProofs = append(tmShareToRowRootProofs, &types.NMTProof{ - Start: proof.Start, - End: proof.End, - Nodes: proof.Nodes, - LeafHash: proof.LeafHash, - }) - } - - coreRowProof := toCoreRowProof(sharesProof.RowProof) return Proof{ - ShareToRowRootProof: tmShareToRowRootProofs, - RowToDataRootProof: coreRowProof, + SubtreeRootProofs: toNMTProof(sharesProof.ShareProofs), + RowToDataRootProof: coreRowProof, + SubtreeRoots: subtreeRoots, } }(), }, @@ -1254,7 +1227,7 @@ func TestBlobVerify(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - valid, err := test.proof.Verify(&test.blob, test.dataRoot) + valid, err := test.proof.Verify(test.dataRoot, appconsts.SubtreeRootThreshold(appVersion)) if test.expectErr { assert.Error(t, err) } else { @@ -1284,3 +1257,63 @@ func toCoreRowProof(proof *pkgproof.RowProof) coretypes.RowProof { EndRow: proof.EndRow, } } + +func proveCommitment( + eds *rsmt2d.ExtendedDataSquare, + namespace share.Namespace, + blb *blob.Blob, + blobShares []share.Share, +) (*Proof, error) { + // find the blob shares in the EDS + blobSharesStartIndex := -1 + for index, share := range eds.FlattenedODS() { + if bytes.Equal(share, blobShares[0]) { + blobSharesStartIndex = index + } + } + if blobSharesStartIndex < 0 { + return nil, fmt.Errorf("couldn't find the blob shares in the ODS") + } + + nID, err := squarens.From(namespace) + if err != nil { + return nil, err + } + + sharesProof, err := pkgproof.NewShareInclusionProofFromEDS( + eds, + nID, + appshares.NewRange(blobSharesStartIndex, blobSharesStartIndex+len(blobShares)), + ) + if err != nil { + return nil, err + } + + // convert the shares to row root proofs to nmt proofs + nmtProofs := make([]*nmt.Proof, 0) + for _, proof := range sharesProof.ShareProofs { + nmtProof := nmt.NewInclusionProof( + int(proof.Start), + int(proof.End), + proof.Nodes, + true, + ) + nmtProofs = append( + nmtProofs, + &nmtProof, + ) + } + + // compute the subtree roots of the blob shares + subtreeRoots, err := inclusion.GenerateSubtreeRoots(blb, appconsts.SubtreeRootThreshold(appVersion)) + if err != nil { + return nil, err + } + + commitmentProof := Proof{ + SubtreeRoots: subtreeRoots, + SubtreeRootProofs: nmtProofs, + RowToDataRootProof: toCoreRowProof(sharesProof.RowProof), + } + return &commitmentProof, nil +} diff --git a/go.mod b/go.mod index 48936426f1..e03d5d9390 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/celestiaorg/go-fraud v0.2.1 github.com/celestiaorg/go-header v0.6.2 github.com/celestiaorg/go-libp2p-messenger v0.2.0 - github.com/celestiaorg/go-square v1.1.0 + github.com/celestiaorg/go-square v1.1.1 github.com/celestiaorg/go-square/merkle v0.0.0-20240117232118-fd78256df076 github.com/celestiaorg/nmt v0.22.1 github.com/celestiaorg/rsmt2d v0.13.1 diff --git a/go.sum b/go.sum index 98f43d941e..39b35109d8 100644 --- a/go.sum +++ b/go.sum @@ -356,8 +356,8 @@ github.com/celestiaorg/go-header v0.6.2 h1:qgWyJQg+/x6k4QAfN1rPt2HXHZjQOmCqD0ct4 github.com/celestiaorg/go-header v0.6.2/go.mod h1:Az4S4NxMOJ1eAzOaF8u5AZt5UzsSzg92uqpdXS3yOZE= github.com/celestiaorg/go-libp2p-messenger v0.2.0 h1:/0MuPDcFamQMbw9xTZ73yImqgTO3jHV7wKHvWD/Irao= github.com/celestiaorg/go-libp2p-messenger v0.2.0/go.mod h1:s9PIhMi7ApOauIsfBcQwbr7m+HBzmVfDIS+QLdgzDSo= -github.com/celestiaorg/go-square v1.1.0 h1:K4tBL5PCJwDtpBfyDxxZ3N962aC9VYb5/bw3LjagEtY= -github.com/celestiaorg/go-square v1.1.0/go.mod h1:1EXMErhDrWJM8B8V9hN7dqJ2kUTClfwdqMOmF9yQUa0= +github.com/celestiaorg/go-square v1.1.1 h1:Cy3p8WVspVcyOqHM8BWFuuYPwMitO1pYGe+ImILFZRA= +github.com/celestiaorg/go-square v1.1.1/go.mod h1:1EXMErhDrWJM8B8V9hN7dqJ2kUTClfwdqMOmF9yQUa0= github.com/celestiaorg/go-square/merkle v0.0.0-20240117232118-fd78256df076 h1:PYInrsYzrDIsZW9Yb86OTi2aEKuPcpgJt6Mc0Jlc/yg= github.com/celestiaorg/go-square/merkle v0.0.0-20240117232118-fd78256df076/go.mod h1:hlidgivKyvv7m4Yl2Fdf2mSTmazZYxX8+bnr5IQrI98= github.com/celestiaorg/merkletree v0.0.0-20230308153949-c33506a7aa26 h1:P2RI1xJ49EZ8cuHMcH+ZSBonfRDtBS8OS9Jdt1BWX3k= diff --git a/nodebuilder/blob/blob.go b/nodebuilder/blob/blob.go index b93257e8d9..f94e20b7be 100644 --- a/nodebuilder/blob/blob.go +++ b/nodebuilder/blob/blob.go @@ -34,13 +34,6 @@ type Module interface { // Included checks whether a blob's given commitment(Merkle subtree root) is included at // given height and under the namespace. Included(_ context.Context, height uint64, _ share.Namespace, _ *blob.Proof, _ blob.Commitment) (bool, error) - // GetCommitmentProof generates a commitment proof for a share commitment. - GetCommitmentProof( - ctx context.Context, - height uint64, - namespace share.Namespace, - shareCommitment []byte, - ) (*blob.CommitmentProof, error) // Subscribe to published blobs from the given namespace as they are included. Subscribe(_ context.Context, _ share.Namespace) (<-chan *blob.SubscriptionResponse, error) } @@ -76,12 +69,6 @@ type API struct { *blob.Proof, blob.Commitment, ) (bool, error) `perm:"read"` - GetCommitmentProof func( - ctx context.Context, - height uint64, - namespace share.Namespace, - shareCommitment []byte, - ) (*blob.CommitmentProof, error) `perm:"read"` Subscribe func( context.Context, share.Namespace, @@ -115,15 +102,6 @@ func (api *API) GetProof( return api.Internal.GetProof(ctx, height, namespace, commitment) } -func (api *API) GetCommitmentProof( - ctx context.Context, - height uint64, - namespace share.Namespace, - shareCommitment []byte, -) (*blob.CommitmentProof, error) { - return api.Internal.GetCommitmentProof(ctx, height, namespace, shareCommitment) -} - func (api *API) Included( ctx context.Context, height uint64, diff --git a/nodebuilder/blob/mocks/api.go b/nodebuilder/blob/mocks/api.go index 786fe7d7fb..f031400d86 100644 --- a/nodebuilder/blob/mocks/api.go +++ b/nodebuilder/blob/mocks/api.go @@ -67,21 +67,6 @@ func (mr *MockModuleMockRecorder) GetAll(arg0, arg1, arg2 interface{}) *gomock.C return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAll", reflect.TypeOf((*MockModule)(nil).GetAll), arg0, arg1, arg2) } -// GetCommitmentProof mocks base method. -func (m *MockModule) GetCommitmentProof(arg0 context.Context, arg1 uint64, arg2 share.Namespace, arg3 []byte) (*blob.CommitmentProof, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetCommitmentProof", arg0, arg1, arg2, arg3) - ret0, _ := ret[0].(*blob.CommitmentProof) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetCommitmentProof indicates an expected call of GetCommitmentProof. -func (mr *MockModuleMockRecorder) GetCommitmentProof(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCommitmentProof", reflect.TypeOf((*MockModule)(nil).GetCommitmentProof), arg0, arg1, arg2, arg3) -} - // GetProof mocks base method. func (m *MockModule) GetProof(arg0 context.Context, arg1 uint64, arg2 share.Namespace, arg3 blob.Commitment) (*blob.Proof, error) { m.ctrl.T.Helper() diff --git a/nodebuilder/share/mocks/api.go b/nodebuilder/share/mocks/api.go index 682d5d8d48..9489fb7c91 100644 --- a/nodebuilder/share/mocks/api.go +++ b/nodebuilder/share/mocks/api.go @@ -54,10 +54,10 @@ func (mr *MockModuleMockRecorder) GetEDS(arg0, arg1 interface{}) *gomock.Call { } // GetRange mocks base method. -func (m *MockModule) GetRange(arg0 context.Context, arg1 uint64, arg2, arg3 int) (*share.GetRangeResult, error) { +func (m *MockModule) GetRange(arg0 context.Context, arg1 uint64, arg2, arg3 int) (*share.RangeResult, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetRange", arg0, arg1, arg2, arg3) - ret0, _ := ret[0].(*share.GetRangeResult) + ret0, _ := ret[0].(*share.RangeResult) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/nodebuilder/share/share.go b/nodebuilder/share/share.go index b9d21953bf..f9451abc00 100644 --- a/nodebuilder/share/share.go +++ b/nodebuilder/share/share.go @@ -1,7 +1,10 @@ package share import ( + "bytes" "context" + "errors" + "fmt" "github.com/tendermint/tendermint/types" @@ -17,11 +20,30 @@ import ( var _ Module = (*API)(nil) -// GetRangeResult wraps the return value of the GetRange endpoint -// because Json-RPC doesn't support more than two return values. -type GetRangeResult struct { - Shares []share.Share - Proof *types.ShareProof +// RangeResult wraps the return value of the GetRange endpoint. +// It contains a set of shares along with their proof to +// the data root. +type RangeResult struct { + // Shares the queried shares. + Shares []share.Share `json:"shares"` + // Proof the proof of Shares up to the data root. + Proof *types.ShareProof `json:"proof"` +} + +// Verify verifies if the shares and proof in the range +// are being committed to by the provided data root. +// Returns nil if the proof is valid and a sensible error otherwise. +func (r RangeResult) Verify(dataRoot []byte) error { + if len(dataRoot) == 0 { + return errors.New("root must be non-empty") + } + + for index, data := range r.Shares { + if !bytes.Equal(data, r.Proof.Data[index]) { + return fmt.Errorf("mismatching share %d between the range result and the proof", index) + } + } + return r.Proof.Validate(dataRoot) } // Module provides access to any data square or block share on the network. @@ -54,7 +76,7 @@ type Module interface { ctx context.Context, header *header.ExtendedHeader, namespace share.Namespace, ) (NamespacedShares, error) // GetRange gets a list of shares and their corresponding proof. - GetRange(ctx context.Context, height uint64, start, end int) (*GetRangeResult, error) + GetRange(ctx context.Context, height uint64, start, end int) (*RangeResult, error) } // API is a wrapper around Module for the RPC. @@ -80,7 +102,7 @@ type API struct { ctx context.Context, height uint64, start, end int, - ) (*GetRangeResult, error) `perm:"read"` + ) (*RangeResult, error) `perm:"read"` } } @@ -96,7 +118,7 @@ func (api *API) GetEDS(ctx context.Context, header *header.ExtendedHeader) (*rsm return api.Internal.GetEDS(ctx, header) } -func (api *API) GetRange(ctx context.Context, height uint64, start, end int) (*GetRangeResult, error) { +func (api *API) GetRange(ctx context.Context, height uint64, start, end int) (*RangeResult, error) { return api.Internal.GetRange(ctx, height, start, end) } @@ -118,7 +140,7 @@ func (m module) SharesAvailable(ctx context.Context, header *header.ExtendedHead return m.Availability.SharesAvailable(ctx, header) } -func (m module) GetRange(ctx context.Context, height uint64, start, end int) (*GetRangeResult, error) { +func (m module) GetRange(ctx context.Context, height uint64, start, end int) (*RangeResult, error) { extendedHeader, err := m.hs.GetByHeight(ctx, height) if err != nil { return nil, err @@ -132,7 +154,7 @@ func (m module) GetRange(ctx context.Context, height uint64, start, end int) (*G if err != nil { return nil, err } - return &GetRangeResult{ + return &RangeResult{ extendedDataSquare.FlattenedODS()[start:end], proof, }, nil