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

feat!: return proofs up to the data root #3796

Draft
wants to merge 5 commits into
base: main
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
78 changes: 50 additions & 28 deletions api/docgen/exampledata/blobProof.json
Original file line number Diff line number Diff line change
@@ -1,31 +1,53 @@
[
{
"end": 8,
"nodes": [
"/////////////////////////////////////////////////////////////////////////////wuxStDHcZ7+b5byNQMVLJbzBT3wmObsThoQ0sCTjTCP"
{
"ShareToRowRootProof": [
{
"start": 3,
"end": 4,
"nodes": [
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ72eTVOUxB9THxFjAEwtTePJQA1b0xcz2f6TJc400Uw",
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBD0CYbGYoGN4q9VfSmeGZeg/h1NDBA/jtXjZrrKRHE6",
"/////////////////////////////////////////////////////////////////////////////8KDE4JDf0N2lZB7DW1Fpasdk/wz4jHOxuBPAk5Vf5ZI"
]
},
{
"end": 1,
"nodes": [
"//////////////////////////////////////7//////////////////////////////////////plEqgR/c4IAVkNdYRWOYOAESD4whneKR54Dz5Dfe4p2",
"//////////////////////////////////////7//////////////////////////////////////lrD0qJ9dspxSO1Yl8NDioZfgOm8Yj63Y+BGDRHlKCRj",
"/////////////////////////////////////////////////////////////////////////////xQyI+g89aM6rhy9rl2eKr0Uc2NPauf3fkLY3Z+gBtuM"
]
}
],
"RowProof": {
"row_roots": [
"00000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000808080808080808BC517066A5A8C81E2A4353DB500EBB3410047A93D2EE8ADF0B6797B9A5519557",
"0000000000000000000000000000000000000000000808080808080808FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE015AB6AAC6FAF0ABF26F9453AF390FDA3B39EB384F0B71D0170D84CF69CBA2BC"
],
"is_max_namespace_ignored": true
},
{
"end": 8,
"nodes": [
"//////////////////////////////////////////////////////////////////////////////n1NeJxPU2bZUAccKZZ+LAu2Wj5ajbVYURV9ojhSKwp"
"proofs": [
{
"total": 16,
"index": 1,
"leaf_hash": "lJek/BHnKH6PyRB8jlk69F6EY9Tfx2LRanaF74JVciU=",
"aunts": [
"bLjvftajE6jVsgQQBkV4RUPESRc+v4bhP0Ljf36858Q=",
"QaF9mNskaURxk98S3BExB1PzRAjOqVydrDLvUu0B5/M=",
"K2xW8JJ3Ff4FvtbfZi5ZD/ygnswaNCNIKXsSzbO2Jrc=",
"uySRG/gINLAgGgywJCTiXMlFkfQivF1O1zLg5+RRUP8="
]
},
{
"total": 16,
"index": 2,
"leaf_hash": "h+4ND52kT4qkc9nWW22dIMAK/4YjkC6fBoD01WF0+Uo=",
"aunts": [
"2x8OISRBMLYJRV8NfTNtVvZUg2F7MtCK5xCZuE9fQwQ=",
"Xvr5IalE2y3pxHjxh5kcHFSRaz4g5MxdOj4NIGwRXY0=",
"K2xW8JJ3Ff4FvtbfZi5ZD/ygnswaNCNIKXsSzbO2Jrc=",
"uySRG/gINLAgGgywJCTiXMlFkfQivF1O1zLg5+RRUP8="
]
}
],
"is_max_namespace_ignored": true
},
{
"end": 8,
"nodes": [
"/////////////////////////////////////////////////////////////////////////////0xK8BKnzDmwK0HR4ZJvyB4kh3jPPXGxaGPFoga8vPxF"
],
"is_max_namespace_ignored": true
},
{
"end": 7,
"nodes": [
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAMJ/xGlNMdEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwn/EaU0x0UTO9HUGKjyjcv5U2gHeSjJ8S1rftqv6k8kxlVWW8e/7",
"/////////////////////////////////////////////////////////////////////////////wexh4khLQ9HQ2X6nh9wU5B+m6r+LWwPTEDTa5/CosDF"
],
"is_max_namespace_ignored": true
"start_row": 1,
"end_row": 3
}
]
}
108 changes: 78 additions & 30 deletions blob/blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ 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"
v2 "github.com/celestiaorg/celestia-app/v2/pkg/appconsts/v2"
Expand All @@ -23,40 +26,78 @@ const appVersion = v2.Version

var errEmptyShares = errors.New("empty shares")

// The Proof is a set of nmt proofs that can be verified only through
// the included method (due to limitation of the nmt https://github.com/celestiaorg/nmt/issues/218).
// Proof proves the WHOLE namespaced data to the row roots.
// TODO (@vgonkivs): rework `Proof` in order to prove a particular blob.
// https://github.com/celestiaorg/celestia-node/issues/2303
type Proof []*nmt.Proof

func (p Proof) Len() int { return len(p) }

// equal is a temporary method that compares two proofs.
// should be removed in BlobService V1.
func (p Proof) equal(input Proof) error {
if p.Len() != input.Len() {
return ErrInvalidProof
}

for i, proof := range p {
pNodes := proof.Nodes()
inputNodes := input[i].Nodes()
for i, node := range pNodes {
if !bytes.Equal(node, inputNodes[i]) {
return ErrInvalidProof
}
}
// 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.
// If the blob spans across multiple rows, then this will contain multiple proofs.
ShareToRowRootProof []*tmproto.NMTProof
// RowToDataRootProof the proofs of the row roots containing the blob shares
// to the data root.
RowToDataRootProof coretypes.RowProof
}

if proof.Start() != input[i].Start() || proof.End() != input[i].End() {
return ErrInvalidProof
}
// 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
}
if !blob.Commitment.Equal(blobCommitment) {
return false, fmt.Errorf(
"%w: generated commitment does not match the provided blob commitment",
ErrMismatchCommitment,
)
}
rawShares, err := BlobsToShares(blob)
if err != nil {
return false, err
}
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
if err := p.RowToDataRootProof.Validate(dataRoot); err != nil {
return false, fmt.Errorf("%w: invalid row root to data root proof", err)
}

if !bytes.Equal(proof.LeafHash(), input[i].LeafHash()) {
return ErrInvalidProof
// 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)
}
nmtProof := nmt.NewInclusionProof(
int(proof.Start),
int(proof.End),
proof.Nodes,
true,
)
valid := nmtProof.VerifyInclusion(
consts.NewBaseHashFunc(),
ns,
rawShares[cursor:sharesUsed+cursor],
p.RowToDataRootProof.RowRoots[i],
)
if !valid {
return false, ErrInvalidProof
}
cursor += sharesUsed
}
return nil
return true, nil
}

// Blob represents any application-specific binary data that anyone can submit to Celestia.
Expand Down Expand Up @@ -182,3 +223,10 @@ func (b *Blob) UnmarshalJSON(data []byte) error {
b.index = jsonBlob.Index
return nil
}

// 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 {
_, proofs := merkle.ProofsFromByteSlices(roots)
return proofs[start:end]
}
Loading
Loading