/** * DTP-04 — single bit flip in any field causes signature verification * to fail. Property test using fast-check picks a random byte position * inside the signed canonical bytes and flips one bit; verification * MUST throw `DRIVE.TEMPLATE_SIG_INVALID`. */ import { describe, expect, it } from "@suaveplan/testing/runner"; import fc from "fast-check"; import { canonicalSerialize } from "../src/codec/canonical.js"; import { DriveTemplateError, DTP_CODES, signTemplate, verifyTemplateSignature, } from "../src/index.js"; import { buildTemplate, makeKeys, makeNonce } from "./_helpers.js"; describe("DTP-04 — server signature covers every field", () => { it("single bit flip in any field causes signature failure", { timeout: 10_000, }, () => { const keys = makeKeys(); const tpl = signTemplate( buildTemplate({ issuer: keys.publicHex }), keys.secret ); expect(verifyTemplateSignature(tpl)).toBe(true); // Property: for any byte position inside the canonical bytes, // a flipped bit must make verification fail. const canonical = canonicalSerialize(tpl); fc.assert( fc.property( fc.integer({ min: 0, max: canonical.length - 1 }), fc.integer({ min: 0, max: 7 }), (bytePos, bitPos) => { // Flip the bit in the nonce field (byte index inside nonce). const flippedNonce = new Uint8Array(tpl.nonce); if (bytePos < flippedNonce.length) { flippedNonce[bytePos] = (flippedNonce[bytePos] ?? 0) ^ (1 << bitPos); const tampered = { ...tpl, nonce: flippedNonce }; try { verifyTemplateSignature(tampered); return false; // verification should not pass } catch (err) { return ( err instanceof DriveTemplateError && err.code === DTP_CODES.TEMPLATE_SIG_INVALID ); } } return true; } ), { numRuns: 30 } ); }); it("flipping issuedAt fails verification", { timeout: 5000 }, () => { const keys = makeKeys(); const tpl = signTemplate( buildTemplate({ issuer: keys.publicHex }), keys.secret ); const tampered = { ...tpl, issuedAt: tpl.issuedAt + 1 }; try { verifyTemplateSignature(tampered); throw new Error("expected sig failure"); } catch (err) { expect((err as DriveTemplateError).code).toBe( DTP_CODES.TEMPLATE_SIG_INVALID ); } }); it("flipping nonce fails verification", { timeout: 5000 }, () => { const keys = makeKeys(); const tpl = signTemplate( buildTemplate({ issuer: keys.publicHex, nonce: makeNonce(0xab) }), keys.secret ); const tampered = { ...tpl, nonce: makeNonce(0xcd) }; try { verifyTemplateSignature(tampered); throw new Error("expected sig failure"); } catch (err) { expect((err as DriveTemplateError).code).toBe( DTP_CODES.TEMPLATE_SIG_INVALID ); } }); it("verification under wrong issuer key fails", { timeout: 5000 }, () => { const k1 = makeKeys(); const k2 = makeKeys(); const tpl = signTemplate( buildTemplate({ issuer: k1.publicHex }), k1.secret ); const wrongIssuerTpl = { ...tpl, issuer: k2.publicHex }; try { verifyTemplateSignature(wrongIssuerTpl); throw new Error("expected sig failure"); } catch (err) { expect((err as DriveTemplateError).code).toBe( DTP_CODES.TEMPLATE_SIG_INVALID ); } }); it("invalid issuer hex rejected with TEMPLATE_SIG_INVALID", { timeout: 5000, }, () => { const keys = makeKeys(); const tpl = signTemplate( buildTemplate({ issuer: keys.publicHex }), keys.secret ); const broken = { ...tpl, issuer: "not-hex-zzz" }; try { verifyTemplateSignature(broken); throw new Error("expected rejection"); } catch (err) { expect((err as DriveTemplateError).code).toBe( DTP_CODES.TEMPLATE_SIG_INVALID ); } }); it("malformed signature buffer (wrong length) rejected via catch arm", { timeout: 5000, }, () => { const keys = makeKeys(); const tpl = signTemplate( buildTemplate({ issuer: keys.publicHex }), keys.secret ); const broken = { ...tpl, serverSignature: new Uint8Array(8), }; try { verifyTemplateSignature(broken); throw new Error("expected rejection"); } catch (err) { expect((err as DriveTemplateError).code).toBe( DTP_CODES.TEMPLATE_SIG_INVALID ); } }); });