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
JavaScript
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;
}
}