// test/helpers/identity.js // Proper identity setup with PLC registration for e2e tests import { base32Encode, base64UrlEncode, bytesToHex, generateKeyPair, importPrivateKey, sign, } from '@pdsjs/core/crypto'; import { cborEncodeDagCbor } from '@pdsjs/core/repo'; const PLC_URL = 'http://localhost:2582'; // Multicodec prefix for P-256 public key (0x1200) const P256_MULTICODEC = new Uint8Array([0x80, 0x24]); /** * @param {Uint8Array} bytes * @returns {string} */ function base58btcEncode(bytes) { const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; let zeros = 0; for (const b of bytes) { if (b === 0) zeros++; else break; } const digits = [0]; for (const byte of bytes) { let carry = byte; for (let i = 0; i < digits.length; i++) { carry += digits[i] << 8; digits[i] = carry % 58; carry = (carry / 58) | 0; } while (carry > 0) { digits.push(carry % 58); carry = (carry / 58) | 0; } } let result = '1'.repeat(zeros); for (let i = digits.length - 1; i >= 0; i--) { result += ALPHABET[digits[i]]; } return result; } /** * @param {Uint8Array} compressedPublicKey * @returns {string} */ function publicKeyToDidKey(compressedPublicKey) { const keyWithCodec = new Uint8Array( P256_MULTICODEC.length + compressedPublicKey.length, ); keyWithCodec.set(P256_MULTICODEC); keyWithCodec.set(compressedPublicKey, P256_MULTICODEC.length); return `did:key:z${base58btcEncode(keyWithCodec)}`; } /** * @param {Uint8Array} data * @returns {Promise} */ async function sha256(data) { const hash = await crypto.subtle.digest( 'SHA-256', /** @type {BufferSource} */ (data), ); return new Uint8Array(hash); } /** * @param {Record} operation * @param {CryptoKey} cryptoKey * @returns {Promise} */ async function signPlcOperation(operation, cryptoKey) { const { sig, ...opWithoutSig } = operation; const encoded = cborEncodeDagCbor(opWithoutSig); const signature = await sign(cryptoKey, encoded); return base64UrlEncode(signature); } /** * @param {Record} operation * @returns {Promise} */ async function deriveDidFromOperation(operation) { const encoded = cborEncodeDagCbor(operation); const hash = await sha256(encoded); return `did:plc:${base32Encode(hash.slice(0, 15))}`; } /** * Create a properly registered identity for e2e tests * @param {object} opts * @param {string} opts.pdsUrl - PDS URL (e.g., 'https://host.docker.internal:3443') * @param {string} opts.handle - Handle subdomain (e.g., 'test') * @returns {Promise<{did: string, privateKeyHex: string, handle: string}>} */ export async function createTestIdentity(opts) { const { pdsUrl, handle } = opts; // Generate keypair const keyPair = await generateKeyPair(); const cryptoKey = await importPrivateKey(keyPair.privateKey); const didKey = publicKeyToDidKey(keyPair.publicKey); // Build full handle (use hostname, not host, to exclude port) const pdsHostname = new URL(pdsUrl).hostname; const fullHandle = handle ? `${handle}.${pdsHostname}` : pdsHostname; // Create genesis operation /** @type {Record} */ const operation = { type: 'plc_operation', rotationKeys: [didKey], verificationMethods: { atproto: didKey, }, alsoKnownAs: [`at://${fullHandle}`], services: { atproto_pds: { type: 'AtprotoPersonalDataServer', endpoint: pdsUrl, }, }, prev: null, }; // Sign the operation operation.sig = await signPlcOperation(operation, cryptoKey); // Derive DID from operation const did = await deriveDidFromOperation(operation); // Register with local PLC const plcRes = await fetch(`${PLC_URL}/${encodeURIComponent(did)}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(operation), }); if (!plcRes.ok) { const text = await plcRes.text(); throw new Error(`PLC registration failed: ${plcRes.status} ${text}`); } return { did, privateKeyHex: bytesToHex(keyPair.privateKey), handle: fullHandle, }; } export { PLC_URL };