@atproto/repo 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bench/mst.bench.ts +7 -4
- package/bench/repo.bench.ts +25 -16
- package/dist/block-map.d.ts +25 -0
- package/dist/data-diff.d.ts +36 -0
- package/dist/error.d.ts +20 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +11605 -10399
- package/dist/index.js.map +4 -4
- package/dist/mst/diff.d.ts +4 -33
- package/dist/mst/mst.d.ts +68 -25
- package/dist/mst/util.d.ts +13 -5
- package/dist/parse.d.ts +16 -0
- package/dist/readable-repo.d.ts +22 -0
- package/dist/repo.d.ts +14 -30
- package/dist/storage/index.d.ts +4 -0
- package/dist/storage/memory-blockstore.d.ts +28 -0
- package/dist/storage/readable-blockstore.d.ts +24 -0
- package/dist/storage/repo-storage.d.ts +18 -0
- package/dist/storage/sync-storage.d.ts +15 -0
- package/dist/storage/types.d.ts +3 -0
- package/dist/sync/consumer.d.ts +18 -0
- package/dist/sync/index.d.ts +2 -0
- package/dist/sync/provider.d.ts +9 -0
- package/dist/types.d.ts +124 -317
- package/dist/util.d.ts +31 -12
- package/dist/verify.d.ts +26 -4
- package/package.json +4 -2
- package/src/block-map.ts +95 -0
- package/src/cid-set.ts +1 -2
- package/src/data-diff.ts +121 -0
- package/src/error.ts +31 -0
- package/src/index.ts +3 -1
- package/src/mst/diff.ts +120 -90
- package/src/mst/mst.ts +185 -184
- package/src/mst/util.ts +54 -31
- package/src/parse.ts +44 -0
- package/src/readable-repo.ts +75 -0
- package/src/repo.ts +119 -249
- package/src/storage/index.ts +4 -0
- package/src/storage/memory-blockstore.ts +114 -0
- package/src/storage/readable-blockstore.ts +56 -0
- package/src/storage/repo-storage.ts +42 -0
- package/src/storage/sync-storage.ts +35 -0
- package/src/storage/types.ts +3 -0
- package/src/sync/consumer.ts +137 -0
- package/src/sync/index.ts +2 -0
- package/src/sync/provider.ts +91 -0
- package/src/types.ts +101 -62
- package/src/util.ts +237 -56
- package/src/verify.ts +207 -42
- package/tests/_util.ts +132 -97
- package/tests/mst.test.ts +269 -122
- package/tests/repo.test.ts +48 -50
- package/tests/sync/checkout.test.ts +57 -0
- package/tests/sync/diff.test.ts +87 -0
- package/tests/sync/narrow.test.ts +145 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/tsconfig.json +2 -1
- package/src/blockstore/index.ts +0 -2
- package/src/blockstore/ipld-store.ts +0 -103
- package/src/blockstore/memory-blockstore.ts +0 -49
- package/src/sync.ts +0 -38
- package/tests/sync.test.ts +0 -129
package/src/util.ts
CHANGED
|
@@ -1,88 +1,269 @@
|
|
|
1
1
|
import { CID } from 'multiformats/cid'
|
|
2
|
-
import * as
|
|
2
|
+
import * as cbor from '@ipld/dag-cbor'
|
|
3
|
+
import { CarReader } from '@ipld/car/reader'
|
|
4
|
+
import { BlockWriter, CarWriter } from '@ipld/car/writer'
|
|
5
|
+
import { Block as CarBlock } from '@ipld/car/api'
|
|
6
|
+
import {
|
|
7
|
+
streamToArray,
|
|
8
|
+
verifyCidForBytes,
|
|
9
|
+
cborDecode,
|
|
10
|
+
check,
|
|
11
|
+
schema,
|
|
12
|
+
cidForCbor,
|
|
13
|
+
} from '@atproto/common'
|
|
14
|
+
import { ipldToLex, lexToIpld, LexValue, RepoRecord } from '@atproto/lexicon'
|
|
15
|
+
|
|
16
|
+
import * as crypto from '@atproto/crypto'
|
|
3
17
|
import Repo from './repo'
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
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
|
-
|
|
18
|
+
import { MST } from './mst'
|
|
19
|
+
import DataDiff from './data-diff'
|
|
20
|
+
import { RepoStorage } from './storage'
|
|
21
|
+
import {
|
|
22
|
+
Commit,
|
|
23
|
+
DataStore,
|
|
24
|
+
RecordCreateDescript,
|
|
25
|
+
RecordDeleteDescript,
|
|
26
|
+
RecordPath,
|
|
27
|
+
RecordUpdateDescript,
|
|
28
|
+
RecordWriteDescript,
|
|
29
|
+
UnsignedCommit,
|
|
30
|
+
WriteLog,
|
|
31
|
+
WriteOpAction,
|
|
32
|
+
} from './types'
|
|
33
|
+
import BlockMap from './block-map'
|
|
34
|
+
import { MissingBlocksError } from './error'
|
|
35
|
+
import * as parse from './parse'
|
|
36
|
+
import { Keypair } from '@atproto/crypto'
|
|
37
|
+
|
|
38
|
+
export async function* verifyIncomingCarBlocks(
|
|
39
|
+
car: AsyncIterable<CarBlock>,
|
|
40
|
+
): AsyncIterable<CarBlock> {
|
|
41
|
+
for await (const block of car) {
|
|
42
|
+
await verifyCidForBytes(block.cid, block.bytes)
|
|
43
|
+
yield block
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const writeCar = async (
|
|
48
|
+
root: CID | null,
|
|
49
|
+
fn: (car: BlockWriter) => Promise<void>,
|
|
50
|
+
): Promise<Uint8Array> => {
|
|
51
|
+
const { writer, out } =
|
|
52
|
+
root !== null ? CarWriter.create(root) : CarWriter.create()
|
|
53
|
+
const bytes = streamToArray(out)
|
|
54
|
+
try {
|
|
55
|
+
await fn(writer)
|
|
56
|
+
} finally {
|
|
57
|
+
writer.close()
|
|
58
|
+
}
|
|
59
|
+
return bytes
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const blocksToCar = async (
|
|
63
|
+
root: CID | null,
|
|
64
|
+
blocks: BlockMap,
|
|
65
|
+
): Promise<Uint8Array> => {
|
|
66
|
+
return writeCar(root, async (writer) => {
|
|
67
|
+
for (const entry of blocks.entries()) {
|
|
68
|
+
await writer.put(entry)
|
|
36
69
|
}
|
|
37
|
-
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const readCar = async (
|
|
74
|
+
bytes: Uint8Array,
|
|
75
|
+
): Promise<{ roots: CID[]; blocks: BlockMap }> => {
|
|
76
|
+
const car = await CarReader.fromBytes(bytes)
|
|
77
|
+
const roots = await car.getRoots()
|
|
78
|
+
const blocks = new BlockMap()
|
|
79
|
+
for await (const block of verifyIncomingCarBlocks(car.blocks())) {
|
|
80
|
+
await blocks.set(block.cid, block.bytes)
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
roots,
|
|
84
|
+
blocks,
|
|
38
85
|
}
|
|
39
|
-
return null
|
|
40
86
|
}
|
|
41
87
|
|
|
42
|
-
export const
|
|
43
|
-
|
|
44
|
-
|
|
88
|
+
export const readCarWithRoot = async (
|
|
89
|
+
bytes: Uint8Array,
|
|
90
|
+
): Promise<{ root: CID; blocks: BlockMap }> => {
|
|
91
|
+
const { roots, blocks } = await readCar(bytes)
|
|
92
|
+
if (roots.length !== 1) {
|
|
93
|
+
throw new Error(`Expected one root, got ${roots.length}`)
|
|
94
|
+
}
|
|
95
|
+
const root = roots[0]
|
|
96
|
+
return {
|
|
97
|
+
root,
|
|
98
|
+
blocks,
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const getWriteLog = async (
|
|
103
|
+
storage: RepoStorage,
|
|
45
104
|
latest: CID,
|
|
46
|
-
|
|
47
|
-
|
|
105
|
+
earliest: CID | null,
|
|
106
|
+
): Promise<WriteLog> => {
|
|
107
|
+
const commits = await storage.getCommitPath(latest, earliest)
|
|
48
108
|
if (!commits) throw new Error('Could not find shared history')
|
|
49
|
-
const heads = await Promise.all(commits.map((c) => Repo.load(
|
|
109
|
+
const heads = await Promise.all(commits.map((c) => Repo.load(storage, c)))
|
|
50
110
|
// Turn commit path into list of diffs
|
|
51
|
-
let prev: DataStore = await MST.create(
|
|
111
|
+
let prev: DataStore = await MST.create(storage) // Empty
|
|
52
112
|
const msts = heads.map((h) => h.data)
|
|
53
113
|
const diffs: DataDiff[] = []
|
|
54
114
|
for (const mst of msts) {
|
|
55
|
-
diffs.push(await
|
|
115
|
+
diffs.push(await DataDiff.of(mst, prev))
|
|
56
116
|
prev = mst
|
|
57
117
|
}
|
|
118
|
+
const fullDiff = collapseDiffs(diffs)
|
|
119
|
+
const diffBlocks = await storage.getBlocks(fullDiff.newCidList())
|
|
120
|
+
if (diffBlocks.missing.length > 0) {
|
|
121
|
+
throw new MissingBlocksError('write op log', diffBlocks.missing)
|
|
122
|
+
}
|
|
58
123
|
// Map MST diffs to write ops
|
|
59
|
-
return Promise.all(
|
|
124
|
+
return Promise.all(
|
|
125
|
+
diffs.map((diff) => diffToWriteDescripts(diff, diffBlocks.blocks)),
|
|
126
|
+
)
|
|
60
127
|
}
|
|
61
128
|
|
|
62
|
-
export const
|
|
63
|
-
blockstore: IpldStore,
|
|
129
|
+
export const diffToWriteDescripts = (
|
|
64
130
|
diff: DataDiff,
|
|
65
|
-
|
|
131
|
+
blocks: BlockMap,
|
|
132
|
+
): Promise<RecordWriteDescript[]> => {
|
|
66
133
|
return Promise.all([
|
|
67
134
|
...diff.addList().map(async (add) => {
|
|
68
|
-
const { collection, rkey } =
|
|
69
|
-
const value = await
|
|
70
|
-
return {
|
|
135
|
+
const { collection, rkey } = parseDataKey(add.key)
|
|
136
|
+
const value = await parse.getAndParseRecord(blocks, add.cid)
|
|
137
|
+
return {
|
|
138
|
+
action: WriteOpAction.Create,
|
|
139
|
+
collection,
|
|
140
|
+
rkey,
|
|
141
|
+
cid: add.cid,
|
|
142
|
+
record: value.record,
|
|
143
|
+
} as RecordCreateDescript
|
|
71
144
|
}),
|
|
72
145
|
...diff.updateList().map(async (upd) => {
|
|
73
|
-
const { collection, rkey } =
|
|
74
|
-
const value = await
|
|
75
|
-
return {
|
|
146
|
+
const { collection, rkey } = parseDataKey(upd.key)
|
|
147
|
+
const value = await parse.getAndParseRecord(blocks, upd.cid)
|
|
148
|
+
return {
|
|
149
|
+
action: WriteOpAction.Update,
|
|
150
|
+
collection,
|
|
151
|
+
rkey,
|
|
152
|
+
cid: upd.cid,
|
|
153
|
+
prev: upd.prev,
|
|
154
|
+
record: value.record,
|
|
155
|
+
} as RecordUpdateDescript
|
|
76
156
|
}),
|
|
77
157
|
...diff.deleteList().map((del) => {
|
|
78
|
-
const { collection, rkey } =
|
|
79
|
-
return {
|
|
158
|
+
const { collection, rkey } = parseDataKey(del.key)
|
|
159
|
+
return {
|
|
160
|
+
action: WriteOpAction.Delete,
|
|
161
|
+
collection,
|
|
162
|
+
rkey,
|
|
163
|
+
cid: del.cid,
|
|
164
|
+
} as RecordDeleteDescript
|
|
80
165
|
}),
|
|
81
166
|
])
|
|
82
167
|
}
|
|
83
168
|
|
|
84
|
-
export const
|
|
169
|
+
export const collapseWriteLog = (log: WriteLog): RecordWriteDescript[] => {
|
|
170
|
+
const creates: Record<string, RecordCreateDescript> = {}
|
|
171
|
+
const updates: Record<string, RecordUpdateDescript> = {}
|
|
172
|
+
const deletes: Record<string, RecordDeleteDescript> = {}
|
|
173
|
+
for (const commit of log) {
|
|
174
|
+
for (const op of commit) {
|
|
175
|
+
const key = op.collection + '/' + op.rkey
|
|
176
|
+
if (op.action === WriteOpAction.Create) {
|
|
177
|
+
const del = deletes[key]
|
|
178
|
+
if (del) {
|
|
179
|
+
if (del.cid !== op.cid) {
|
|
180
|
+
updates[key] = {
|
|
181
|
+
...op,
|
|
182
|
+
action: WriteOpAction.Update,
|
|
183
|
+
prev: del.cid,
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
delete deletes[key]
|
|
187
|
+
} else {
|
|
188
|
+
creates[key] = op
|
|
189
|
+
}
|
|
190
|
+
} else if (op.action === WriteOpAction.Update) {
|
|
191
|
+
updates[key] = op
|
|
192
|
+
delete creates[key]
|
|
193
|
+
delete deletes[key]
|
|
194
|
+
} else if (op.action === WriteOpAction.Delete) {
|
|
195
|
+
if (creates[key]) {
|
|
196
|
+
delete creates[key]
|
|
197
|
+
} else {
|
|
198
|
+
delete updates[key]
|
|
199
|
+
deletes[key] = op
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
throw new Error(`unknown action: ${op}`)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return [
|
|
207
|
+
...Object.values(creates),
|
|
208
|
+
...Object.values(updates),
|
|
209
|
+
...Object.values(deletes),
|
|
210
|
+
]
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export const collapseDiffs = (diffs: DataDiff[]): DataDiff => {
|
|
214
|
+
return diffs.reduce((acc, cur) => {
|
|
215
|
+
acc.addDiff(cur)
|
|
216
|
+
return acc
|
|
217
|
+
}, new DataDiff())
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export const parseDataKey = (key: string): RecordPath => {
|
|
85
221
|
const parts = key.split('/')
|
|
86
222
|
if (parts.length !== 2) throw new Error(`Invalid record key: ${key}`)
|
|
87
223
|
return { collection: parts[0], rkey: parts[1] }
|
|
88
224
|
}
|
|
225
|
+
|
|
226
|
+
export const formatDataKey = (collection: string, rkey: string): string => {
|
|
227
|
+
return collection + '/' + rkey
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export const metaEqual = (a: Commit, b: Commit): boolean => {
|
|
231
|
+
return a.did === b.did && a.version === b.version
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export const signCommit = async (
|
|
235
|
+
unsigned: UnsignedCommit,
|
|
236
|
+
keypair: Keypair,
|
|
237
|
+
): Promise<Commit> => {
|
|
238
|
+
const encoded = cbor.encode(unsigned)
|
|
239
|
+
const sig = await keypair.sign(encoded)
|
|
240
|
+
return {
|
|
241
|
+
...unsigned,
|
|
242
|
+
sig,
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export const verifyCommitSig = async (
|
|
247
|
+
commit: Commit,
|
|
248
|
+
didKey: string,
|
|
249
|
+
): Promise<boolean> => {
|
|
250
|
+
const { sig, ...rest } = commit
|
|
251
|
+
const encoded = cbor.encode(rest)
|
|
252
|
+
return crypto.verifySignature(didKey, encoded, sig)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export const cborToLex = (val: Uint8Array): LexValue => {
|
|
256
|
+
return ipldToLex(cborDecode(val))
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export const cborToLexRecord = (val: Uint8Array): RepoRecord => {
|
|
260
|
+
const parsed = cborToLex(val)
|
|
261
|
+
if (!check.is(parsed, schema.map)) {
|
|
262
|
+
throw new Error('lexicon records be a json object')
|
|
263
|
+
}
|
|
264
|
+
return parsed
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export const cidForRecord = async (val: LexValue) => {
|
|
268
|
+
return cidForCbor(lexToIpld(val))
|
|
269
|
+
}
|
package/src/verify.ts
CHANGED
|
@@ -1,62 +1,227 @@
|
|
|
1
1
|
import { CID } from 'multiformats/cid'
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import { MemoryBlockstore, ReadableBlockstore, RepoStorage } from './storage'
|
|
3
|
+
import DataDiff from './data-diff'
|
|
4
|
+
import SyncStorage from './storage/sync-storage'
|
|
5
|
+
import ReadableRepo from './readable-repo'
|
|
4
6
|
import Repo from './repo'
|
|
5
|
-
import
|
|
7
|
+
import CidSet from './cid-set'
|
|
6
8
|
import * as util from './util'
|
|
9
|
+
import { RecordClaim, RepoContents } from './types'
|
|
7
10
|
import { def } from './types'
|
|
11
|
+
import { MST } from './mst'
|
|
12
|
+
import { cidForCbor } from '@atproto/common'
|
|
13
|
+
|
|
14
|
+
export type VerifiedCheckout = {
|
|
15
|
+
contents: RepoContents
|
|
16
|
+
newCids: CidSet
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const verifyCheckout = async (
|
|
20
|
+
storage: ReadableBlockstore,
|
|
21
|
+
head: CID,
|
|
22
|
+
did: string,
|
|
23
|
+
signingKey: string,
|
|
24
|
+
): Promise<VerifiedCheckout> => {
|
|
25
|
+
const repo = await ReadableRepo.load(storage, head)
|
|
26
|
+
if (repo.did !== did) {
|
|
27
|
+
throw new RepoVerificationError(`Invalid repo did: ${repo.did}`)
|
|
28
|
+
}
|
|
29
|
+
const validSig = await util.verifyCommitSig(repo.commit, signingKey)
|
|
30
|
+
if (!validSig) {
|
|
31
|
+
throw new RepoVerificationError(
|
|
32
|
+
`Invalid signature on commit: ${repo.cid.toString()}`,
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
const diff = await DataDiff.of(repo.data, null)
|
|
36
|
+
const newCids = new CidSet([repo.cid]).addSet(diff.newCids)
|
|
37
|
+
|
|
38
|
+
const contents: RepoContents = {}
|
|
39
|
+
for (const add of diff.addList()) {
|
|
40
|
+
const { collection, rkey } = util.parseDataKey(add.key)
|
|
41
|
+
if (!contents[collection]) {
|
|
42
|
+
contents[collection] = {}
|
|
43
|
+
}
|
|
44
|
+
const record = await storage.readRecord(add.cid)
|
|
45
|
+
contents[collection][rkey] = record
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
contents,
|
|
50
|
+
newCids,
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type VerifiedUpdate = {
|
|
55
|
+
commit: CID
|
|
56
|
+
prev: CID | null
|
|
57
|
+
diff: DataDiff
|
|
58
|
+
newCids: CidSet
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const verifyFullHistory = async (
|
|
62
|
+
storage: RepoStorage,
|
|
63
|
+
head: CID,
|
|
64
|
+
did: string,
|
|
65
|
+
signingKey: string,
|
|
66
|
+
): Promise<VerifiedUpdate[]> => {
|
|
67
|
+
const commitPath = await storage.getCommitPath(head, null)
|
|
68
|
+
if (commitPath === null) {
|
|
69
|
+
throw new RepoVerificationError('Could not find shared history')
|
|
70
|
+
} else if (commitPath.length < 1) {
|
|
71
|
+
throw new RepoVerificationError('Expected at least one commit')
|
|
72
|
+
}
|
|
73
|
+
const baseRepo = await Repo.load(storage, commitPath[0])
|
|
74
|
+
const baseDiff = await DataDiff.of(baseRepo.data, null)
|
|
75
|
+
const baseRepoCids = new CidSet([baseRepo.cid]).addSet(baseDiff.newCids)
|
|
76
|
+
const init: VerifiedUpdate = {
|
|
77
|
+
commit: baseRepo.cid,
|
|
78
|
+
prev: null,
|
|
79
|
+
diff: baseDiff,
|
|
80
|
+
newCids: baseRepoCids,
|
|
81
|
+
}
|
|
82
|
+
const updates = await verifyCommitPath(
|
|
83
|
+
baseRepo,
|
|
84
|
+
storage,
|
|
85
|
+
commitPath.slice(1),
|
|
86
|
+
did,
|
|
87
|
+
signingKey,
|
|
88
|
+
)
|
|
89
|
+
return [init, ...updates]
|
|
90
|
+
}
|
|
8
91
|
|
|
9
92
|
export const verifyUpdates = async (
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
93
|
+
repo: ReadableRepo,
|
|
94
|
+
updateStorage: RepoStorage,
|
|
95
|
+
updateRoot: CID,
|
|
96
|
+
did: string,
|
|
97
|
+
signingKey: string,
|
|
98
|
+
): Promise<VerifiedUpdate[]> => {
|
|
99
|
+
const commitPath = await updateStorage.getCommitPath(updateRoot, repo.cid)
|
|
16
100
|
if (commitPath === null) {
|
|
17
|
-
throw new
|
|
101
|
+
throw new RepoVerificationError('Could not find shared history')
|
|
18
102
|
}
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
for (const commit of commitPath.slice(1)) {
|
|
23
|
-
const nextRepo = await Repo.load(blockstore, commit)
|
|
24
|
-
const diff = await prevRepo.data.diff(nextRepo.data)
|
|
103
|
+
const syncStorage = new SyncStorage(updateStorage, repo.storage)
|
|
104
|
+
return verifyCommitPath(repo, syncStorage, commitPath, did, signingKey)
|
|
105
|
+
}
|
|
25
106
|
|
|
26
|
-
|
|
27
|
-
|
|
107
|
+
export const verifyCommitPath = async (
|
|
108
|
+
baseRepo: ReadableRepo,
|
|
109
|
+
storage: ReadableBlockstore,
|
|
110
|
+
commitPath: CID[],
|
|
111
|
+
did: string,
|
|
112
|
+
signingKey: string,
|
|
113
|
+
): Promise<VerifiedUpdate[]> => {
|
|
114
|
+
const updates: VerifiedUpdate[] = []
|
|
115
|
+
if (commitPath.length === 0) return updates
|
|
116
|
+
let prevRepo = baseRepo
|
|
117
|
+
for (const commit of commitPath) {
|
|
118
|
+
const nextRepo = await ReadableRepo.load(storage, commit)
|
|
119
|
+
const diff = await DataDiff.of(nextRepo.data, prevRepo.data)
|
|
120
|
+
|
|
121
|
+
if (nextRepo.did !== did) {
|
|
122
|
+
throw new RepoVerificationError(`Invalid repo did: ${nextRepo.did}`)
|
|
123
|
+
}
|
|
124
|
+
if (!util.metaEqual(nextRepo.commit, prevRepo.commit)) {
|
|
125
|
+
throw new RepoVerificationError('Not supported: repo metadata updated')
|
|
28
126
|
}
|
|
29
127
|
|
|
30
|
-
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
nextRepo.root.auth_token,
|
|
35
|
-
def.string,
|
|
128
|
+
const validSig = await util.verifyCommitSig(nextRepo.commit, signingKey)
|
|
129
|
+
if (!validSig) {
|
|
130
|
+
throw new RepoVerificationError(
|
|
131
|
+
`Invalid signature on commit: ${nextRepo.cid.toString()}`,
|
|
36
132
|
)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const newCids = new CidSet([nextRepo.cid]).addSet(diff.newCids)
|
|
136
|
+
|
|
137
|
+
updates.push({
|
|
138
|
+
commit: nextRepo.cid,
|
|
139
|
+
prev: prevRepo.cid,
|
|
140
|
+
diff,
|
|
141
|
+
newCids,
|
|
142
|
+
})
|
|
143
|
+
prevRepo = nextRepo
|
|
144
|
+
}
|
|
145
|
+
return updates
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export const verifyProofs = async (
|
|
149
|
+
proofs: Uint8Array,
|
|
150
|
+
claims: RecordClaim[],
|
|
151
|
+
did: string,
|
|
152
|
+
didKey: string,
|
|
153
|
+
): Promise<{ verified: RecordClaim[]; unverified: RecordClaim[] }> => {
|
|
154
|
+
const car = await util.readCarWithRoot(proofs)
|
|
155
|
+
const blockstore = new MemoryBlockstore(car.blocks)
|
|
156
|
+
const commit = await blockstore.readObj(car.root, def.commit)
|
|
157
|
+
if (commit.did !== did) {
|
|
158
|
+
throw new RepoVerificationError(`Invalid repo did: ${commit.did}`)
|
|
159
|
+
}
|
|
160
|
+
const validSig = await util.verifyCommitSig(commit, didKey)
|
|
161
|
+
if (!validSig) {
|
|
162
|
+
throw new RepoVerificationError(
|
|
163
|
+
`Invalid signature on commit: ${car.root.toString()}`,
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
const mst = MST.load(blockstore, commit.data)
|
|
167
|
+
const verified: RecordClaim[] = []
|
|
168
|
+
const unverified: RecordClaim[] = []
|
|
169
|
+
for (const claim of claims) {
|
|
170
|
+
const found = await mst.get(
|
|
171
|
+
util.formatDataKey(claim.collection, claim.rkey),
|
|
172
|
+
)
|
|
173
|
+
const record = found ? await blockstore.readObj(found, def.map) : null
|
|
174
|
+
if (claim.record === null) {
|
|
175
|
+
if (record === null) {
|
|
176
|
+
verified.push(claim)
|
|
177
|
+
} else {
|
|
178
|
+
unverified.push(claim)
|
|
41
179
|
}
|
|
42
|
-
didForSignature = token.payload.iss
|
|
43
180
|
} else {
|
|
44
|
-
|
|
181
|
+
const expected = await cidForCbor(claim.record)
|
|
182
|
+
if (expected.equals(found)) {
|
|
183
|
+
verified.push(claim)
|
|
184
|
+
} else {
|
|
185
|
+
unverified.push(claim)
|
|
186
|
+
}
|
|
45
187
|
}
|
|
188
|
+
}
|
|
189
|
+
return { verified, unverified }
|
|
190
|
+
}
|
|
46
191
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
192
|
+
export const verifyRecords = async (
|
|
193
|
+
proofs: Uint8Array,
|
|
194
|
+
did: string,
|
|
195
|
+
signingKey: string,
|
|
196
|
+
): Promise<RecordClaim[]> => {
|
|
197
|
+
const car = await util.readCarWithRoot(proofs)
|
|
198
|
+
const blockstore = new MemoryBlockstore(car.blocks)
|
|
199
|
+
const commit = await blockstore.readObj(car.root, def.commit)
|
|
200
|
+
if (commit.did !== did) {
|
|
201
|
+
throw new RepoVerificationError(`Invalid repo did: ${commit.did}`)
|
|
202
|
+
}
|
|
203
|
+
const validSig = await util.verifyCommitSig(commit, signingKey)
|
|
204
|
+
if (!validSig) {
|
|
205
|
+
throw new RepoVerificationError(
|
|
206
|
+
`Invalid signature on commit: ${car.root.toString()}`,
|
|
53
207
|
)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
208
|
+
}
|
|
209
|
+
const mst = MST.load(blockstore, commit.data)
|
|
57
210
|
|
|
58
|
-
|
|
59
|
-
|
|
211
|
+
const records: RecordClaim[] = []
|
|
212
|
+
const leaves = await mst.reachableLeaves()
|
|
213
|
+
for (const leaf of leaves) {
|
|
214
|
+
const { collection, rkey } = util.parseDataKey(leaf.key)
|
|
215
|
+
const record = await blockstore.attemptReadRecord(leaf.value)
|
|
216
|
+
if (record) {
|
|
217
|
+
records.push({
|
|
218
|
+
collection,
|
|
219
|
+
rkey,
|
|
220
|
+
record,
|
|
221
|
+
})
|
|
222
|
+
}
|
|
60
223
|
}
|
|
61
|
-
return
|
|
224
|
+
return records
|
|
62
225
|
}
|
|
226
|
+
|
|
227
|
+
export class RepoVerificationError extends Error {}
|