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); }); });