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

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
177 lines · 5.5 KB Raw History
// Produces artifacts with the reference implementation for pds.js to verify.
// Copied into the atproto worktree by scripts/space-reference/run.sh as a
// scratch file; never committed there.

import { mkdirSync, writeFileSync } from 'node:fs';
import { P256Keypair } from '@atproto/crypto';
import { cidForLex, encode } from '@atproto/lex-cbor';
import { parseCid } from '@atproto/lex-data';
import { it } from 'vitest';
import {
  COMMIT_MAC_TYPE,
  computeCommitMac,
  computeRecordMac,
  createSpaceSigHeaders,
  createSpaceToken,
  encapsulate,
  formatMacHeader,
  formatWantMacHeader,
  RECORD_MAC_TYPE,
  RepoCommit,
  serializeRepo,
} from '../src/index.js';

const OUT = process.env.OUT_DIR ?? '/tmp/ref-space-artifacts';

const AUTHORITY = 'did:plc:refauthority';
const SPACE = `at://${AUTHORITY}/space/com.example.forum/general`;
const AUTHOR = 'did:plc:refuser';
const BLOB = 'bafkreigh2akiscaildcqabsyg3dfr6chu3fgpregiymsck7e7aqa4s52zy';

it('generates reference artifacts', async () => {
  mkdirSync(OUT, { recursive: true });

  const author = await P256Keypair.create({ exportable: true });
  const authorKem = await P256Keypair.create({ exportable: true });
  const authority = await P256Keypair.create({ exportable: true });

  // In strict path order, which the archive requires. `com.e.p/x` sorts
  // before `com.example.post/aaa` byte for byte.
  const inputs = [
    { collection: 'com.e.p', rkey: 'x', record: { $type: 'com.e.p', n: 42 } },
    {
      collection: 'com.example.post',
      rkey: 'aaa',
      record: { $type: 'com.example.post', text: 'first' },
    },
    {
      collection: 'com.example.post',
      rkey: 'bbb',
      record: { $type: 'com.example.post', text: 'second' },
    },
    {
      collection: 'com.example.post',
      rkey: 'ccc',
      record: {
        $type: 'com.example.post',
        text: 'with a picture',
        image: {
          $type: 'blob',
          ref: parseCid(BLOB),
          mimeType: 'image/png',
          size: 1234,
        },
      },
    },
    {
      collection: 'com.example.reply',
      rkey: 'zz',
      record: { $type: 'com.example.reply', deep: { nested: [1, 2, 3] } },
    },
  ];

  const records = await Promise.all(
    inputs.map(async (i) => ({
      collection: i.collection as any,
      rkey: i.rkey as any,
      cid: await cidForLex(i.record as any),
      bytes: encode(i.record as any),
    })),
  );

  const rev = '3mrxrefrevaaa';
  const commit = RepoCommit.fromRecords(records).toCommit({
    space: SPACE,
    repo: AUTHOR as any,
    rev,
  });

  // As the repo host would answer one syncer's getRepo: a MAC under the
  // secret of that syncer's key share. pds.js derives the secret itself from
  // the KEM key and the key share.
  const repoShare = await encapsulate(authorKem.did());
  const commitMac = computeCommitMac(repoShare.sharedSecret, commit);

  const chunks: Uint8Array[] = [];
  for await (const chunk of serializeRepo(commit, records)) chunks.push(chunk);
  writeFileSync(`${OUT}/repo.star`, Buffer.concat(chunks));

  const recordShare = await encapsulate(authorKem.did());
  const recordUri = `${SPACE}/${AUTHOR}/com.example.post/ccc`;
  const recordMac = computeRecordMac(
    recordShare.sharedSecret,
    recordUri,
    records[3].cid,
  );

  const delegationToken = await createSpaceToken(
    'delegation',
    { iss: AUTHOR, sub: SPACE, aud: `${AUTHORITY}#atproto_pds` },
    author,
  );
  // The key a holder signs with, which the credential names in `cnf.kid`.
  const holder = await P256Keypair.create();
  const credential = await createSpaceToken(
    'credential',
    { iss: AUTHORITY, sub: SPACE, keyId: holder.did() as any },
    authority,
  );
  const useSignature = await createSpaceSigHeaders(holder, {
    authorization: `Atproto-Space-Credential ${credential}`,
    audience: AUTHOR as any,
  });

  const b64 = (b: Uint8Array) => Buffer.from(b).toString('base64');
  const hex = (b: Uint8Array) => Buffer.from(b).toString('hex');
  writeFileSync(
    `${OUT}/manifest.json`,
    `${JSON.stringify(
      {
        producer: '@atproto/space',
        space: SPACE,
        author: AUTHOR,
        authority: AUTHORITY,
        userDidKey: author.did(),
        authorityDidKey: authority.did(),
        kemPrivateKeyHex: hex(await authorKem.export()),
        kemDidKey: authorKem.did(),
        rev,
        recordCount: records.length,
        commit: {
          ver: commit.ver,
          repo: commit.repo,
          space: commit.space,
          rev: commit.rev,
          data: { $bytes: b64(commit.data) },
        },
        getRepo: {
          wantMac: formatWantMacHeader(COMMIT_MAC_TYPE, repoShare.keyShare),
          keyShare: repoShare.keyShare,
          mac: formatMacHeader(COMMIT_MAC_TYPE, commitMac),
        },
        getRecord: {
          wantMac: formatWantMacHeader(RECORD_MAC_TYPE, recordShare.keyShare),
          keyShare: recordShare.keyShare,
          record: {
            uri: recordUri,
            cid: records[3].cid.toString(),
            value: {
              ...inputs[3].record,
              image: { ...inputs[3].record.image, ref: { $link: BLOB } },
            },
          },
          mac: formatMacHeader(RECORD_MAC_TYPE, recordMac),
        },
        delegationToken,
        delegationAud: `${AUTHORITY}#atproto_pds`,
        credential,
        credentialKeyId: holder.did(),
        useSignature,
        expectedPaths: records.map((r) => `${r.collection}/${r.rkey}`),
      },
      null,
      2,
    )}\n`,
  );
  console.log(`wrote ${OUT}/repo.star and manifest.json`);
});