~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
113 lines · 3.4 KB Raw History
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);
  });
});