Files
playbook/antigravity-awesome-skills/verification/aas-v1/verifier/lib/canonical.mjs
T
2026-07-18 00:02:59 +00:00

41 lines
1.5 KiB
JavaScript

import crypto from "node:crypto";
function canonicalNumber(value) {
if (!Number.isFinite(value)) throw new TypeError("Canonical JSON forbids non-finite numbers");
if (Object.is(value, -0)) return "0";
return JSON.stringify(value);
}
export function canonicalJson(value) {
if (value === null) return "null";
if (typeof value === "boolean") return value ? "true" : "false";
if (typeof value === "number") return canonicalNumber(value);
if (typeof value === "string") return JSON.stringify(value);
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
if (typeof value === "object") {
// RFC 8785 orders names by UTF-16 code units, matching ECMAScript's
// default string sort. UTF-8 byte order differs for some non-ASCII keys.
const keys = Object.keys(value).sort();
return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(",")}}`;
}
throw new TypeError(`Canonical JSON cannot encode ${typeof value}`);
}
export function sha256(value) {
return `sha256-${crypto.createHash("sha256").update(value).digest("hex")}`;
}
export function sha512(value) {
return `sha512-${crypto.createHash("sha512").update(value).digest("base64")}`;
}
export function digestJson(value) {
return sha256(Buffer.from(canonicalJson(value), "utf8"));
}
export function parseCanonicalJson(text) {
const value = JSON.parse(text);
if (canonicalJson(value) !== text.trim()) throw new Error("Input is not canonical JSON");
return value;
}