/** * Shared ed25519 key helpers. * * The host pins public keys in the exact string form `ed25519:`, where * the base64 payload is the RAW 32-byte ed25519 public key -- NOT a DER/SPKI * blob and NOT a PEM. node:crypto only exports SPKI, so we slice the raw key * out of it (and splice it back in on the way in). * * An ed25519 SPKI DER is always exactly 44 bytes: * 30 2a 30 05 06 03 2b 65 70 03 21 00 || <32-byte raw key> * so the prefix is a fixed 12-byte constant. */ import { createPublicKey, type KeyObject } from "node:crypto"; /** Fixed 12-byte DER prefix of an ed25519 SubjectPublicKeyInfo. */ const SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex"); export const PUBKEY_PREFIX = "ed25519:"; /** * The public key currently compiled into the Punktfunk host (slot 1). * `verify` defaults to this so a signed index can be checked with no arguments. */ export const DEFAULT_PUBLIC_KEY = "ed25519:qK18TE2qygIyObtMlHxUI/G1gKby8tPxuieAfnEKgYE="; /** Encode a node KeyObject public key as the host's `ed25519:` form. */ export function encodePublicKey(key: KeyObject): string { const spki = key.export({ type: "spki", format: "der" }); if (spki.length !== 44 || !spki.subarray(0, 12).equals(SPKI_PREFIX)) { throw new Error( `not an ed25519 public key (unexpected SPKI: ${spki.length} bytes)`, ); } return PUBKEY_PREFIX + spki.subarray(12).toString("base64"); } /** Parse the host's `ed25519:` form into a usable KeyObject. */ export function decodePublicKey(text: string): KeyObject { const trimmed = text.trim(); if (!trimmed.startsWith(PUBKEY_PREFIX)) { throw new Error(`public key must start with "${PUBKEY_PREFIX}"`); } const b64 = trimmed.slice(PUBKEY_PREFIX.length); if (!/^[A-Za-z0-9+/]{43}=$/.test(b64)) { throw new Error( "public key payload must be base64 of exactly 32 raw bytes", ); } const raw = Buffer.from(b64, "base64"); if (raw.length !== 32) { throw new Error(`public key must be 32 raw bytes, got ${raw.length}`); } return createPublicKey({ key: Buffer.concat([SPKI_PREFIX, raw]), format: "der", type: "spki", }); }