#!/usr/bin/env node /** * PDS Setup Script * * Registers a did:plc, initializes the PDS, and notifies the relay. * Zero dependencies - uses Node.js built-ins only. * * Usage: node scripts/setup.js --handle alice --pds https://your-pds.workers.dev */ import { webcrypto } from 'crypto' // === ARGUMENT PARSING === function parseArgs() { const args = process.argv.slice(2) const opts = { handle: null, pds: null, plcUrl: 'https://plc.directory', relayUrl: 'https://bsky.network' } for (let i = 0; i < args.length; i++) { if (args[i] === '--handle' && args[i + 1]) { opts.handle = args[++i] } else if (args[i] === '--pds' && args[i + 1]) { opts.pds = args[++i] } else if (args[i] === '--plc-url' && args[i + 1]) { opts.plcUrl = args[++i] } else if (args[i] === '--relay-url' && args[i + 1]) { opts.relayUrl = args[++i] } } if (!opts.handle || !opts.pds) { console.error('Usage: node scripts/setup.js --handle --pds ') console.error('') console.error('Options:') console.error(' --handle Handle name (e.g., "alice")') console.error(' --pds PDS URL (e.g., "https://atproto-pds.chad-53c.workers.dev")') console.error(' --plc-url PLC directory URL (default: https://plc.directory)') console.error(' --relay-url Relay URL (default: https://bsky.network)') process.exit(1) } return opts } // === KEY GENERATION === async function generateP256Keypair() { const keyPair = await webcrypto.subtle.generateKey( { name: 'ECDSA', namedCurve: 'P-256' }, true, ['sign', 'verify'] ) // Export private key as raw 32 bytes const privateJwk = await webcrypto.subtle.exportKey('jwk', keyPair.privateKey) const privateBytes = base64UrlDecode(privateJwk.d) // Export public key as uncompressed point (65 bytes) const publicRaw = await webcrypto.subtle.exportKey('raw', keyPair.publicKey) const publicBytes = new Uint8Array(publicRaw) // Compress public key to 33 bytes const compressedPublic = compressPublicKey(publicBytes) return { privateKey: privateBytes, publicKey: compressedPublic, cryptoKey: keyPair.privateKey } } function compressPublicKey(uncompressed) { // uncompressed is 65 bytes: 0x04 + x(32) + y(32) const x = uncompressed.slice(1, 33) const y = uncompressed.slice(33, 65) const prefix = (y[31] & 1) === 0 ? 0x02 : 0x03 const compressed = new Uint8Array(33) compressed[0] = prefix compressed.set(x, 1) return compressed } function base64UrlDecode(str) { const base64 = str.replace(/-/g, '+').replace(/_/g, '/') const binary = atob(base64) const bytes = new Uint8Array(binary.length) for (let i = 0; i < binary.length; i++) { bytes[i] = binary.charCodeAt(i) } return bytes } function bytesToHex(bytes) { return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('') } // === DID:KEY ENCODING === // Multicodec prefix for P-256 public key (0x1200) const P256_MULTICODEC = new Uint8Array([0x80, 0x24]) function publicKeyToDidKey(compressedPublicKey) { // did:key format: "did:key:" + multibase(base58btc) of multicodec + key 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) } function base58btcEncode(bytes) { const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' // Count leading zeros let zeros = 0 for (const b of bytes) { if (b === 0) zeros++ else break } // Convert to base58 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 } } // Convert to string let result = '1'.repeat(zeros) for (let i = digits.length - 1; i >= 0; i--) { result += ALPHABET[digits[i]] } return result } // === MAIN === async function main() { const opts = parseArgs() console.log('PDS Federation Setup') console.log('====================') console.log(`Handle: ${opts.handle}`) console.log(`PDS: ${opts.pds}`) console.log('') // Step 1: Generate keypair console.log('Generating P-256 keypair...') const keyPair = await generateP256Keypair() const didKey = publicKeyToDidKey(keyPair.publicKey) console.log(` did:key: ${didKey}`) console.log(` Private key: ${bytesToHex(keyPair.privateKey)}`) console.log('') // TODO: Register DID:PLC // TODO: Initialize PDS // TODO: Notify relay } main().catch(err => { console.error('Error:', err.message) process.exit(1) })