From 4b5b98e84b2b5e5923185043d3b5b0bd1d23ee0f Mon Sep 17 00:00:00 2001 From: Greg Soucy Date: Mon, 18 May 2026 11:40:42 -0400 Subject: [PATCH] fix: align receipt signing and verification with runtime-core 1.2 API --- src/index.ts | 10 +++++++++- src/receipt.ts | 12 ++++-------- test/receipt.test.ts | 14 +++++++++++--- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/index.ts b/src/index.ts index f76516c..7ff673a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,6 +17,7 @@ export interface CommandLayerConfig { } export const DEFAULT_VERIFIER_URL = "https://runtime.commandlayer.org/verify"; +export const DEFAULT_CANONICALIZATION = "json.sorted_keys.v1"; export const TRUST_FAMILY = "trust-verification" as const; export const TRUST_VERSION = "1.0.0" as const; @@ -97,11 +98,18 @@ export class CommandLayer { throw new Error("Missing privateKeyPem (or deprecated privateKey alias)"); } + const canonicalization = config.canonicalization ?? DEFAULT_CANONICALIZATION; + if (canonicalization !== DEFAULT_CANONICALIZATION) { + throw new Error( + `Unsupported canonicalization \"${canonicalization}\". Only \"${DEFAULT_CANONICALIZATION}\" is supported.`, + ); + } + this.config = { ...config, signer, privateKeyPem, - canonicalization: config.canonicalization ?? "json.sorted_keys.v1", + canonicalization, verifierUrl: config.verifierUrl ?? DEFAULT_VERIFIER_URL, }; diff --git a/src/receipt.ts b/src/receipt.ts index 42e9b2e..3326500 100644 --- a/src/receipt.ts +++ b/src/receipt.ts @@ -1,8 +1,6 @@ import { signCommandLayerReceipt, - type CommandLayerCanonicalization, type CommandLayerReceipt, - type SignCommandLayerReceiptParams, } from "@commandlayer/runtime-core"; import type { JsonValue } from "./canonicalize.js"; @@ -23,7 +21,7 @@ export interface ReceiptInput { }; } -export type Receipt = CommandLayerReceipt; +export type Receipt = CommandLayerReceipt; export function canonicalPayloadFromReceiptInput(receipt: ReceiptInput) { return { @@ -41,13 +39,11 @@ export function canonicalPayloadFromReceiptInput(receipt: ReceiptInput) { export async function createReceipt(params: { keyId: string; privateKeyPem: string; - canonicalization: CommandLayerCanonicalization; + canonicalization: string; input: ReceiptInput; }): Promise { - return signCommandLayerReceipt({ - receipt: params.input, + return signCommandLayerReceipt(params.input, { privateKeyPem: params.privateKeyPem, kid: params.keyId, - canonicalization: params.canonicalization, - } as SignCommandLayerReceiptParams); + }); } diff --git a/test/receipt.test.ts b/test/receipt.test.ts index 012b7eb..38ba2be 100644 --- a/test/receipt.test.ts +++ b/test/receipt.test.ts @@ -19,6 +19,12 @@ async function generateKeyPair(): Promise<{ pem: string; publicKey: CryptoKey }> return { pem: toPem(pkcs8), publicKey: keyPair.publicKey }; } +function toSpkiPem(spki: ArrayBuffer): string { + const b64 = Buffer.from(spki).toString("base64"); + const lines = b64.match(/.{1,64}/g)?.join("\n") ?? b64; + return `-----BEGIN PUBLIC KEY-----\n${lines}\n-----END PUBLIC KEY-----`; +} + async function generatePrivateKeyPem(): Promise { return (await generateKeyPair()).pem; } @@ -80,6 +86,8 @@ test("wrap rejects an unrecognized verb before running the wrapped function", as test("emitted receipt verifies with runtime-core and tampering is invalid", async () => { const { pem, publicKey } = await generateKeyPair(); + const spki = await webcrypto.subtle.exportKey("spki", publicKey); + const publicKeyPem = toSpkiPem(spki); const cl = new CommandLayer({ signer: "verifyagent.eth", @@ -92,11 +100,11 @@ test("emitted receipt verifies with runtime-core and tampering is invalid", asyn run: async () => ({ y: 2 }), }); - const verification = await verifyCommandLayerReceipt({ receipt }); - assert.equal(verification.status, "VALID"); + const verification = await verifyCommandLayerReceipt(receipt, { publicKeyPemOrDer: publicKeyPem }); + assert.equal(verification.status, "VERIFIED"); const tampered = { ...receipt, output: { y: 99 } }; - const tamperedVerification = await verifyCommandLayerReceipt({ receipt: tampered }); + const tamperedVerification = await verifyCommandLayerReceipt(tampered, { publicKeyPemOrDer: publicKeyPem }); assert.equal(tamperedVerification.status, "INVALID"); });