Files
Portfolio/tests/unit/approval-envelope.test.ts

138 lines
3.9 KiB
TypeScript

import { generateKeyPairSync, sign } from "node:crypto";
import { describe, expect, test } from "vitest";
import {
type ApprovalPayload,
parseApprovalEnvelopeJson,
verifyApprovalEnvelope,
} from "../../src/security/approval-envelope.ts";
import {
canonicalJsonDigest,
canonicalValueBytes,
} from "../../src/security/canonical-digest.ts";
const hash = (character: string) => character.repeat(64);
const payload = (): ApprovalPayload => ({
schema: "portfolio-approval-payload/v1",
keyId: "approval-2026",
issuerEpoch: 3,
authorizedRole: "portfolio-approver",
evidenceFingerprints: [hash("a")],
recordClass: "content",
recordType: "profile",
publicRecordIdOrAssetId: "profile-main",
approvedPublicRecordHashOrManifestRecordHash: hash("b"),
sanitizedOutputHashOrNull: null,
decision: "approve",
issuedAt: "2026-09-01T08:00:00Z",
expiresAt: "2026-10-01T08:00:00Z",
previousLedgerHeadHash: hash("0"),
ledgerRevision: 1,
policyVersion: "approval-policy-1",
});
function signedEnvelope(overrides: Partial<ApprovalPayload> = {}) {
const keys = generateKeyPairSync("ed25519");
const signedPayload = { ...payload(), ...overrides };
const signature = sign(
null,
canonicalValueBytes(signedPayload),
keys.privateKey,
).toString("base64url");
return {
envelope: {
schema: "portfolio-approval-envelope/v1" as const,
payload: signedPayload,
signature: {
algorithm: "Ed25519" as const,
encoding: "base64url-nopad" as const,
value: signature,
},
},
publicKey: keys.publicKey,
};
}
function trustFor(
envelope: ReturnType<typeof signedEnvelope>["envelope"],
publicKey: ReturnType<typeof signedEnvelope>["publicKey"],
) {
const envelopeDigest = canonicalJsonDigest(
"portfolio-approval-envelope-digest/v1",
envelope,
);
const latestLedgerHeadHash = canonicalJsonDigest(
"portfolio-approval-ledger-entry/v1",
{
previousLedgerHeadHash: hash("0"),
ledgerRevision: 1,
envelopeDigest,
},
);
return {
now: "2026-09-02T00:00:00Z",
latestLedgerHeadHash,
latestLedgerRevision: 1,
trustedKeys: [
{
keyId: "approval-2026",
issuerEpoch: 3,
scope: "portfolio-approval-decision/v1" as const,
authorizedRoles: ["portfolio-approver"],
publicKey,
},
],
ledgerProof: {
schema: "portfolio-approval-ledger-proof/v1" as const,
genesisHeadHash: hash("0"),
entries: [envelope],
},
};
}
describe("approval envelopes", () => {
test("verifies an exact, current, scoped Ed25519 approval", () => {
const { envelope, publicKey } = signedEnvelope();
const verified = verifyApprovalEnvelope(
envelope,
trustFor(envelope, publicKey),
);
expect(verified.publicRecordIdOrAssetId).toBe("profile-main");
});
test.each([
["expired", { expiresAt: "2026-09-01T09:00:00Z" }],
["non-UTC timestamp", { issuedAt: "2026-09-01T10:00:00+02:00" }],
["wrong epoch", { issuerEpoch: 2 }],
["rejection", { decision: "reject" }],
] as const)("rejects a %s envelope", (_label, override) => {
const { envelope, publicKey } = signedEnvelope(override);
expect(() =>
verifyApprovalEnvelope(envelope, trustFor(envelope, publicKey)),
).toThrow();
});
test("rejects unknown fields, duplicate keys, and alternate signature encodings at ingest", () => {
const { envelope } = signedEnvelope();
expect(() =>
parseApprovalEnvelopeJson(JSON.stringify({ ...envelope, extra: true })),
).toThrow();
expect(() =>
parseApprovalEnvelopeJson(
'{"schema":"portfolio-approval-envelope/v1","schema":"portfolio-approval-envelope/v1"}',
),
).toThrow();
expect(() =>
parseApprovalEnvelopeJson(
JSON.stringify({
...envelope,
signature: {
...envelope.signature,
value: `${envelope.signature.value}=`,
},
}),
),
).toThrow();
});
});