chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
import { describe, expect, it } from 'vitest';
import { RepoCommit, verifyCommit } from '../src/commit.js';
import { fromHex, referenceCommit, vectors } from './helpers/vectors.js';
const { records } = vectors.commit;
const secret = fromHex(vectors.commit.secretHex);
const mac = fromHex(vectors.commit.macHex);
const params = {
space: vectors.commit.space,
repo: vectors.commit.repo,
sharedSecret: secret,
mac,
};
describe('RepoCommit', () => {
it('reproduces the reference commit from the same records', async () => {
const commit = await RepoCommit.fromRecords(records).toCommit({
space: vectors.commit.space,
repo: vectors.commit.repo,
rev: vectors.commit.rev,
});
expect(commit).toEqual(referenceCommit());
});
it('matches() compares against a commit', async () => {
expect(
await RepoCommit.fromRecords(records).matches(referenceCommit()),
).toBe(true);
expect(await new RepoCommit().matches(referenceCommit())).toBe(false);
});
it('fromState round-trips', async () => {
const a = RepoCommit.fromRecords(records);
const b = RepoCommit.fromState(a.state());
expect(await b.matches(referenceCommit())).toBe(true);
});
it('fromState treats null as an empty repo', () => {
expect(RepoCommit.fromState(null).setHash.isEmpty()).toBe(true);
});
it('applyOp handles create, update, and delete', async () => {
const [a, b] = records;
const repo = new RepoCommit();
repo.applyOp({ ...a, prev: null });
repo.applyOp({ ...b, prev: null });
expect(await repo.matches(referenceCommit())).toBe(true);
repo.applyOp({ ...a, cid: b.cid, prev: a.cid });
expect(await repo.matches(referenceCommit())).toBe(false);
repo.applyOps([
{ ...a, cid: a.cid, prev: b.cid },
{ ...b, cid: null, prev: b.cid },
]);
expect(await repo.matches(referenceCommit())).toBe(false);
repo.applyOp({ ...b, prev: null });
expect(await repo.matches(referenceCommit())).toBe(true);
});
});
describe('verifyCommit', () => {
it('accepts the reference commit under its MAC', async () => {
expect(await verifyCommit(referenceCommit(), params)).toBe(true);
});
it('rejects a wrong version', async () => {
expect(await verifyCommit({ ...referenceCommit(), ver: 2 }, params)).toBe(
false,
);
});
it('rejects a commit for another repo or space', async () => {
expect(
await verifyCommit(referenceCommit(), {
...params,
repo: 'did:plc:other',
}),
).toBe(false);
expect(
await verifyCommit(referenceCommit(), {
...params,
space: 'at://did:plc:aaaa/space/com.example.type/key2',
}),
).toBe(false);
});
it('rejects tampered data, rev or MAC', async () => {
const data = referenceCommit().data.slice();
data[0] ^= 1;
expect(await verifyCommit({ ...referenceCommit(), data }, params)).toBe(
false,
);
expect(
await verifyCommit(
{ ...referenceCommit(), rev: '3lkabcdefgh2y' },
params,
),
).toBe(false);
const bad = mac.slice();
bad[31] ^= 1;
expect(await verifyCommit(referenceCommit(), { ...params, mac: bad })).toBe(
false,
);
});
it('rejects the right MAC under another secret', async () => {
expect(
await verifyCommit(referenceCommit(), {
...params,
sharedSecret: new Uint8Array(32),
}),
).toBe(false);
});
});