npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

pcrzero

v0.1.3

Published

PCRZERO offline CLI + SDK — install: npm i -g pcrzero → pcr0 verify (A-CLI: zero phone-home)

Downloads

714

Readme

pcrzero

Install: npm i -g pcrzero → run pcr0 verify

npm i -g pcrzero
pcr0 verify --receipt ./receipt.wire --keys ./keys.json
# or without global install:
npx pcrzero pcr0 verify --receipt ./receipt.wire --keys ./keys.json

Package name pcrzero · command pcr0 (LEAD #162 G1b). Unscoped brand pcr0 as package name is blocked by npm (similarity to crc).

Inference delivery receipt specification

Public SoT (minimum surface): Scytalex-LLC/pcrzero-receipt-spec

Working copies in this (private) SDK: SPEC.md, tests/fixtures/inference-receipt-vectors-v1.json.

This SPEC defines inference delivery receipts only. Attestation / policy-verify receipts from /v1/verify are a sibling product (shared dual-sig envelope; different body — see SPEC §1.1).

What this is

Two entry points, deliberately separated:

| Import | Contains | For | |---|---|---| | pcrzero | HTTP client, resources | servers calling the API | | pcrzero/receipt | offline verifyReceipt only — no HTTP, no Node built-ins | edge runtimes, browsers, auditors | | pcrzero/lock | fail-closed assertPayoutLock on verifyReceipt | payout APIs — sit on the wire that moves money | | pcrzero/inference | dual-sign inference receipt kit | PoM / auditor offline | | pcrzero/inclusion | verifyInclusion | day-tree proofs |

The split is the point. A receipt is meant to be verifiable by someone who has no account, no API key, and no network — so the code that does that must not drag an HTTP client along with it.

Quickstart

import { PCRZero } from 'pcrzero';
const pcrzero = new PCRZero({ apiKey: process.env.PCRZERO_API_KEY! });

const { verdict, reasons, receipt } = await pcrzero.verify({
  document: base64Doc,
  policy: { docType: 'nitro', measurements: { pcr0: [EXPECTED_PCR0] } },
});

if (verdict !== 'pass') throw new Error(`attestation rejected: ${reasons.join(', ')}`);
await db.receipts.put(receipt);   // opaque string; verifiable offline, forever

tests/quickstart.test.ts is this exact snippet compiled against the real exported types, so the documented ergonomics cannot drift from the shipped API.

Verifying a receipt offline

The key set is public and unauthenticated, so a verifier needs no account and no client — a plain fetch is the expected path. toReceiptKeys decodes it into what the verifier takes; it accepts the raw wire shape (ml_dsa_public_key) and the typed client's camelCase (mlDsaPublicKey) interchangeably, and throws naming the field if key material is the wrong length or not hex.

import { verifyReceipt, toReceiptKeys } from 'pcrzero/receipt';

// Fetch once and cache — the endpoint is Cache-Control: public, max-age=3600.
const keys = toReceiptKeys(await (await fetch('https://api.pcrzero.com/v1/keys')).json());

const result = verifyReceipt(receipt, keys);   // pure: no network, no clock, no API key
if (!result.valid) throw new Error(result.reasons[0]);
console.log(result.body.verdict, result.body.measurements.pcr0, result.body.iat);

Key lifecycle — presence in the set is not validity forever

Each served key may carry a status: active (absent means active), retired (honestly rotated out — its old receipts keep verifying), or revoked (compromised — toReceiptKeys excludes it, so its receipts fail unknown_signing_key). The enum is closed; an unknown status throws rather than being guessed at. notAfter is advisory metadata and never enforced by the SDK — a receipt signed before a key's end-of-life must keep verifying after it.

Because the verifier is offline, revocation reaches you only when you refresh: re-fetch GET /v1/keys at least every 24 hours. An auditor examining a revoked-key receipt deliberately can still construct a ReceiptKey by hand — the exclusion is a default, not a lock.

Fetching the key set is the only networked step, and it is deliberately separable: pin the keys once and every later verification is fully offline, forever.

Payout lock (pcrzero/lock, 0.1.2)

Verify-only, no account, fail closed. Missing / invalid / fail receipts do not clear. Never charges. Never calls Scytalex on the hot path. Pin the keyset once; then this is pure.

import { toReceiptKeys } from 'pcrzero/receipt';
import { assertPayoutLock } from 'pcrzero/lock';

const keys = toReceiptKeys(pinnedKeysetJson); // from GET /v1/keys, cached
const seen = new Set<string>(); // persist across requests

function hexNonce(n: Uint8Array): string {
  let h = '';
  for (const b of n) h += b.toString(16).padStart(2, '0');
  return h;
}

export function clearPayout(receipt: string | undefined): void {
  const d = assertPayoutLock({ receipt, keys, seenNonces: seen });
  if (!d.allowed) throw new Error(d.reason); // missing_receipt | invalid_receipt | verdict_not_pass | …
  seen.add(hexNonce(d.body.nonce));
  // only now move money
}

verdict: "fail" is a bounce (verdict_not_pass). Optional nowMs + maxAgeSec, policyIds, and envs are extra allowlists — omit them and they do not run.

Every receipt carries both an Ed25519 and an ML-DSA-44 signature, always. verifyReceipt defaults to full conformance and checks both; either failing is a rejection. Downgrading is explicit and visible at the call site:

verifyReceipt(receipt, LIVE_KEYS, { conformance: 'classical-only' });  // NO PQ guarantee

There is no silent fallback. The ML-DSA import is unconditional and at module scope, so a runtime missing the post-quantum dependency fails to load the module rather than quietly degrading to a classical-only check — a verifier that believes it is checking a PQ signature but is not would be worse than one that never had it.

The envelope is a fixed five elements, and that is a security property rather than a style choice. A four-element envelope is rejected in every conformance class, including classical-only, so an attacker cannot strip the post-quantum signature to force a weaker check. Two golden vectors exist solely to keep that true.

result.body uses the wire-literal field names from the signed bytes (doc_type, doc_sha256) rather than camelCase. It is the signed artifact: what you inspect should be exactly what was signed, with no translation layer in between.

Two things that surprise people

A failing verdict is not an error. verdict: "fail" resolves normally with HTTP 200 and carries the measurements that explain the failure. A thrown PCRZeroApiError means we could not evaluate — quota, auth, a malformed document — which is a categorically different thing. Branch on error.code, never on error.message: the code is the contract, the prose is not.

Measurement names are data, not field names. The SDK converts camelCase↔snake_case at the wire boundary, but never touches the keys inside measurements. Those are wire-literal register names chosen by the docType (pcr0, and for sev-snp host_data, id_key_digest), and the offline verifier reads them straight out of CBOR without passing through the case codec. Converting them here would give one logical field two different names depending on which API you reached for.

Dependencies

Runtime dependencies are minimal and exist only for the receipt verifier:

| Package | Why | |---|---| | @noble/curves | Ed25519 verification (receipt classical signature) | | @noble/post-quantum | ML-DSA-44 verification (receipt PQ signature) |

That is the whole list, and a test enforces it: tests/receipt/bundle.test.ts walks the receipt subpath's transitive import graph and asserts it uses exactly the declared runtime dependencies, imports no Node built-in, and reaches no module outside src/receipt/. It caught a declared-but-unused @noble/hashes on its first run.

There is deliberately no CBOR library. The canonical codec is hand-rolled and narrow, because a third-party library's "deterministic mode" staying byte-stable across minor versions is not a guarantee a signature contract can rest on (spec §6).

Bundling for a browser: do NOT flatten @noble/hashes

A correct install has two copies, and that is not a mistake to clean up:

| Copy | Version | Needed by | |---|---|---| | top level | 1.8.x | @noble/curves (Ed25519) | | nested under @noble/post-quantum | 2.2.x | @noble/post-quantum (ML-DSA-44) |

Flatten them — npm dedupe, an overrides entry, a bundler dedupe/vendor copy, or an import map without scopes — and the two abytes signatures mix. ML-DSA verification then throws from deep inside noble, where nothing names the real cause: the failure looks like a broken receipt or a corrupt key, and the post-quantum half is exactly the half you cannot afford to debug by guesswork.

Use an import-map scope (or the bundler's equivalent dedupe-off) to keep them separate. tests/receipt/resolution.test.ts pins the split on our side and runs a real ML-DSA sign/verify under it, but the flattening happens in your build, where our tests cannot reach — so this warning is the guard. (Found by s1 bundling the demo console, 2026-08-07.)

The HTTP client has no runtime dependencies at all — it uses global fetch.

Offline CLI: pcr0 (#158 · PoM B3)

Product CLI for inference receipt dual-sign verify (schema v1 / #117). Trust base is caller-supplied files only — offline by default, zero phone-home (A-CLI). Does not claim training-data provenance (A-CLI-5). The live one-screen contract is pcr0 help (what it proves, what it does not, exits 0/1/2). Usage errors name the gap and a next flag; they do not reprint the full help.

# published (#162 G1b — package pcrzero, command pcr0):
npm i -g pcrzero
pcr0 help
pcr0 verify --receipt ./receipt.wire --keys ./keys.json
# or:
npx pcrzero pcr0 verify --receipt ./receipt.wire --keys ./keys.json

# optional local inclusion (all three required together; still offline):
pcr0 verify \
  --receipt ./receipt.wire \
  --keys ./keys.json \
  --proof ./inclusion-proof.json \
  --checkpoint ./checkpoint.json \
  --anchors ./anchors.json

| Exit | Meaning | |---|---| | 0 | valid receipt (and inclusion if requested) | | 1 | usage / IO / incomplete flags / network mode refused | | 2 | invalid receipt or inclusion |

--allow-network is rejected in v1 (fail closed). Keys file shape:

{
  "keys": [
    {
      "kid": "<hex>",
      "ml_dsa_public_key": "<hex>",
      "ed25519_public_key": "<hex>"
    }
  ]
}

Stdout on success is JSON with valid, receipt_id, trust_base (caller-supplied / not-checked-this-command), and a product disclaimer.

Format spec (public): https://github.com/Scytalex-LLC/pcrzero-receipt-spec/blob/main/SPEC.md · R4 vectors: same repo inference-receipt-vectors-v1.json. CLI tests: tests/cli-verify-inference.test.ts.

Development

npm test          # vitest
npm run typecheck # tsc --noEmit
npm run build     # tsc -> dist/

fetch is injectable via the client config, so tests exercise the real request pipeline against real Response objects rather than a mocking library.