UNPKG

noa-receipt

Version:

NOA Agent Action Receipt — open, offline-verifiable provenance for AI-agent actions. The governance/receipt organ only; the NOA brain is separate and proprietary.

68 lines (67 loc) • 3 kB
import { bufferFrom, bufToString, bufEquals, bufSubarray, byteLength, createPublicKeyCaptured, keyExportSpkiDer, keyExportPkcs8Der, createPrivateKeyCaptured, generateEd25519KeyPair, ed25519Sign, ed25519Verify, asymmetricKeyType, toBigInt, } from "./intrinsics.js"; import { membership } from "./inert.js"; const isSmallOrderPubkey = membership([ "0100000000000000000000000000000000000000000000000000000000000000", "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000080", "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05", "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85", "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a", "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa", ]); export function generateKeyPair(kid) { const { publicKey, privateKey } = generateEd25519KeyPair(); return { kid, publicKey: bufToString(keyExportSpkiDer(publicKey), "base64"), privateKey: bufToString(keyExportPkcs8Der(privateKey), "base64"), }; } export function signEd25519(privateKeyB64, message) { const key = createPrivateKeyCaptured({ key: bufferFrom(privateKeyB64, "base64"), format: "der", type: "pkcs8", }); if (asymmetricKeyType(key) !== "ed25519") throw new Error("signEd25519: key is not an Ed25519 key"); return bufToString(ed25519Sign(message, key), "base64"); } export function verifyEd25519(publicKeyB64, message, signatureB64) { try { const der = bufferFrom(publicKeyB64, "base64"); if (bufToString(der, "base64") !== publicKeyB64) return false; const key = createPublicKeyCaptured({ key: der, format: "der", type: "spki" }); if (asymmetricKeyType(key) !== "ed25519") return false; const canonical = keyExportSpkiDer(key); if (!bufEquals(canonical, der)) return false; const raw = bufSubarray(canonical, 12); const yBytes = bufferFrom(raw); yBytes[31] = yBytes[31] & 0x7f; const Q = (1n << 255n) - 19n; let y = 0n; for (let i = 31; i >= 0; i--) y = (y << 8n) | toBigInt(yBytes[i]); if (y >= Q) return false; if (isSmallOrderPubkey(bufToString(raw, "hex"))) return false; const sigBytes = bufferFrom(signatureB64, "base64"); if (byteLength(sigBytes) !== 64 || bufToString(sigBytes, "base64") !== signatureB64) return false; const L = 2n ** 252n + 27742317777372353535851937790883648493n; let S = 0n; for (let i = 63; i >= 32; i--) S = (S << 8n) | toBigInt(sigBytes[i]); if (S >= L) return false; return ed25519Verify(message, key, sigBytes); } catch { return false; } }