~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
52 lines · 2.0 KB Raw History
// Mints a rotation (recovery) key entirely in the browser for a migration. The
// private key is generated here and NEVER sent anywhere — only the public
// did:key goes into the PLC operation as a rotation key. The user keeps the
// private key so they can recover their identity if their PDS is ever lost.
//
// secp256k1, to match the wider atproto tooling (goat, pds-moover): a key minted
// here can later be handed to `goat account plc` to sign a recovery operation.

import { secp256k1 } from '@noble/curves/secp256k1';

// Multicodec prefix for a secp256k1 public key, varint-encoded (0xe701). A
// compressed point behind this prefix, base58btc-encoded, is the `zQ3sh…`
// did:key every atproto rotation key uses.
const SECP256K1_MULTICODEC = [0xe7, 0x01];
const BASE58_ALPHABET =
  '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';

/** Encode bytes as base58btc (Bitcoin alphabet), preserving leading zeros. */
function base58btc(/** @type {Uint8Array} */ bytes) {
  let value = 0n;
  for (const byte of bytes) value = value * 256n + BigInt(byte);
  let out = '';
  while (value > 0n) {
    out = BASE58_ALPHABET[Number(value % 58n)] + out;
    value /= 58n;
  }
  for (const byte of bytes) {
    if (byte === 0) out = `1${out}`;
    else break;
  }
  return out;
}

/** Lowercase hex, the format `goat` expects for a k256 private key. */
function toHex(/** @type {Uint8Array} */ bytes) {
  return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
}

/**
 * Generate a fresh secp256k1 recovery key.
 * @returns {{ didKey: string, privateKeyHex: string }} the public did:key to put
 *   in the PLC op, and the private key (hex) for the user to save.
 */
export function generateRecoveryKey() {
  const privateKey = secp256k1.utils.randomPrivateKey();
  const publicKey = secp256k1.getPublicKey(privateKey, true); // compressed, 33 bytes
  const prefixed = new Uint8Array([...SECP256K1_MULTICODEC, ...publicKey]);
  return {
    didKey: `did:key:z${base58btc(prefixed)}`,
    privateKeyHex: toHex(privateKey),
  };
}