chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
// Signature verification for did:key public keys.
//
// P-256 ships built in on crypto.subtle. secp256k1 is injected: hand-rolling
// ECDSA verification is a different risk class from hand-rolling a hash, since
// a subtle bug means accepting a forged signature rather than a failing test.
// The reference stack verifies secp256k1 with @noble/curves; a deployment that
// wants to verify secp256k1 keys (the curve the reference PDS gives every
// account) injects an implementation backed by it. Without one, this verifier
// interoperates with P-256 keys only.
import {
base58btcDecode,
P256_MULTICODEC,
SECP256K1_MULTICODEC,
} from './plc.js';
/**
* Decode a did:key (or bare multibase) into its curve and public key bytes.
* @param {string} didKey - did:key:z… or the z… multibase value alone
* @returns {{curve: 'p256'|'secp256k1', publicKey: Uint8Array}}
*/
export function parseDidKey(didKey) {
const value = didKey.startsWith('did:key:') ? didKey.slice(8) : didKey;
if (!value.startsWith('z')) {
throw new Error(`expected a did:key multibase value, got: ${didKey}`);
}
const bytes = base58btcDecode(value.slice(1));
if (bytes[0] === P256_MULTICODEC[0] && bytes[1] === P256_MULTICODEC[1]) {
return { curve: 'p256', publicKey: bytes.slice(2) };
}
if (
bytes[0] === SECP256K1_MULTICODEC[0] &&
bytes[1] === SECP256K1_MULTICODEC[1]
) {
return { curve: 'secp256k1', publicKey: bytes.slice(2) };
}
throw new Error(`unsupported did:key multicodec: ${didKey}`);
}
/**
* Build a signature verifier. P-256 is handled with crypto.subtle; secp256k1
* is delegated to an injected implementation, or refused if none was given.
*
* @param {{secp256k1?: (publicKey: Uint8Array, data: Uint8Array, sig: Uint8Array) => Promise<boolean>}} [opts]
* @returns {import('./ports.js').SignatureVerifierPort}
*/
export function createVerifier(opts = {}) {
return {
async verify(didKey, data, sig) {
const { curve, publicKey } = parseDidKey(didKey);
if (curve === 'p256') return verifyP256(publicKey, data, sig);
if (!opts.secp256k1) {
throw new Error(
'secp256k1 verification requires an injected verifier; ' +
'this deployment supports P-256 keys only',
);
}
return opts.secp256k1(publicKey, data, sig);
},
};
}
/**
* @param {Uint8Array} publicKey - 33-byte compressed point
* @param {Uint8Array} data
* @param {Uint8Array} sig - 64-byte raw r||s
* @returns {Promise<boolean>}
*/
async function verifyP256(publicKey, data, sig) {
const key = await crypto.subtle.importKey(
'raw',
/** @type {BufferSource} */ (publicKey),
{ name: 'ECDSA', namedCurve: 'P-256' },
false,
['verify'],
);
return crypto.subtle.verify(
{ name: 'ECDSA', hash: 'SHA-256' },
key,
/** @type {BufferSource} */ (sig),
/** @type {BufferSource} */ (data),
);
}