-
Notifications
You must be signed in to change notification settings - Fork 0
/
share_proof.go
136 lines (121 loc) · 4.09 KB
/
share_proof.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package celestia_da_light_client
import (
"crypto/sha256"
"errors"
"fmt"
"math"
"github.com/celestiaorg/nmt"
"github.com/cometbft/cometbft/proto/tendermint/crypto"
)
// ShareProof is an NMT proof that a set of shares exist in a set of rows and a
// Merkle proof that those rows exist in a Merkle tree with a given data root.
type shareProof struct {
// Data are the raw shares that are being proven.
Data [][]byte `json:"data"`
// ShareProofs are NMT proofs that the shares in Data exist in a set of
// rows. There will be one ShareProof per row that the shares occupy.
ShareProofs []*NMTProof `json:"share_proofs"`
// NamespaceID is the namespace id of the shares 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 []byte `json:"namespace_id"`
RowProof rowProof `json:"row_proof"`
NamespaceVersion uint32 `json:"namespace_version"`
}
func (sp shareProof) ToProto() ShareProof {
// TODO consider extracting a ToProto function for RowProof
rowRoots := make([][]byte, len(sp.RowProof.RowRoots))
rowProofs := make([]*crypto.Proof, len(sp.RowProof.Proofs))
for i := range sp.RowProof.RowRoots {
rowRoots[i] = sp.RowProof.RowRoots[i].Bytes()
rowProofs[i] = sp.RowProof.Proofs[i].ToProto()
}
pbtp := ShareProof{
Data: sp.Data,
ShareProofs: sp.ShareProofs,
NamespaceId: sp.NamespaceID,
RowProof: &RowProof{
RowRoots: rowRoots,
Proofs: rowProofs,
StartRow: sp.RowProof.StartRow,
EndRow: sp.RowProof.EndRow,
},
NamespaceVersion: sp.NamespaceVersion,
}
return pbtp
}
// shareProofFromProto creates a ShareProof from a proto message.
// Expects the proof to be pre-validated.
func ShareProofFromProto(pb *ShareProof) (shareProof, error) {
if pb == nil {
return shareProof{}, fmt.Errorf("nil share proof protobuf")
}
return shareProof{
RowProof: rowProofFromProto(pb.RowProof),
Data: pb.Data,
ShareProofs: pb.ShareProofs,
NamespaceID: pb.NamespaceId,
NamespaceVersion: pb.NamespaceVersion,
}, nil
}
// Validate runs basic validations on the proof then verifies if it is consistent.
// It returns nil if the proof is valid. Otherwise, it returns a sensible error.
// The `root` is the block data root that the shares to be proven belong to.
// Note: these proofs are tested on the app side.
func (sp shareProof) Validate(root []byte) error {
numberOfSharesInProofs := int32(0)
for _, proof := range sp.ShareProofs {
// the range is not inclusive from the left.
numberOfSharesInProofs += proof.End - proof.Start
}
if len(sp.ShareProofs) != len(sp.RowProof.RowRoots) {
return fmt.Errorf("the number of share proofs %d must equal the number of row roots %d", len(sp.ShareProofs), len(sp.RowProof.RowRoots))
}
if len(sp.Data) != int(numberOfSharesInProofs) {
return fmt.Errorf("the number of shares %d must equal the number of shares in share proofs %d", len(sp.Data), numberOfSharesInProofs)
}
for _, proof := range sp.ShareProofs {
if proof.Start < 0 {
return errors.New("proof index cannot be negative")
}
if (proof.End - proof.Start) <= 0 {
return errors.New("proof total must be positive")
}
}
if err := sp.RowProof.Validate(root); err != nil {
return err
}
if ok := sp.VerifyProof(); !ok {
return errors.New("share proof failed to verify")
}
return nil
}
func (sp shareProof) VerifyProof() bool {
cursor := int32(0)
for i, proof := range sp.ShareProofs {
nmtProof := nmt.NewInclusionProof(
int(proof.Start),
int(proof.End),
proof.Nodes,
true,
)
sharesUsed := proof.End - proof.Start
if sp.NamespaceVersion > math.MaxUint8 {
return false
}
// Consider extracting celestia-app's namespace package. We can't use it
// here because that would introduce a circulcar import.
namespace := append([]byte{uint8(sp.NamespaceVersion)}, sp.NamespaceID...)
valid := nmtProof.VerifyInclusion(
sha256.New(),
namespace,
sp.Data[cursor:sharesUsed+cursor],
sp.RowProof.RowRoots[i],
)
if !valid {
return false
}
cursor += sharesUsed
}
return true
}