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

johnandersen777.bsky.social/atlibs

0
0
main
50 lines · 3.2 KB Raw History
// The com.fedfork.exec.attest pseudo-call in the browser: network.attested.signature entries,
// byte for byte as socialweb-computer-emdash src/attest.ts (atlibs-host-calls) and kcp
// internal/atproto/market/attest.go. The secp256k1 implementation is injected
// (@noble/curves 2.x `secp256k1`: getPublicKey, sign with sha256 prehash, utils.randomSecretKey).

import { base58, dagCbor, toBase32 } from "../encoding/index.js";

export const ATTEST_NSID = "com.fedfork.exec.attest";
const SIGNATURE = "network.attested.signature";
// CIDv1 dag-cbor sha2-256 of the record without signatures, with $sig = entry metadata + repository.
export async function attestationCid(record, metadata, repository) {
	if (typeof record.$type !== "string" || !record.$type) throw new Error("Attested record needs a $type");
	const { cid: _cid, signature: _signature, ...sig } = metadata;
	const { signatures: _signatures, ...prepared } = record;
	const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", new Uint8Array(dagCbor({ ...prepared, $sig: { ...sig, repository } }))));
	const bytes = Uint8Array.from([0x01, 0x71, 0x12, 0x20, ...digest]);
	return { bytes, cid: `b${toBase32(bytes)}` };
}

// did:key of a compressed secp256k1 public key (multicodec 0xe7 0x01).
export const didKey = (publicKey) => `did:key:z${base58(Uint8Array.from([0xe7, 0x01, ...publicKey]))}`;

async function signInline(secp256k1, secretKey, record, repository) {
	const metadata = { $type: SIGNATURE, key: didKey(secp256k1.getPublicKey(secretKey, true)), issuer: repository };
	const { bytes, cid } = await attestationCid(record, metadata, repository);
	return { ...metadata, cid, signature: { $bytes: (await secp256k1.sign(bytes, secretKey)).toBase64({ omitPadding: true }) } };
}

const hex = (bytes) => Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
const unhex = (text) => Uint8Array.from(text.match(/../g), (h) => parseInt(h, 16));

// call(nsid, request) handler for ATTEST_NSID: one key per account DID, in the secret setting
// `attestKeys` ({did: hex}) of ctx (createPluginContext with settingsSchema {attestKeys: {type: "secret"}}).
// The key never enters wasm; the output is the input record plus one signatures[] entry.
// ponytail: get-then-set, two tabs generating at once can race; use compareAndSet if that matters.
export function attestHost(ctx, secp256k1, account) {
	const key = async () => {
		const keys = (await ctx.settings.get("attestKeys")) ?? {};
		if (!keys[account]) await ctx.settings.set("attestKeys", { ...keys, [account]: hex(secp256k1.utils.randomSecretKey()) });
		return unhex((await ctx.settings.get("attestKeys"))[account]);
	};
	return async ({ repository, record } = {}) => {
		if (!account || repository !== account) throw new Error("attest repository must be the session DID");
		const secretKey = await key();
		if (record === undefined) return { key: didKey(secp256k1.getPublicKey(secretKey, true)) };
		if (typeof record !== "object" || record === null || Array.isArray(record)) throw new Error("attest record must be an object");
		const entry = await signInline(secp256k1, secretKey, record, account);
		return { key: entry.key, record: { ...record, signatures: [...(Array.isArray(record.signatures) ? record.signatures : []), entry] } };
	};
}