import { TID, cidForCbor, streamToBuffer } from '@atproto/common' import * as crypto from '@atproto/crypto' import { RecordCidClaim, RecordPath, Repo, RepoContents } from '../src' import { MemoryBlockstore } from '../src/storage' import * as sync from '../src/sync' import * as util from './_util' describe('Repo Proofs', () => { let storage: MemoryBlockstore let repo: Repo let keypair: crypto.Keypair let repoData: RepoContents const repoDid = 'did:example:test' beforeAll(async () => { storage = new MemoryBlockstore() keypair = await crypto.Secp256k1Keypair.create() repo = await Repo.create(storage, repoDid, keypair) const filled = await util.fillRepo(repo, keypair, 5) repo = filled.repo repoData = filled.data }) const getProofs = async (claims: RecordPath[]) => { return streamToBuffer(sync.getRecords(storage, repo.cid, claims)) } const doVerify = (proofs: Uint8Array, claims: RecordCidClaim[]) => { return sync.verifyProofs(proofs, claims, repoDid, keypair.did()) } const contentsToClaims = async ( contents: RepoContents, ): Promise => { const claims: RecordCidClaim[] = [] for (const coll of Object.keys(contents)) { for (const rkey of Object.keys(contents[coll])) { claims.push({ collection: coll, rkey: rkey, cid: await cidForCbor(contents[coll][rkey]), }) } } return claims } it('verifies valid records', async () => { const claims = await contentsToClaims(repoData) const proofs = await getProofs(claims) const results = await doVerify(proofs, claims) expect(results.verified.length).toBeGreaterThan(0) expect(results.verified).toEqual(claims) expect(results.unverified.length).toBe(0) }) it('verifies record nonexistence', async () => { const claims: RecordCidClaim[] = [ { collection: util.testCollections[0], rkey: TID.nextStr(), // does not exist cid: null, }, ] const proofs = await getProofs(claims) const results = await doVerify(proofs, claims) expect(results.verified.length).toBeGreaterThan(0) expect(results.verified).toEqual(claims) expect(results.unverified.length).toBe(0) }) it('does not verify a record that doesnt exist', async () => { const realClaims = await contentsToClaims(repoData) const claims: RecordCidClaim[] = [ { ...realClaims[0], rkey: TID.nextStr(), }, ] const proofs = await getProofs(claims) const results = await doVerify(proofs, claims) expect(results.verified.length).toBe(0) expect(results.unverified.length).toBeGreaterThan(0) expect(results.unverified).toEqual(claims) }) it('does not verify an invalid record at a real path', async () => { const realClaims = await contentsToClaims(repoData) const claims: RecordCidClaim[] = [ { ...realClaims[0], cid: await util.randomCid(), }, ] const proofs = await getProofs(claims) const results = await doVerify(proofs, claims) expect(results.verified.length).toBe(0) expect(results.unverified.length).toBeGreaterThan(0) expect(results.unverified).toEqual(claims) }) it('does not verify a delete where the record does exist', async () => { const realClaims = await contentsToClaims(repoData) const claims: RecordCidClaim[] = [ { collection: realClaims[0].collection, rkey: realClaims[0].rkey, cid: null, }, ] const proofs = await getProofs(claims) const results = await doVerify(proofs, claims) expect(results.verified.length).toBe(0) expect(results.unverified.length).toBeGreaterThan(0) expect(results.unverified).toEqual(claims) }) it('can determine record proofs from car file', async () => { const possible = await contentsToClaims(repoData) const claims = [ //random sampling of records possible[0], possible[4], possible[5], possible[8], ] const proofs = await getProofs(claims) const records = await sync.verifyRecords(proofs, repoDid, keypair.did()) for (const record of records) { const foundClaim = claims.find( (claim) => claim.collection === record.collection && claim.rkey === record.rkey, ) if (!foundClaim) { throw new Error('Could not find record for claim') } expect(foundClaim.cid).toEqual( await cidForCbor(repoData[record.collection][record.rkey]), ) } }) it('verifyProofs throws on a bad signature', async () => { const badRepo = await util.addBadCommit(repo, keypair) const claims = await contentsToClaims(repoData) const proofs = await streamToBuffer( sync.getRecords(storage, badRepo.cid, claims), ) const fn = sync.verifyProofs(proofs, claims, repoDid, keypair.did()) await expect(fn).rejects.toThrow(sync.RepoVerificationError) }) })