// did:key for an Ed25519 public key (multicodec 0xed01, multibase base58btc, prefix z). Pure, no network.
import { generateKeyPairSync, createPublicKey } from 'node:crypto';
const B58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
export function base58btc(bytes) {
let n = 0n;
for (const b of bytes) n = (n << 8n) | BigInt(b);
let out = '';
while (n > 0n) { out = B58[Number(n % 58n)] + out; n /= 58n; }
for (const b of bytes) { if (b !== 0) break; out = '1' + out; }
return out;
}
export function base58decode(s) {
let n = 0n;
for (const c of s) { const i = B58.indexOf(c); if (i < 0) throw new Error('not base58'); n = n * 58n + BigInt(i); }
const bytes = [];
while (n > 0n) { bytes.unshift(Number(n & 0xffn)); n >>= 8n; }
for (const c of s) { if (c !== '1') break; bytes.unshift(0); }
return Uint8Array.from(bytes);
}
/** Raw 32 byte Ed25519 public key from a KeyObject. */
export function rawPublicKey(publicKey) {
const der = publicKey.export({ format: 'der', type: 'spki' });
return new Uint8Array(der.subarray(der.length - 32));
}
export function didKeyFromPublic(raw) {
if (raw.length !== 32) throw new Error('an Ed25519 public key is 32 bytes');
return `did:key:z${base58btc(Uint8Array.from([0xed, 0x01, ...raw]))}`;
}
export function publicFromDidKey(did) {
const m = /^did:key:z([1-9A-HJ-NP-Za-km-z]+)$/.exec(did);
if (!m) throw new Error('not a did:key');
const b = base58decode(m[1]);
if (b[0] !== 0xed || b[1] !== 0x01 || b.length !== 34) throw new Error('not an Ed25519 did:key');
return b.subarray(2);
}
/** A fresh identity made on this machine. The private key stays a KeyObject; the caller decides where it is stored. */
export function newIdentity() {
const { publicKey, privateKey } = generateKeyPairSync('ed25519');
const raw = rawPublicKey(publicKey);
return { did: didKeyFromPublic(raw), publicKey, privateKey };
}
export { createPublicKey };
agentgate-source: code mirror on Cloudflare Pages. All repos