@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.
Files changed (63) hide show
  1. package/bench/mst.bench.ts +7 -4
  2. package/bench/repo.bench.ts +25 -16
  3. package/dist/block-map.d.ts +25 -0
  4. package/dist/data-diff.d.ts +36 -0
  5. package/dist/error.d.ts +20 -0
  6. package/dist/index.d.ts +3 -1
  7. package/dist/index.js +11605 -10399
  8. package/dist/index.js.map +4 -4
  9. package/dist/mst/diff.d.ts +4 -33
  10. package/dist/mst/mst.d.ts +68 -25
  11. package/dist/mst/util.d.ts +13 -5
  12. package/dist/parse.d.ts +16 -0
  13. package/dist/readable-repo.d.ts +22 -0
  14. package/dist/repo.d.ts +14 -30
  15. package/dist/storage/index.d.ts +4 -0
  16. package/dist/storage/memory-blockstore.d.ts +28 -0
  17. package/dist/storage/readable-blockstore.d.ts +24 -0
  18. package/dist/storage/repo-storage.d.ts +18 -0
  19. package/dist/storage/sync-storage.d.ts +15 -0
  20. package/dist/storage/types.d.ts +3 -0
  21. package/dist/sync/consumer.d.ts +18 -0
  22. package/dist/sync/index.d.ts +2 -0
  23. package/dist/sync/provider.d.ts +9 -0
  24. package/dist/types.d.ts +124 -317
  25. package/dist/util.d.ts +31 -12
  26. package/dist/verify.d.ts +26 -4
  27. package/package.json +4 -2
  28. package/src/block-map.ts +95 -0
  29. package/src/cid-set.ts +1 -2
  30. package/src/data-diff.ts +121 -0
  31. package/src/error.ts +31 -0
  32. package/src/index.ts +3 -1
  33. package/src/mst/diff.ts +120 -90
  34. package/src/mst/mst.ts +185 -184
  35. package/src/mst/util.ts +54 -31
  36. package/src/parse.ts +44 -0
  37. package/src/readable-repo.ts +75 -0
  38. package/src/repo.ts +119 -249
  39. package/src/storage/index.ts +4 -0
  40. package/src/storage/memory-blockstore.ts +114 -0
  41. package/src/storage/readable-blockstore.ts +56 -0
  42. package/src/storage/repo-storage.ts +42 -0
  43. package/src/storage/sync-storage.ts +35 -0
  44. package/src/storage/types.ts +3 -0
  45. package/src/sync/consumer.ts +137 -0
  46. package/src/sync/index.ts +2 -0
  47. package/src/sync/provider.ts +91 -0
  48. package/src/types.ts +101 -62
  49. package/src/util.ts +237 -56
  50. package/src/verify.ts +207 -42
  51. package/tests/_util.ts +132 -97
  52. package/tests/mst.test.ts +269 -122
  53. package/tests/repo.test.ts +48 -50
  54. package/tests/sync/checkout.test.ts +57 -0
  55. package/tests/sync/diff.test.ts +87 -0
  56. package/tests/sync/narrow.test.ts +145 -0
  57. package/tsconfig.build.tsbuildinfo +1 -1
  58. package/tsconfig.json +2 -1
  59. package/src/blockstore/index.ts +0 -2
  60. package/src/blockstore/ipld-store.ts +0 -103
  61. package/src/blockstore/memory-blockstore.ts +0 -49
  62. package/src/sync.ts +0 -38
  63. 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 auth from '@atproto/auth'
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 { DataDiff, MST } from './mst'
5
- import { IpldStore } from './blockstore'
6
- import { DataStore, RecordWriteOp, def } from './types'
7
-
8
- export const ucanForOperation = async (
9
- prevData: DataStore,
10
- newData: DataStore,
11
- rootDid: string,
12
- authStore: auth.AuthStore,
13
- ): Promise<string> => {
14
- const diff = await prevData.diff(newData)
15
- const neededCaps = diff.neededCapabilities(rootDid)
16
- const ucanForOp = await authStore.createUcanForCaps(rootDid, neededCaps, 30)
17
- return auth.encodeUcan(ucanForOp)
18
- }
19
-
20
- export const getCommitPath = async (
21
- blockstore: IpldStore,
22
- earliest: CID | null,
23
- latest: CID,
24
- ): Promise<CID[] | null> => {
25
- let curr: CID | null = latest
26
- const path: CID[] = []
27
- while (curr !== null) {
28
- path.push(curr)
29
- const commit = await blockstore.get(curr, def.commit)
30
- if (earliest && curr.equals(earliest)) {
31
- return path.reverse()
32
- }
33
- const root = await blockstore.get(commit.root, def.repoRoot)
34
- if (!earliest && root.prev === null) {
35
- return path.reverse()
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
- curr = root.prev
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 getWriteOpLog = async (
43
- blockstore: IpldStore,
44
- earliest: CID | null,
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
- ): Promise<RecordWriteOp[][]> => {
47
- const commits = await getCommitPath(blockstore, earliest, latest)
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(blockstore, c)))
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(blockstore) // Empty
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 prev.diff(mst))
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(diffs.map((diff) => diffToWriteOps(blockstore, diff)))
124
+ return Promise.all(
125
+ diffs.map((diff) => diffToWriteDescripts(diff, diffBlocks.blocks)),
126
+ )
60
127
  }
61
128
 
62
- export const diffToWriteOps = (
63
- blockstore: IpldStore,
129
+ export const diffToWriteDescripts = (
64
130
  diff: DataDiff,
65
- ): Promise<RecordWriteOp[]> => {
131
+ blocks: BlockMap,
132
+ ): Promise<RecordWriteDescript[]> => {
66
133
  return Promise.all([
67
134
  ...diff.addList().map(async (add) => {
68
- const { collection, rkey } = parseRecordKey(add.key)
69
- const value = await blockstore.getUnchecked(add.cid)
70
- return { action: 'create' as const, collection, rkey, value }
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 } = parseRecordKey(upd.key)
74
- const value = await blockstore.getUnchecked(upd.cid)
75
- return { action: 'update' as const, collection, rkey, value }
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 } = parseRecordKey(del.key)
79
- return { action: 'delete' as const, collection, rkey }
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 parseRecordKey = (key: string) => {
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 * as auth from '@atproto/auth'
3
- import { IpldStore } from './blockstore'
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 { DataDiff } from './mst'
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
- blockstore: IpldStore,
11
- earliest: CID | null,
12
- latest: CID,
13
- verifier: auth.Verifier,
14
- ): Promise<DataDiff> => {
15
- const commitPath = await util.getCommitPath(blockstore, earliest, latest)
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 Error('Could not find shared history')
101
+ throw new RepoVerificationError('Could not find shared history')
18
102
  }
19
- const fullDiff = new DataDiff()
20
- if (commitPath.length === 0) return fullDiff
21
- let prevRepo = await Repo.load(blockstore, commitPath[0])
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
- if (!nextRepo.root.meta.equals(prevRepo.root.meta)) {
27
- throw new Error('Not supported: repo metadata updated')
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
- let didForSignature: string
31
- if (nextRepo.root.auth_token) {
32
- // verify auth token covers all necessary writes
33
- const encodedToken = await blockstore.get(
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
- const token = await verifier.validateUcan(encodedToken)
38
- const neededCaps = diff.neededCapabilities(prevRepo.did)
39
- for (const cap of neededCaps) {
40
- await verifier.verifyAtpUcan(token, prevRepo.did, cap)
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
- didForSignature = prevRepo.did
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
- // verify signature matches repo root + auth token
48
- // const commit = await toRepo.getCommit()
49
- const validSig = await verifier.verifySignature(
50
- didForSignature,
51
- nextRepo.commit.root.bytes,
52
- nextRepo.commit.sig,
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
- if (!validSig) {
55
- throw new Error(`Invalid signature on commit: ${nextRepo.cid.toString()}`)
56
- }
208
+ }
209
+ const mst = MST.load(blockstore, commit.data)
57
210
 
58
- fullDiff.addDiff(diff)
59
- prevRepo = nextRepo
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 fullDiff
224
+ return records
62
225
  }
226
+
227
+ export class RepoVerificationError extends Error {}