// The identity half of setup: a secp256k1 signing key, its PLC operation and // the DID derived from it. Everything here runs in the visitor's browser. The // key is secp256k1 rather than P-256 because some third-party services verify // only ES256K service JWTs. import { p256 } from '@noble/curves/p256'; import { secp256k1 } from '@noble/curves/secp256k1'; import { base32Encode, bytesToHex } from '@pdsjs/core/crypto'; import { buildPlcOperation, publicKeyToDidKey, signPlcOperationObject, } from '@pdsjs/core/plc'; import { cborEncodeDagCbor } from '@pdsjs/core/repo'; export function generateIdentity() { const privateKey = secp256k1.utils.randomPrivateKey(); const publicKey = secp256k1.getPublicKey(privateKey, true); // The `#atproto_kem` key. Space syncers derive a shared secret against it. const kemPrivateKey = p256.utils.randomPrivateKey(); return { privateKey, privateKeyHex: bytesToHex(privateKey), didKey: publicKeyToDidKey(publicKey, 'secp256k1'), kemKeyHex: bytesToHex(kemPrivateKey), kemDidKey: publicKeyToDidKey( p256.getPublicKey(kemPrivateKey, true), 'p256', ), /** @type {import('@pdsjs/core/crypto').SigningKey} */ signer: { curve: 'secp256k1', async sign(/** @type {Uint8Array} */ data) { const hash = new Uint8Array( // SigningKey takes any Uint8Array; SubtleCrypto takes one backed by // an ArrayBuffer. Every caller here passes the latter. await crypto.subtle.digest( 'SHA-256', /** @type {Uint8Array} */ (data), ), ); // Low-S compact r||s, the shape atproto verifies. return secp256k1 .sign(hash, privateKey, { lowS: true }) .toCompactRawBytes(); }, async publicKey() { return publicKey; }, }, }; } /** * @param {ReturnType} identity * @param {string} handle * @param {string} pdsUrl */ export async function createPlcOperation(identity, handle, pdsUrl) { const operation = await signPlcOperationObject( buildPlcOperation({ didKey: identity.didKey, kemDidKey: identity.kemDidKey, handle, pdsEndpoint: pdsUrl, }), identity.signer, ); // The DID covers the signature, so it is derived from the signed operation. const digest = new Uint8Array( await crypto.subtle.digest( 'SHA-256', /** @type {Uint8Array} */ (cborEncodeDagCbor(operation)), ), ); const did = `did:plc:${base32Encode(digest.slice(0, 15))}`; return { operation, did }; }