-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathproof.go
128 lines (115 loc) · 3.64 KB
/
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
package unixfsproof
import (
"bufio"
"bytes"
"context"
"fmt"
"io"
bsrv "github.com/ipfs/go-blockservice"
"github.com/ipfs/go-cid"
"github.com/ipfs/go-datastore"
dssync "github.com/ipfs/go-datastore/sync"
blockstore "github.com/ipfs/go-ipfs-blockstore"
offline "github.com/ipfs/go-ipfs-exchange-offline"
ipld "github.com/ipfs/go-ipld-format"
dag "github.com/ipfs/go-merkledag"
"github.com/ipfs/go-unixfs"
car "github.com/ipld/go-car"
"github.com/ipld/go-car/util"
)
// Verify validates a proof for a Cid at a specified offset. If the proof is valid, the return
// parameter is true. If the proof is invalid false is returned. In any other case an error is returned.
func Verify(ctx context.Context, root cid.Cid, offset uint64, proof []byte) (bool, error) {
r := bufio.NewReader(bytes.NewReader(proof))
bstore := blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))
cr, err := car.NewCarReader(r)
if err != nil {
return false, fmt.Errorf("invalid carv1: %s", err)
}
if len(cr.Header.Roots) != 1 {
return false, fmt.Errorf("root list should have exactly one element")
}
if cr.Header.Roots[0] != root {
return false, fmt.Errorf("the root isn't the expected one")
}
// TODO: if we assume some ordering in the CAR file we could simply have a CAR-serial walker
// which would make this much faster and probably simpler in a way avoiding blockstores, etc.
// For now, not have those assumptions and do a naive-ish walk.
for {
block, err := cr.Next()
if err == io.EOF {
break
}
if err := bstore.Put(block); err != nil {
return false, fmt.Errorf("adding block to blockstore: %s", err)
}
}
dserv := dag.NewDAGService(bsrv.New(bstore, offline.Exchange(bstore)))
// Smart (or lazy?) way to verify the proof. If we assume an ideal full DAGStore, trying to
// re-create the proof with the proof as the underlying blockstore should fail if something
// is missing.
regenProof, err := Prove(ctx, root, offset, dserv)
if err != nil {
return false, nil
}
return bytes.Equal(proof, regenProof), nil
}
// Prove creates a proof for a Cid at a specified file offset.
func Prove(ctx context.Context, root cid.Cid, offset uint64, dserv ipld.DAGService) ([]byte, error) {
n, err := dserv.Get(ctx, root)
if err != nil {
return nil, fmt.Errorf("get %s from dag service: %s", root, err)
}
fsRoot, err := unixfs.ExtractFSNode(n)
if err != nil {
return nil, fmt.Errorf("extracting fsnode from merkle-dag: %s", err)
}
if fsRoot.FileSize() < offset {
return nil, fmt.Errorf("the offset is greater than the file size")
}
proofNodes := []ipld.Node{n}
var currOffset uint64
for n != nil {
var next ipld.Node
for _, child := range n.Links() {
cn, err := child.GetNode(ctx, dserv)
if err != nil {
return nil, fmt.Errorf("get child %s: %s", child.Cid, err)
}
proofNodes = append(proofNodes, cn)
// REMOVE THIS?
if next == nil {
fsNode, err := unixfs.ExtractFSNode(n)
if err != nil {
return nil, fmt.Errorf("extracting fsnode from merkle-dag: %s", err)
}
if currOffset+fsNode.FileSize() >= offset {
next = cn
break
} else {
currOffset += fsNode.FileSize()
}
}
}
n = next
}
var buf bytes.Buffer
h := &car.CarHeader{
Roots: []cid.Cid{root},
Version: 1,
}
if err := car.WriteHeader(h, &buf); err != nil {
return nil, fmt.Errorf("writing car header: %s", err)
}
seen := cid.NewSet()
for i := 0; i < len(proofNodes); i++ {
n := proofNodes[i]
if !seen.Visit(n.Cid()) {
continue
}
if err := util.LdWrite(&buf, n.Cid().Bytes(), n.RawData()); err != nil {
return nil, fmt.Errorf("encoding car block: %s", err)
}
}
return buf.Bytes(), nil
}