/** RFC 8785 JCS の UTF-8 対象文字列を生成する。T-12。 */ export function canonicalize(value: unknown): string { return encode(value); } function encode(v: unknown): string { if (v === null) return "null"; if (typeof v === "string") return JSON.stringify(v); if (typeof v === "boolean") return v ? "true" : "false"; if (typeof v === "number") { if (!Number.isFinite(v)) throw new TypeError("JCS does not support non-finite numbers"); return JSON.stringify(Object.is(v, -0) ? 0 : v); } if (Array.isArray(v)) return `[${v.map(encode).join(",")}]`; if (typeof v === "object") { const o = v as Record; const keys = Object.keys(o).sort(); return `{${keys.map((k) => `${JSON.stringify(k)}:${encode(o[k])}`).join(",")}}`; } throw new TypeError(`Unsupported JCS value: ${typeof v}`); } /** JCS正規化したUTF-8をSHA-256し、指定形式で返す。T-12。 */ export async function sha256Jcs(value: unknown): Promise { const bytes = new TextEncoder().encode(canonicalize(value)); const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes); return `sha256-jcs:${Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, "0")).join("")}`; }