🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@veritasacta/verify

Package Overview
Dependencies
Maintainers
1
Versions
16
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@veritasacta/verify - npm Package Compare versions

Comparing version
0.2.5
to
0.3.0
+17
sigil.json
{
"sigil_version": 1,
"fingerprint": "dd0443f0",
"name": "Slow Reed",
"sigil_hash": "dd0443f0ae189b11d739e5e944b68fc955b3d777514d652a1f0af0a4e0b8666b",
"project_public_key": "fe665e861867cec7e171c0c13bbc873c3362079faef21f54df7804b7fb9ae8af",
"policy": {
"version": 2,
"package": "@veritasacta/verify",
"package_version": "0.3.0",
"source_hash": "e7af6bb636d2336bbdc07d508d5011cc12a15ad5ec83171a6cbb8f32da78645e",
"ietf_draft": "draft-farley-acta-signed-receipts-02",
"created_at": 1776079766532
},
"policy_hash": "789aa634fa4496b0feed688d9d96731691dc75303c722868a5311fb704feafa5",
"derived_at": "2026-04-13T11:29:26.532Z"
}
+264
-1

@@ -23,2 +23,3 @@ #!/usr/bin/env node

import { readFileSync } from 'node:fs';
import { createHash } from 'node:crypto';
import { dirname, join } from 'node:path';

@@ -43,3 +44,159 @@ import { fileURLToPath } from 'node:url';

const bold = (s) => isCI ? s : `\x1b[1m${s}\x1b[0m`;
const teal = (s) => isCI ? s : `\x1b[36m${s}\x1b[0m`;
const peach = (s) => isCI ? s : `\x1b[38;5;216m${s}\x1b[0m`;
const bgTeal = (s) => isCI ? s : `\x1b[46m${s}\x1b[0m`;
const bgPeach = (s) => isCI ? s : `\x1b[48;5;216m${s}\x1b[0m`;
// ── Sigil: Terminal Renderer ────────────────────────────────────
// Same algorithm as @scopeblind/passport sigil.ts — deterministic
// visual identity derived from Ed25519 public key via SHA-256.
// Rendered as 11×11 Unicode block art for terminal display.
const SIGIL_DOMAIN = 'scopeblind:sigil:v1';
const SIGIL_SEGMENTS = 6;
function sigilHashFromKey(publicKeyHex) {
return createHash('sha256')
.update(SIGIL_DOMAIN)
.update(Buffer.from(publicKeyHex, 'hex'))
.digest();
}
function deriveSigilGrid(hash) {
const extHash = createHash('sha256').update('ext:').update(hash).digest();
const b = (i) => i < 32 ? hash[i] : extHash[i - 32];
let idx = 0;
const diamond = {
top: b(idx++) % 3, right: b(idx++) % 3,
bottom: b(idx++) % 3, left: b(idx++) % 3,
};
const surround = [];
for (let i = 0; i < 4; i++) surround.push(b(idx++) % 3);
const innerRing = [];
for (let i = 0; i < SIGIL_SEGMENTS; i++) innerRing.push(b(idx++) % 3);
const midRing = [];
for (let i = 0; i < SIGIL_SEGMENTS; i++) midRing.push(b(idx++) % 3);
const outerRing = [];
for (let i = 0; i < SIGIL_SEGMENTS; i++) outerRing.push(b(idx++) % 3);
const corners = [];
for (let i = 0; i < 8; i++) corners.push(b(idx++) % 3);
return { diamond, surround, innerRing, midRing, outerRing, corners };
}
function sigilPassesFilter(g) {
const all = [
g.diamond.top, g.diamond.right, g.diamond.bottom, g.diamond.left,
...g.surround, ...g.innerRing, ...g.midRing, ...g.outerRing, ...g.corners,
];
let p = 0, s = 0;
for (const v of all) { if (v === 1) p++; if (v === 2) s++; }
const filled = p + s;
return p >= 4 && s >= 4 && filled >= 10 && filled <= 28;
}
function deriveFilteredSigilGrid(publicKeyHex) {
let nonce = 0;
const keyBuf = Buffer.from(publicKeyHex, 'hex');
while (nonce < 256) {
const hash = nonce === 0
? sigilHashFromKey(publicKeyHex)
: createHash('sha256').update(SIGIL_DOMAIN).update(keyBuf).update(Buffer.from([nonce])).digest();
const grid = deriveSigilGrid(hash);
if (sigilPassesFilter(grid)) {
return { grid, fingerprint: hash.toString('hex').slice(0, 8) };
}
nonce++;
}
const hash = sigilHashFromKey(publicKeyHex);
return { grid: deriveSigilGrid(hash), fingerprint: hash.toString('hex').slice(0, 8) };
}
/**
* Render sigil as 11×11 terminal block art.
*
* Grid layout (11×11):
* Cells [0-1, 9-10] rows/cols = corners (4 quadrants, 2 splits each)
* Cells [2, 8] ring = outer ring (6 segments)
* Cells [3, 7] ring = mid ring (6 segments)
* Cells [4, 6] ring = inner ring (6 segments)
* Cell 5 ring = surround + diamond center
*
* Each pixel maps to an angular sector and radial band.
* States: 0=background, 1=primary (teal ●), 2=secondary (peach ○)
*/
function renderTerminalSigil(publicKeyHex) {
const { grid, fingerprint } = deriveFilteredSigilGrid(publicKeyHex);
const SIZE = 11;
const cx = 5, cy = 5;
const R = 5.5; // outer radius (flush with 11×11)
// Radial bands (same proportions as SVG renderer)
const outerR = R;
const midR = R * 0.72;
const innerR = R * 0.44;
const surroundR = innerR * 0.65;
const diamondR = surroundR * 0.6;
const lines = [];
for (let y = 0; y < SIZE; y++) {
let row = ' ';
for (let x = 0; x < SIZE; x++) {
const dx = x - cx;
const dy = y - cy;
const dist = Math.sqrt(dx * dx + dy * dy);
const angle = Math.atan2(dy, dx);
// Normalize angle to [0, 2π)
const normAngle = angle < 0 ? angle + 2 * Math.PI : angle;
const segIdx = Math.floor((normAngle / (2 * Math.PI)) * SIGIL_SEGMENTS) % SIGIL_SEGMENTS;
let state = 0; // background
if (dist <= diamondR) {
// Diamond center — determine quadrant
// Top: angle ∈ [-π, -π/2], Right: [-π/2, 0], Bottom: [0, π/2], Left: [π/2, π]
if (angle >= -Math.PI && angle < -Math.PI / 2) state = grid.diamond.left;
else if (angle >= -Math.PI / 2 && angle < 0) state = grid.diamond.top;
else if (angle >= 0 && angle < Math.PI / 2) state = grid.diamond.right;
else state = grid.diamond.bottom;
} else if (dist <= surroundR) {
// Surround — 4 wedges
const sIdx = Math.floor(((normAngle + Math.PI / 4) % (2 * Math.PI)) / (Math.PI / 2)) % 4;
state = grid.surround[sIdx];
} else if (dist <= innerR) {
state = grid.innerRing[segIdx];
} else if (dist <= midR) {
state = grid.midRing[segIdx];
} else if (dist <= outerR) {
state = grid.outerRing[segIdx];
} else {
// Corner region — outside the circle, inside the square
const cIdx = (y < cy ? 0 : 2) + (x < cx ? 0 : 1); // TL=0, TR=1, BL=2, BR=3
// Split: which half of the corner?
const cornerAngle = Math.atan2(y - cy, x - cx);
const cornerMidAngles = [
-3 * Math.PI / 4, // TL
-Math.PI / 4, // TR
3 * Math.PI / 4, // BL
Math.PI / 4, // BR
];
const half = ((cIdx === 0 || cIdx === 1)
? (cornerAngle < cornerMidAngles[cIdx] ? 0 : 1)
: (cornerAngle > cornerMidAngles[cIdx] ? 0 : 1));
state = grid.corners[cIdx * 2 + half];
}
if (state === 1) row += teal('█');
else if (state === 2) row += peach('▓');
else row += dim('·');
}
lines.push(row);
}
lines.push(` ${dim('sigil:')} ${teal(fingerprint)}`);
return lines.join('\n');
}
// ── CLI Argument Parsing ────────────────────────────────────────

@@ -59,2 +216,3 @@

selfTest: false,
selfCheck: false,
};

@@ -80,2 +238,4 @@

opts.selfTest = true;
} else if (arg === '--self-check') {
opts.selfCheck = true;
} else if (!arg.startsWith('-')) {

@@ -108,3 +268,4 @@ opts.file = arg;

--verbose, -v Show detailed verification info
--self-test Verify bundled sample artifacts (proves the verifier works)
--self-test Verify bundled sample artifacts (proves the verifier works)
--self-check Verify this verifier is the canonical, unmodified release
--help, -h Show this help

@@ -327,2 +488,7 @@

if (opts.json) {
// Include sigil fingerprint in JSON output
if (result.valid && result.publicKey && result.publicKey.length === 64) {
const { fingerprint } = deriveFilteredSigilGrid(result.publicKey);
result.sigil_fingerprint = fingerprint;
}
console.log(JSON.stringify(result, null, 2));

@@ -335,2 +501,8 @@ return;

// Show sigil for valid verifications
if (result.valid && result.publicKey && result.publicKey.length === 64 && !isCI) {
console.log('');
console.log(renderTerminalSigil(result.publicKey));
}
console.log(`\n${icon} Signature: ${status}`);

@@ -398,2 +570,8 @@ console.log(` Format: ${result.format || 'unknown'}`);

// Show sigil of the test key — visual proof of which key is being tested
if (!isCI) {
console.log(renderTerminalSigil(testKey));
console.log('');
}
// Test sample receipt

@@ -440,2 +618,87 @@ try {

// ── Self-check: verify the verifier itself ──────────────────────
// Computes SHA-256 of the installed cli.js, compares to the Sigil
// commitment that was generated at release time. If they match,
// this is the canonical, unmodified verifier. If not, the code
// has been modified since release.
if (opts.selfCheck) {
const __dirname = dirname(fileURLToPath(import.meta.url));
const sigilPath = join(__dirname, 'sigil.json');
console.log(`\n${bold('@veritasacta/verify — self-check')}\n`);
// Load the Sigil commitment
let sigil;
try {
sigil = JSON.parse(readFileSync(sigilPath, 'utf-8'));
} catch {
console.log(` ${red('✗')} No sigil.json found — this verifier has no Sigil commitment.`);
console.log(` This may be a development build or a fork.`);
console.log('');
process.exit(2);
}
// Compute the hash of the installed cli.js
const cliPath = join(__dirname, 'cli.js');
const cliContent = readFileSync(cliPath);
const installedHash = createHash('sha256').update(cliContent).digest('hex');
// Compare to the committed hash
const committedHash = sigil.policy?.source_hash;
const hashMatch = installedHash === committedHash;
// Re-derive the Sigil from the project key + policy
const policyJson = JSON.stringify(sigil.policy);
const policyHash = createHash('sha256').update(policyJson).digest('hex');
const policyMatch = policyHash === sigil.policy_hash;
// Re-derive the Sigil hash
const domain = Buffer.from('scopeblind:sigil:v2');
const pubKey = Buffer.from(sigil.project_public_key, 'hex');
const policyBuf = Buffer.from(policyHash, 'hex');
const input = Buffer.concat([domain, pubKey, policyBuf, Buffer.from([0])]);
const rederived = createHash('sha256').update(input).digest('hex');
const sigilMatch = rederived === sigil.sigil_hash;
const allGood = hashMatch && policyMatch && sigilMatch;
if (allGood) {
// Show the Sigil visual of the project key
if (!isCI) {
console.log(renderTerminalSigil(sigil.project_public_key));
console.log('');
}
console.log(` ${green('✓')} Canonical verifier — ${green(sigil.name)}`);
console.log(` Sigil: ${teal(sigil.fingerprint)}`);
console.log(` Version: ${sigil.policy.package_version}`);
console.log(` Package: ${sigil.policy.package}`);
console.log(` Source: ${dim(installedHash.slice(0, 16) + '...')} ${green('matches commitment')}`);
console.log(` Policy: ${dim(policyHash.slice(0, 16) + '...')} ${green('matches commitment')}`);
console.log(` Sigil: ${dim(rederived.slice(0, 16) + '...')} ${green('matches commitment')}`);
console.log('');
console.log(` ${dim('This verifier is the unmodified canonical release.')}`);
console.log(` ${dim('The source code has not been changed since it was published.')}`);
} else {
console.log(` ${red('✗')} Modified verifier — NOT the canonical release\n`);
if (!hashMatch) {
console.log(` Source: ${red('MISMATCH')}`);
console.log(` Installed: ${installedHash.slice(0, 32)}...`);
console.log(` Expected: ${committedHash?.slice(0, 32) || 'n/a'}...`);
}
if (!policyMatch) {
console.log(` Policy: ${red('MISMATCH')} — sigil.json may have been tampered with`);
}
if (!sigilMatch) {
console.log(` Sigil: ${red('MISMATCH')} — the commitment chain is broken`);
}
console.log('');
console.log(` ${yellow('This verifier has been modified since the canonical release.')}`);
console.log(` ${yellow('It may be a fork, a development build, or a tampered copy.')}`);
console.log(` ${dim('Get the canonical verifier: npm install @veritasacta/verify')}`);
}
console.log('');
process.exit(allGood ? 0 : 1);
}
// Read input

@@ -442,0 +705,0 @@ let input;

+5
-2
{
"name": "@veritasacta/verify",
"version": "0.2.5",
"version": "0.3.0",
"mcpName": "io.github.tomjwxf/veritasacta-verify",

@@ -13,2 +13,3 @@ "description": "CLI tool to verify signed artifacts (receipts, manifests, tickets). Works offline.",

"cli.js",
"sigil.json",
"README.md",

@@ -39,3 +40,5 @@ "samples/",

"test": "node test/conformance.js",
"self-test": "node cli.js samples/sample-receipt.json --key d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a && node cli.js samples/sample-bundle.json --bundle"
"self-test": "node cli.js samples/sample-receipt.json --key d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a && node cli.js samples/sample-bundle.json --bundle",
"self-check": "node cli.js --self-check",
"generate-sigil": "node generate-sigil.mjs"
},

@@ -42,0 +45,0 @@ "repository": {

+72
-34
# @veritasacta/verify
**Verify signed artifacts offline.** No accounts. No API calls. No ScopeBlind dependency.
**Verify signed artifacts offline.** No accounts. No API calls. No trust required.
> Apache-2.0 licensed. Works offline. Requires zero trust in ScopeBlind or anyone else.
> Apache-2.0 licensed. Works offline. The verifier verifies itself.
## Sigil: Slow Reed
```
Sigil: Slow Reed
Fingerprint: dd0443f0
Version: 0.3.0
```
This release carries a cryptographic Sigil — a commitment to the exact source code published in this package. Run `--self-check` to verify you have the canonical, unmodified verifier:
```bash
npx @veritasacta/verify --self-check
```
```
@veritasacta/verify — self-check
✓ Canonical verifier — Slow Reed
Sigil: dd0443f0
Version: 0.3.0
Package: @veritasacta/verify
Source: e7af6bb636d2336b... matches commitment
Policy: 789aa634fa4496b0... matches commitment
Sigil: dd0443f0ae189b11... matches commitment
This verifier is the unmodified canonical release.
The source code has not been changed since it was published.
```
If you see "Modified verifier — NOT the canonical release," the code has been changed. Get the canonical version: `npm install @veritasacta/verify`
**Why this matters:** any fork of this verifier can rename itself, but it cannot produce a matching Sigil without the project's private key. The `--self-check` flag lets anyone confirm they are running the real thing.
## Prove It

@@ -23,4 +56,2 @@

That's it. The verifier works. Now verify your own receipts:
## Usage

@@ -30,3 +61,3 @@

# Verify a receipt with a known public key
npx @veritasacta/verify receipt.json --key d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a
npx @veritasacta/verify receipt.json --key <public-key-hex>

@@ -44,2 +75,5 @@ # Verify a receipt, fetching key from JWKS

npx @veritasacta/verify receipt.json --key <hex> --json
# Verify the verifier itself
npx @veritasacta/verify --self-check
```

@@ -52,3 +86,3 @@

1. Strips the `signature` field from the artifact
2. Canonicalizes the remaining JSON (sorted keys, deterministic — JCS-style)
2. Canonicalizes the remaining JSON (JCS — RFC 8785)
3. SHA-256 hashes the canonical bytes

@@ -66,2 +100,3 @@ 4. Verifies the Ed25519 signature against the hash using the public key

| Passport SignedEnvelope | `{ payload, signature: { alg, kid, sig } }` |
| IETF draft envelope | `{ payload, signature: { alg, kid, sig } }` per draft-farley-acta-signed-receipts |
| Audit bundle | `{ receipts: [...], verification: { signing_keys: [...] } }` |

@@ -79,3 +114,4 @@

| `--verbose, -v` | Show detailed verification info |
| `--self-test` | Verify bundled sample artifacts |
| `--self-test` | Verify bundled sample artifacts (proves the verifier works) |
| `--self-check` | Verify this verifier is the canonical, unmodified release |
| `--help, -h` | Show help |

@@ -87,47 +123,49 @@

|------|---------|-----------|
| `0` | Signature valid | **Proven authentic** — the Ed25519 math confirms this artifact has not been modified since signing |
| `0` | Signature valid | **Proven authentic** — the Ed25519 math confirms the artifact has not been modified since signing |
| `1` | Signature invalid | **Proven tampered** — the signature does not match the payload. This is a security event. |
| `2` | Verifier error | **Undecidable** — malformed input, missing key, unparseable JSON. The check could not be performed. |
The distinction matters: exit 1 is evidence of tampering (the math was tested and failed).
Exit 2 is an operational error (the math was never tested). These demand different responses.
The distinction matters: exit 1 is evidence of tampering. Exit 2 is an operational error. These demand different responses.
## Where Do Receipts Come From?
## Cross-System Interop
Receipts are generated by [protect-mcp](https://www.npmjs.com/package/protect-mcp), which wraps MCP tool servers and signs every decision (allow, deny, rate-limit, require_approval) as a v2 artifact.
This verifier accepts receipts from multiple governance frameworks:
```bash
# Generate receipts
npx protect-mcp --policy policy.json -- node your-mcp-server.js
| System | Receipt type | Verified |
|--------|-------------|----------|
| [protect-mcp](https://www.npmjs.com/package/protect-mcp) | Cedar policy + execution receipts | Exit 0 |
| [Agent Passport System](https://www.npmjs.com/package/agent-passport-system) | Delegation + evaluation + outcome receipts | Exit 0 |
| [AgentID](https://pypi.org/project/agentid/) | Identity verification attestations | Exit 0 |
# Later, verify them
npx @veritasacta/verify receipt.json --key <gateway-public-key>
```
All three use the same IETF draft envelope format with JCS canonicalization and Ed25519 signatures.
## Conformance Testing
## Sigil Commitment
```bash
# Run the full conformance test suite (20 tests)
npm test
```
The `sigil.json` file in this package contains:
Tests verify:
- All known test vectors pass (4 vectors with hash match)
- Tampered artifacts are correctly rejected
- Wrong public keys are correctly rejected
- Sample receipt and bundle verify
- Format detection works
| Field | Value | Purpose |
|-------|-------|---------|
| `fingerprint` | `dd0443f0` | Short identifier for this release's Sigil |
| `name` | Slow Reed | Human-readable name (deterministic from fingerprint) |
| `project_public_key` | `fe665e86...` | Veritas Acta project Ed25519 public key |
| `policy.source_hash` | `e7af6bb6...` | SHA-256 of cli.js at release time |
| `policy.package_version` | `0.3.0` | npm version this Sigil commits to |
| `policy.ietf_draft` | `draft-farley-acta-signed-receipts-02` | IETF spec version implemented |
The Sigil is derived from: `SHA-256("scopeblind:sigil:v2" || project_public_key || SHA-256(policy_json))`. Anyone with the public key and the source code can independently re-derive the Sigil and confirm it matches.
## Why Trust This?
- **Self-checking** — the verifier verifies itself (`--self-check`)
- **Apache-2.0 licensed** — patent grant included, no vendor lock-in
- **Open schemas** — the artifact format is public
- **Open schemas** — the artifact format is an IETF Internet-Draft
- **Test vectors** — deterministic keypairs with known-good signatures
- **Works offline** — no API calls, no accounts, no ScopeBlind servers
- **Verification is independent** — you don't need to trust ScopeBlind
- **Works offline** — no API calls, no accounts, no servers contacted
- **Cross-system** — verifies receipts from multiple independent governance frameworks
- **Sigil'd** — every release carries a cryptographic commitment to its own source code
> Any platform can log what its agents do. Very few will let you verify those logs without trusting them.
> Any platform can log what its agents do. Very few will let you verify those logs without trusting them. Even fewer will let you verify the verifier.
## License
Apache-2.0 — [veritasacta.com](https://veritasacta.com) | [IETF Draft](https://datatracker.ietf.org/doc/draft-farley-acta-signed-receipts/)
Apache-2.0 — [veritasacta.com](https://veritasacta.com) | [IETF Draft](https://datatracker.ietf.org/doc/draft-farley-acta-signed-receipts/) | [Protocol](https://github.com/VeritasActa/Acta)