@graneth/verify
Advanced tools
+12
-2
@@ -24,3 +24,3 @@ /** | ||
| */ | ||
| import { verifyReceipt } from "./index.js"; | ||
| import { verifyReceipt, receiptShapeDefect } from "./index.js"; | ||
| /** | ||
@@ -222,4 +222,5 @@ * The contract a script may depend on. | ||
| } | ||
| let receipt; | ||
| try { | ||
| return { receipt: JSON.parse(text) }; | ||
| receipt = JSON.parse(text); | ||
| } | ||
@@ -230,2 +231,11 @@ catch (err) { | ||
| } | ||
| // Every question about whether this IS a receipt is answered here, next to | ||
| // the parse, and none of them needs a key. In 0.2.0 they lived behind key | ||
| // resolution, so the same stripped file answered MALFORMED with --key and | ||
| // UNCHECKED without one — and "the obstacle is on this side" is a claim | ||
| // about the document. Same rule, wrong position. | ||
| const defect = receiptShapeDefect(receipt); | ||
| if (defect) | ||
| return malformed(io, defect.reason); | ||
| return { receipt }; | ||
| } | ||
@@ -232,0 +242,0 @@ /** The key is the reader's own artifact: every way it can be wrong is UNCHECKED. */ |
+20
-0
@@ -94,2 +94,22 @@ /** | ||
| /** | ||
| * The ways a file can fail to be a receipt at all — decided WITHOUT a public | ||
| * key, because not one of them needs one. | ||
| * | ||
| * ── WHY THIS IS EXPORTED, AND KEYLESS ─────────────────────────────────────── | ||
| * In 0.2.0 these checks were reachable only through verifyReceipt(), which | ||
| * needs a key to have been supplied and resolved first. So a receipt with its | ||
| * signed keyId stripped answered MALFORMED with `--key` and UNCHECKED without | ||
| * one — "the obstacle is on this side", which is a claim ABOUT THE DOCUMENT, | ||
| * and false for that file. Nothing was wrong with the classification; it was | ||
| * standing behind a step it did not depend on, and the commonest first run is | ||
| * the one that skips that step. | ||
| * | ||
| * Whether a document is a well-formed receipt is a property of the document. | ||
| * Keeping the answer keyless is what makes it impossible to accidentally make | ||
| * it conditional on the key again. | ||
| */ | ||
| export declare function receiptShapeDefect(receipt: unknown): { | ||
| reason: string; | ||
| } | null; | ||
| /** | ||
| * Verify a Graneth governance receipt against a public key, entirely offline. | ||
@@ -96,0 +116,0 @@ * |
+28
-8
@@ -127,8 +127,21 @@ /** | ||
| /** | ||
| * The ways a file can fail to be a receipt at all. Every one of these is the | ||
| * DOCUMENT's defect, so none of them may be reported as "we could not check". | ||
| * The ways a file can fail to be a receipt at all — decided WITHOUT a public | ||
| * key, because not one of them needs one. | ||
| * | ||
| * ── WHY THIS IS EXPORTED, AND KEYLESS ─────────────────────────────────────── | ||
| * In 0.2.0 these checks were reachable only through verifyReceipt(), which | ||
| * needs a key to have been supplied and resolved first. So a receipt with its | ||
| * signed keyId stripped answered MALFORMED with `--key` and UNCHECKED without | ||
| * one — "the obstacle is on this side", which is a claim ABOUT THE DOCUMENT, | ||
| * and false for that file. Nothing was wrong with the classification; it was | ||
| * standing behind a step it did not depend on, and the commonest first run is | ||
| * the one that skips that step. | ||
| * | ||
| * Whether a document is a well-formed receipt is a property of the document. | ||
| * Keeping the answer keyless is what makes it impossible to accidentally make | ||
| * it conditional on the key again. | ||
| */ | ||
| function shapeDefect(receipt, keyId) { | ||
| export function receiptShapeDefect(receipt) { | ||
| if (!receipt || typeof receipt !== "object") { | ||
| return malformed(keyId, "this is not a JSON object, so it is not a receipt"); | ||
| return { reason: "this is not a JSON object, so it is not a receipt" }; | ||
| } | ||
@@ -138,14 +151,21 @@ const r = receipt; | ||
| if (!signedObject || typeof signedObject !== "object") { | ||
| return malformed(keyId, "there is no signed `attestation` object here — this is not a Graneth receipt"); | ||
| return { reason: "there is no signed `attestation` object here — this is not a Graneth receipt" }; | ||
| } | ||
| if (typeof r.signature !== "string" || r.signature.length === 0) { | ||
| return malformed(keyId, "this document carries no `signature` — it has never been signed by anyone"); | ||
| return { reason: "this document carries no `signature` — it has never been signed by anyone" }; | ||
| } | ||
| const so = signedObject; | ||
| if (!signedKeyId(so) && claimsToBeAnAttestation(so)) { | ||
| return malformed(keyId, "the signed object declares a version and an algorithm but names no keyId. Every receipt Graneth issues " + | ||
| "names its signing key INSIDE the signature — so this field has been removed since it was issued"); | ||
| return { | ||
| reason: "the signed object declares a version and an algorithm but names no keyId. Every receipt Graneth issues " + | ||
| "names its signing key INSIDE the signature — so this field has been removed since it was issued", | ||
| }; | ||
| } | ||
| return null; | ||
| } | ||
| /** The same rule, in the shape verifyReceipt returns. One implementation, two callers. */ | ||
| function shapeDefect(receipt, keyId) { | ||
| const defect = receiptShapeDefect(receipt); | ||
| return defect ? malformed(keyId, defect.reason) : null; | ||
| } | ||
| /** | ||
@@ -152,0 +172,0 @@ * Verify a Graneth governance receipt against a public key, entirely offline. |
+1
-1
| { | ||
| "name": "@graneth/verify", | ||
| "version": "0.2.0", | ||
| "version": "0.2.1", | ||
| "description": "Offline verifier for Graneth's signed governance receipts — checks the Ed25519 signature against a public key you already have. Zero network calls, zero runtime dependencies.", | ||
@@ -5,0 +5,0 @@ "type": "module", |
+7
-0
@@ -61,2 +61,9 @@ # @graneth/verify | ||
| Whether a file is a well-formed receipt is a property of that file, so it is | ||
| answered **without a key at all**: `graneth-verify receipt.json` with no `--key` | ||
| still reports `MALFORMED` for a document that is not one. (In `0.2.0` those | ||
| checks sat behind key resolution, so the same file answered `MALFORMED` with a | ||
| key and `UNCHECKED` without — under a sentence that blamed the reader. Fixed in | ||
| `0.2.1`.) | ||
| Two consequences of the same rule: every receipt Graneth issues names its | ||
@@ -63,0 +70,0 @@ signing key **inside** the signature, so a document declaring a version and an |
45021
7.43%795
6.71%112
6.67%