An AT Protocol PDS
at main 71 lines 2.8 kB view raw
1import { Secp256k1Keypair, EcdsaKeypair, type ExportableKeypair } from '@atproto/crypto' 2import type { Db } from '../db/index.js' 3 4/** 5 * Manages per-DID signing keypairs stored in SQLite (as JWK). 6 * Replaces @atproto/pds's ActorStore.keypair() / reserveKeypair() interface. 7 */ 8export class KeyStore { 9 constructor(private db: Db) {} 10 11 async getOrCreateKeypair(did: string): Promise<ExportableKeypair> { 12 const existing = this.db.prepare( 13 `SELECT privateKeyJwk FROM signing_key WHERE did = ?` 14 ).get(did) as { privateKeyJwk: string } | undefined 15 16 if (existing) { 17 return Secp256k1Keypair.import(JSON.parse(existing.privateKeyJwk), { exportable: true }) 18 } 19 20 const keypair = await Secp256k1Keypair.create({ exportable: true }) 21 const jwk = await keypair.export() 22 this.db.prepare(` 23 INSERT OR IGNORE INTO signing_key (did, privateKeyJwk, createdAt) VALUES (?, ?, ?) 24 `).run(did, JSON.stringify(jwk), new Date().toISOString()) 25 26 return keypair 27 } 28 29 async getKeypair(did: string): Promise<ExportableKeypair | null> { 30 const row = this.db.prepare( 31 `SELECT privateKeyJwk FROM signing_key WHERE did = ?` 32 ).get(did) as { privateKeyJwk: string } | undefined 33 if (!row) return null 34 return Secp256k1Keypair.import(JSON.parse(row.privateKeyJwk), { exportable: true }) 35 } 36 37 /** Reserve a signing key for a DID before account creation (migration flow). */ 38 async reserveKeypair(did: string | undefined): Promise<ExportableKeypair> { 39 const keypair = await Secp256k1Keypair.create({ exportable: true }) 40 const jwk = await keypair.export() 41 const key = did ?? keypair.did() 42 this.db.prepare(` 43 INSERT OR IGNORE INTO reserved_keypair (did, privateKeyJwk, createdAt) VALUES (?, ?, ?) 44 `).run(key, JSON.stringify(jwk), new Date().toISOString()) 45 return keypair 46 } 47 48 async getReservedKeypair(did: string): Promise<ExportableKeypair | null> { 49 const row = this.db.prepare( 50 `SELECT privateKeyJwk FROM reserved_keypair WHERE did = ?` 51 ).get(did) as { privateKeyJwk: string } | undefined 52 if (!row) return null 53 return Secp256k1Keypair.import(JSON.parse(row.privateKeyJwk), { exportable: true }) 54 } 55 56 clearReservedKeypair(did: string) { 57 this.db.prepare(`DELETE FROM reserved_keypair WHERE did = ?`).run(did) 58 } 59 60 promoteReservedKeypair(did: string): boolean { 61 const row = this.db.prepare( 62 `SELECT privateKeyJwk FROM reserved_keypair WHERE did = ?` 63 ).get(did) as { privateKeyJwk: string } | undefined 64 if (!row) return false 65 this.db.prepare(` 66 INSERT OR REPLACE INTO signing_key (did, privateKeyJwk, createdAt) VALUES (?, ?, ?) 67 `).run(did, row.privateKeyJwk, new Date().toISOString()) 68 this.db.prepare(`DELETE FROM reserved_keypair WHERE did = ?`).run(did) 69 return true 70 } 71}