feat(verify-gate): verify-receipt pure core sign/accept (G1 task2)
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* verify-receipt (G1, Level A) — чистое ядро подписанной verify-расписки (домен VERIFY_PASS).
|
||||
* Зеркало floor-signer.mjs. Подлинность — подпись подписанта; свежесть — совпадение
|
||||
* code_fingerprint с ТЕКУЩИМ staged-diff (считает consumer/producer, инъекция currentFingerprint).
|
||||
* occurrence — структурный positive-int (анти-двойной-зачёт = Level B). Δ4: ключ не защищён от
|
||||
* мотивированного контроллера (см. план G1 §Forge); подпись даёт out-of-env + шов под HSM.
|
||||
*/
|
||||
import { signPayload, verifyReceipt, RECEIPT_DOMAINS } from './receipt-sign.mjs';
|
||||
|
||||
export function signVerifyReceipt({ code_fingerprint, occurrence }, key) {
|
||||
if (!key) return null;
|
||||
const body = { code_fingerprint, occurrence };
|
||||
const sig = signPayload(body, key, RECEIPT_DOMAINS.VERIFY_PASS);
|
||||
if (!sig) return null;
|
||||
return { ...body, sig };
|
||||
}
|
||||
|
||||
export function verifyReceiptSig(record, key) {
|
||||
return verifyReceipt(record, key, RECEIPT_DOMAINS.VERIFY_PASS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Принять расписку: подпись валидна И occurrence — целое >0 И (если задан currentFingerprint)
|
||||
* code_fingerprint совпадает с ним. Любое нарушение → {accepted:false, reason}.
|
||||
*/
|
||||
export function acceptVerifyReceipt(record, key, { currentFingerprint = null } = {}) {
|
||||
if (!verifyReceiptSig(record, key)) return { accepted: false, reason: 'bad-signature' };
|
||||
const occ = record.occurrence;
|
||||
if (!Number.isInteger(occ) || occ <= 0) return { accepted: false, reason: 'bad-occurrence' };
|
||||
if (currentFingerprint !== null && record.code_fingerprint !== currentFingerprint) {
|
||||
return { accepted: false, reason: 'stale-fingerprint' };
|
||||
}
|
||||
return { accepted: true, reason: 'ok' };
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { signVerifyReceipt, verifyReceiptSig, acceptVerifyReceipt } from './verify-receipt.mjs';
|
||||
|
||||
const KEY = 'signer-key';
|
||||
const FP = 'a'.repeat(64);
|
||||
|
||||
describe('signVerifyReceipt', () => {
|
||||
it('подписывает {code_fingerprint, occurrence}; без ключа → null', () => {
|
||||
expect(signVerifyReceipt({ code_fingerprint: FP, occurrence: 1 }, '')).toBe(null);
|
||||
const r = signVerifyReceipt({ code_fingerprint: FP, occurrence: 1 }, KEY);
|
||||
expect(r.code_fingerprint).toBe(FP);
|
||||
expect(r.occurrence).toBe(1);
|
||||
expect(typeof r.sig).toBe('string');
|
||||
expect(verifyReceiptSig(r, KEY)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('acceptVerifyReceipt', () => {
|
||||
const good = () => signVerifyReceipt({ code_fingerprint: FP, occurrence: 1 }, KEY);
|
||||
it('валидная подпись + occurrence>0 + fingerprint совпал → accepted', () => {
|
||||
expect(acceptVerifyReceipt(good(), KEY, { currentFingerprint: FP })).toEqual({ accepted: true, reason: 'ok' });
|
||||
});
|
||||
it('подмена fingerprint после подписи → bad-signature', () => {
|
||||
const r = { ...good(), code_fingerprint: 'b'.repeat(64) };
|
||||
expect(acceptVerifyReceipt(r, KEY, { currentFingerprint: 'b'.repeat(64) }).accepted).toBe(false);
|
||||
});
|
||||
it('staged-diff изменился (current ≠ receipt) → stale-fingerprint', () => {
|
||||
expect(acceptVerifyReceipt(good(), KEY, { currentFingerprint: 'c'.repeat(64) }))
|
||||
.toEqual({ accepted: false, reason: 'stale-fingerprint' });
|
||||
});
|
||||
it('occurrence не целое/≤0 → bad-occurrence', () => {
|
||||
const r = signVerifyReceipt({ code_fingerprint: FP, occurrence: 0 }, KEY);
|
||||
expect(acceptVerifyReceipt(r, KEY, { currentFingerprint: FP }).reason).toBe('bad-occurrence');
|
||||
});
|
||||
it('без ключа → bad-signature (fail-closed)', () => {
|
||||
expect(acceptVerifyReceipt(good(), '', { currentFingerprint: FP }).accepted).toBe(false);
|
||||
});
|
||||
it('currentFingerprint не задан (null) → проверка свежести пропущена', () => {
|
||||
expect(acceptVerifyReceipt(good(), KEY).accepted).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user