this repo has no description
1#!/usr/bin/env node
2
3/**
4 * PDS Setup Script
5 *
6 * Registers a did:plc, initializes the PDS, and notifies the relay.
7 * Zero dependencies - uses Node.js built-ins only.
8 *
9 * Usage: node scripts/setup.js --handle alice --pds https://your-pds.workers.dev
10 */
11
12import { webcrypto } from 'crypto'
13
14// === ARGUMENT PARSING ===
15
16function parseArgs() {
17 const args = process.argv.slice(2)
18 const opts = {
19 handle: null,
20 pds: null,
21 plcUrl: 'https://plc.directory',
22 relayUrl: 'https://bsky.network'
23 }
24
25 for (let i = 0; i < args.length; i++) {
26 if (args[i] === '--handle' && args[i + 1]) {
27 opts.handle = args[++i]
28 } else if (args[i] === '--pds' && args[i + 1]) {
29 opts.pds = args[++i]
30 } else if (args[i] === '--plc-url' && args[i + 1]) {
31 opts.plcUrl = args[++i]
32 } else if (args[i] === '--relay-url' && args[i + 1]) {
33 opts.relayUrl = args[++i]
34 }
35 }
36
37 if (!opts.handle || !opts.pds) {
38 console.error('Usage: node scripts/setup.js --handle <handle> --pds <pds-url>')
39 console.error('')
40 console.error('Options:')
41 console.error(' --handle Handle name (e.g., "alice")')
42 console.error(' --pds PDS URL (e.g., "https://atproto-pds.chad-53c.workers.dev")')
43 console.error(' --plc-url PLC directory URL (default: https://plc.directory)')
44 console.error(' --relay-url Relay URL (default: https://bsky.network)')
45 process.exit(1)
46 }
47
48 return opts
49}
50
51// === KEY GENERATION ===
52
53async function generateP256Keypair() {
54 const keyPair = await webcrypto.subtle.generateKey(
55 { name: 'ECDSA', namedCurve: 'P-256' },
56 true,
57 ['sign', 'verify']
58 )
59
60 // Export private key as raw 32 bytes
61 const privateJwk = await webcrypto.subtle.exportKey('jwk', keyPair.privateKey)
62 const privateBytes = base64UrlDecode(privateJwk.d)
63
64 // Export public key as uncompressed point (65 bytes)
65 const publicRaw = await webcrypto.subtle.exportKey('raw', keyPair.publicKey)
66 const publicBytes = new Uint8Array(publicRaw)
67
68 // Compress public key to 33 bytes
69 const compressedPublic = compressPublicKey(publicBytes)
70
71 return {
72 privateKey: privateBytes,
73 publicKey: compressedPublic,
74 cryptoKey: keyPair.privateKey
75 }
76}
77
78function compressPublicKey(uncompressed) {
79 // uncompressed is 65 bytes: 0x04 + x(32) + y(32)
80 const x = uncompressed.slice(1, 33)
81 const y = uncompressed.slice(33, 65)
82 const prefix = (y[31] & 1) === 0 ? 0x02 : 0x03
83 const compressed = new Uint8Array(33)
84 compressed[0] = prefix
85 compressed.set(x, 1)
86 return compressed
87}
88
89function base64UrlDecode(str) {
90 const base64 = str.replace(/-/g, '+').replace(/_/g, '/')
91 const binary = atob(base64)
92 const bytes = new Uint8Array(binary.length)
93 for (let i = 0; i < binary.length; i++) {
94 bytes[i] = binary.charCodeAt(i)
95 }
96 return bytes
97}
98
99function bytesToHex(bytes) {
100 return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('')
101}
102
103// === DID:KEY ENCODING ===
104
105// Multicodec prefix for P-256 public key (0x1200)
106const P256_MULTICODEC = new Uint8Array([0x80, 0x24])
107
108function publicKeyToDidKey(compressedPublicKey) {
109 // did:key format: "did:key:" + multibase(base58btc) of multicodec + key
110 const keyWithCodec = new Uint8Array(P256_MULTICODEC.length + compressedPublicKey.length)
111 keyWithCodec.set(P256_MULTICODEC)
112 keyWithCodec.set(compressedPublicKey, P256_MULTICODEC.length)
113
114 return 'did:key:z' + base58btcEncode(keyWithCodec)
115}
116
117function base58btcEncode(bytes) {
118 const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
119
120 // Count leading zeros
121 let zeros = 0
122 for (const b of bytes) {
123 if (b === 0) zeros++
124 else break
125 }
126
127 // Convert to base58
128 const digits = [0]
129 for (const byte of bytes) {
130 let carry = byte
131 for (let i = 0; i < digits.length; i++) {
132 carry += digits[i] << 8
133 digits[i] = carry % 58
134 carry = (carry / 58) | 0
135 }
136 while (carry > 0) {
137 digits.push(carry % 58)
138 carry = (carry / 58) | 0
139 }
140 }
141
142 // Convert to string
143 let result = '1'.repeat(zeros)
144 for (let i = digits.length - 1; i >= 0; i--) {
145 result += ALPHABET[digits[i]]
146 }
147
148 return result
149}
150
151// === MAIN ===
152
153async function main() {
154 const opts = parseArgs()
155
156 console.log('PDS Federation Setup')
157 console.log('====================')
158 console.log(`Handle: ${opts.handle}`)
159 console.log(`PDS: ${opts.pds}`)
160 console.log('')
161
162 // Step 1: Generate keypair
163 console.log('Generating P-256 keypair...')
164 const keyPair = await generateP256Keypair()
165 const didKey = publicKeyToDidKey(keyPair.publicKey)
166 console.log(` did:key: ${didKey}`)
167 console.log(` Private key: ${bytesToHex(keyPair.privateKey)}`)
168 console.log('')
169
170 // TODO: Register DID:PLC
171 // TODO: Initialize PDS
172 // TODO: Notify relay
173}
174
175main().catch(err => {
176 console.error('Error:', err.message)
177 process.exit(1)
178})