chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
import { generateKeyPair, importPrivateKey } from '@pdsjs/core/crypto';
import {
base58btcDecode,
base58btcEncode,
buildPlcOperation,
publicKeyToDidKey,
signPlcOperationObject,
} from '@pdsjs/core/plc';
import { cborEncodeDagCbor } from '@pdsjs/core/repo';
import { describe, expect, test } from 'vitest';
/**
* Derive a verify-only CryptoKey from a signing key.
* @param {CryptoKey} signingKey
*/
async function toVerifyKey(signingKey) {
const jwk = await crypto.subtle.exportKey('jwk', signingKey);
delete jwk.d;
jwk.key_ops = ['verify'];
return crypto.subtle.importKey(
'jwk',
jwk,
{ name: 'ECDSA', namedCurve: 'P-256' },
true,
['verify'],
);
}
describe('base58btcEncode', () => {
test('encodes leading zero bytes as 1s', () => {
expect(base58btcEncode(new Uint8Array([0, 0, 1]))).toBe('112');
});
test('encodes known byte sequence', () => {
// 'hello world' in base58btc
expect(base58btcEncode(new TextEncoder().encode('hello world'))).toBe(
'StV1DL6CwTryKyV',
);
});
});
describe('base58btcDecode', () => {
test('round-trips arbitrary bytes through base58btcEncode', () => {
const bytes = new Uint8Array([1, 2, 3, 250, 251, 252, 253, 254, 255, 0]);
expect(base58btcDecode(base58btcEncode(bytes))).toEqual(bytes);
});
test('round-trips bytes with leading zeros', () => {
const bytes = new Uint8Array([0, 0, 1, 2, 3]);
expect(base58btcDecode(base58btcEncode(bytes))).toEqual(bytes);
});
test('decodes a known byte sequence', () => {
expect(base58btcDecode('StV1DL6CwTryKyV')).toEqual(
new TextEncoder().encode('hello world'),
);
});
});
describe('publicKeyToDidKey', () => {
test('produces a did:key with the P-256 multicodec prefix', async () => {
const { publicKey } = await generateKeyPair();
expect(publicKeyToDidKey(publicKey).startsWith('did:key:zDn')).toBe(true);
});
test('is deterministic for the same key', async () => {
const { publicKey } = await generateKeyPair();
expect(publicKeyToDidKey(publicKey)).toBe(publicKeyToDidKey(publicKey));
});
});
describe('buildPlcOperation', () => {
test('builds a plc_operation with the expected shape', () => {
const op = /** @type {any} */ (
buildPlcOperation({
didKey: 'did:key:zDnaeTEST',
handle: 'alice.example.com',
pdsEndpoint: 'https://pds.example.com',
prev: null,
})
);
expect(op.type).toBe('plc_operation');
expect(op.rotationKeys).toEqual(['did:key:zDnaeTEST']);
expect(op.verificationMethods).toEqual({ atproto: 'did:key:zDnaeTEST' });
expect(op.alsoKnownAs).toEqual(['at://alice.example.com']);
expect(op.services).toEqual({
atproto_pds: {
type: 'AtprotoPersonalDataServer',
endpoint: 'https://pds.example.com',
},
});
expect(op.prev).toBe(null);
});
test('carries prev through for update operations', () => {
const op = /** @type {any} */ (
buildPlcOperation({
didKey: 'did:key:zDnaeTEST',
handle: 'alice.example.com',
pdsEndpoint: 'https://pds.example.com',
prev: 'bafyprevcid',
})
);
expect(op.prev).toBe('bafyprevcid');
});
});
describe('signPlcOperationObject', () => {
test('adds a verifiable signature over the unsigned operation', async () => {
const { privateKey } = await generateKeyPair();
const signingKey = await importPrivateKey(privateKey);
const verifyKey = await toVerifyKey(signingKey);
const op = buildPlcOperation({
didKey: 'did:key:zDnaeTEST',
handle: 'alice.example.com',
pdsEndpoint: 'https://pds.example.com',
prev: null,
});
const signed = await signPlcOperationObject(op, signingKey);
expect(typeof signed.sig).toBe('string');
expect(signed.sig.length).toBeGreaterThan(0);
// Signature verifies against the operation without the sig field
const { sig, ...unsigned } = signed;
const encoded = cborEncodeDagCbor(unsigned);
const sigBytes = Uint8Array.from(
Buffer.from(sig.replace(/-/g, '+').replace(/_/g, '/'), 'base64'),
);
const valid = await crypto.subtle.verify(
{ name: 'ECDSA', hash: 'SHA-256' },
verifyKey,
sigBytes,
/** @type {BufferSource} */ (encoded),
);
expect(valid).toBe(true);
});
test('does not mutate the input operation', async () => {
const { privateKey } = await generateKeyPair();
const signingKey = await importPrivateKey(privateKey);
const op = buildPlcOperation({
didKey: 'did:key:zDnaeTEST',
handle: 'alice.example.com',
pdsEndpoint: 'https://pds.example.com',
prev: null,
});
await signPlcOperationObject(op, signingKey);
expect('sig' in op).toBe(false);
});
});