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

@geoclear/verify-receipt

v1.2.1

Published

Verify GeoClear signed receipts and Pattern 4B hybrid Evidence Bundles (ECDSA P-384 + ML-DSA-87 / FIPS 204) — offline, no network, any auditor.

Readme

@geoclear/verify-receipt

Verify a GeoClear signed receipt in any language, offline, without calling GeoClear servers. Proves to a regulator years from now exactly what GeoClear returned at exactly what moment.

Status (2026-05-05): PUBLISHED. @geoclear/[email protected] is live on npm under MIT license. Source mirrored in this repo for transparency; npm is the authoritative distribution. Install with npm install @geoclear/verify-receipt. See https://www.npmjs.com/package/@geoclear/verify-receipt.

For the broader MCP / Receipts / Notary architecture and how this verifier fits in, see the developer overview. This README is verifier-specific; the overview is the conceptual map.

What a signed receipt is

Every GeoClear API response (JSON) comes with an X-GeoClear-Receipt header. That header value is a JWS (RFC 7515) with this payload:

{
  "iss": "https://geoclear.io",
  "iat": 1745370001,
  "sub": "req_...",
  "api_version": "v1",
  "endpoint": "/v1/risk",
  "req_hash": "sha256:...",
  "resp_hash": "sha256:...",
  "status": 200
}

Signed with ECDSA on curve P-384 (alg: ES384), key held in AWS KMS, public key distributed at https://geoclear.io/.well-known/jwks.json.

What it attests to

A signed receipt proves, cryptographically, what GeoClear returned at what moment. It does not attest to the ground-truth correctness of the underlying flood map, census tract, or climate model. If FEMA updates a flood-zone boundary six months from now, your old receipt is still valid — it accurately records what GeoClear returned on the day you asked.

This distinction is non-negotiable. We're in the attestation-of-our- statements business, not the insurance-of-ground-truth business.

Node.js

// Requires: npm i jose
const { jwtVerify, createRemoteJWKSet } = require('jose');
const crypto = require('crypto');

const JWKS = createRemoteJWKSet(new URL('https://geoclear.io/.well-known/jwks.json'));

function canonicalize(v) {
  if (v === null || typeof v !== 'object') return JSON.stringify(v);
  if (Array.isArray(v)) return '[' + v.map(canonicalize).join(',') + ']';
  const keys = Object.keys(v).sort();
  return '{' + keys.map(k => JSON.stringify(k) + ':' + canonicalize(v[k])).join(',') + '}';
}
const sha256 = (s) => crypto.createHash('sha256').update(s).digest('hex');

async function verifyGeoClearReceipt({ receipt, requestMethod, requestPath, requestBody, responseBody }) {
  const { payload } = await jwtVerify(receipt, JWKS, {
    issuer: 'https://geoclear.io',
    algorithms: ['ES384'],
  });

  const reqRepr = `${requestMethod} ${requestPath}\n${requestBody ? canonicalize(requestBody) : ''}`;
  const reqHash = 'sha256:' + sha256(reqRepr);
  const respHash = 'sha256:' + sha256(canonicalize(responseBody));

  if (payload.req_hash !== reqHash)   throw new Error('request was tampered or differs from what GeoClear received');
  if (payload.resp_hash !== respHash) throw new Error('response was tampered');
  return payload;  // includes iat (timestamp), sub (request ID), status
}

module.exports = { verifyGeoClearReceipt };

Python

# Requires: pip install pyjwt cryptography requests
import hashlib, json, requests, jwt
from jwt.algorithms import ECAlgorithm

def canonicalize(v):
    return json.dumps(v, sort_keys=True, separators=(',', ':'))

def sha256(s):
    return hashlib.sha256(s.encode() if isinstance(s, str) else s).hexdigest()

def verify_geoclear_receipt(receipt, request_method, request_path, request_body, response_body):
    jwks = requests.get('https://geoclear.io/.well-known/jwks.json').json()
    header = jwt.get_unverified_header(receipt)
    key_jwk = next(k for k in jwks['keys'] if k['kid'] == header['kid'])
    key = ECAlgorithm.from_jwk(json.dumps(key_jwk))

    claims = jwt.decode(receipt, key=key, algorithms=['ES384'], issuer='https://geoclear.io')

    req_repr = f"{request_method} {request_path}\n{canonicalize(request_body) if request_body else ''}"
    req_hash = 'sha256:' + sha256(req_repr)
    resp_hash = 'sha256:' + sha256(canonicalize(response_body))

    assert claims['req_hash']  == req_hash,  'request was tampered or differs from what GeoClear received'
    assert claims['resp_hash'] == resp_hash, 'response was tampered'
    return claims

Browser (vanilla JS, no dependencies besides jose)

<script type="module">
import { jwtVerify, createRemoteJWKSet } from 'https://esm.sh/jose@5';

const JWKS = createRemoteJWKSet(new URL('https://geoclear.io/.well-known/jwks.json'));

async function sha256Hex(str) {
  const buf = new TextEncoder().encode(str);
  const hash = await crypto.subtle.digest('SHA-256', buf);
  return [...new Uint8Array(hash)].map(b => b.toString(16).padStart(2, '0')).join('');
}
function canonicalize(v) {
  if (v === null || typeof v !== 'object') return JSON.stringify(v);
  if (Array.isArray(v)) return '[' + v.map(canonicalize).join(',') + ']';
  return '{' + Object.keys(v).sort().map(k => JSON.stringify(k) + ':' + canonicalize(v[k])).join(',') + '}';
}

async function verify(receipt, method, path, reqBody, respBody) {
  const { payload } = await jwtVerify(receipt, JWKS, {
    issuer: 'https://geoclear.io', algorithms: ['ES384'],
  });
  const reqRepr = `${method} ${path}\n${reqBody ? canonicalize(reqBody) : ''}`;
  const reqHash = 'sha256:' + await sha256Hex(reqRepr);
  const respHash = 'sha256:' + await sha256Hex(canonicalize(respBody));
  if (payload.req_hash !== reqHash)   throw new Error('request tampered');
  if (payload.resp_hash !== respHash) throw new Error('response tampered');
  return payload;
}
</script>

Future language support

  • Go: planned (go get github.com/shaileshjgd/geoclear-verify-receipt)
  • Rust: planned (cargo add geoclear-verify-receipt)
  • Ruby: nice-to-have
  • Java: nice-to-have

PRs welcome — see the contribution guide if present, or file an issue first to discuss substantive changes.

License

MIT. Published as @geoclear/verify-receipt on npm. See https://www.npmjs.com/package/@geoclear/verify-receipt.


v1.2.0 — Pattern 4B hybrid bundle verification (Q-1284, EPIC-2026-0041)

Adds verifyBundle() for the Pattern 4B hybrid Evidence Bundle — a ZIP that combines the legacy ECDSA P-384 JWS with a post-quantum ML-DSA-87 (FIPS 204) signature sibling artifact. The two signatures cover the SAME canonical bytes and are cryptographically anchored to each other via the JWS payload's pqc_pubkey_fingerprint claim (the "ECDSA-chain anchor"). Per CNSA 2.0 security category 5; ML-DSA-87 is the parameter set aligned with NSA CNSA 2.0 for NSS workloads for National Security Systems.

CLI usage

npm install -g @geoclear/verify-receipt
geoclear-verify-receipt ./evidence-bundle.zip

Expected output — VALID hybrid bundle

GeoClear Receipt Verifier v1.2.0
Bundle: /path/to/evidence-bundle.zip

[OK]   Canonical payload bytes hash: e3b0c44298fc1c149afbf4c8996fb924...
[OK]   ECDSA P-384 verified via KMS HSM anchor
[OK]   ECDSA chain anchors ML-DSA-87 public key fingerprint
[OK]   ML-DSA-87 signature verified under CNSA 2.0-aligned federal crypto profile
       Standard: NIST FIPS 204 algorithm · NIST security category 5 · Key lifecycle: ephemeral-per-enclave-instance

Bundle VALID. Verification completed in 47ms.
Mode: hybrid-ecdsa-p384-mldsa-87
Issuer: https://geoclear.io
ML-DSA-87 pubkey fingerprint: sha384:7908c2b841764c4f4ba2b7f125e8ab8e...

Negative-case rehearsal — what tamper detection looks like

The verifier produces a specific terminal output for each of the three known tamper attack vectors. APL / NIST / FIPS auditors typically ask to see these LIVE — practice them before the demo.

Case (a) — modified canonical payload (response body bytes changed)

When the canonical body bytes in the bundle don't match the JWS payload's resp_hash claim, the ECDSA verification at step 1 fails immediately:

GeoClear Receipt Verifier v1.2.0
Bundle: /path/to/tampered-payload.zip

[FAIL] ECDSA P-384 verification failed via KMS HSM anchor
       ECDSA_VERIFY_FAILED: signature verification failed (signature mismatch)

Bundle INVALID. ECDSA chain broken.
Exit code: 1 (ECDSA_VERIFY_FAILED)

To rehearse: open the bundle, edit a byte in payload/canonical-payload.bytes, re-zip, run the verifier. The first [FAIL] arrives in <50ms — the chain broke at the foundational layer.

Case (b) — modified ML-DSA-87 public key (downgrade / swap attack)

If an attacker swaps signature/mldsa-pubkey.json for a key they control (hoping the ML-DSA signature is also theirs), the sha384 fingerprint they recompute won't match the one the ECDSA chain anchors:

GeoClear Receipt Verifier v1.2.0
Bundle: /path/to/swapped-pubkey.zip

[OK]   ECDSA P-384 verified via KMS HSM anchor
[FAIL] ECDSA chain does not anchor ML-DSA-87 public key fingerprint
       ECDSA_CHAIN_FINGERPRINT_MISMATCH: ECDSA chain does not anchor ML-DSA public key. Expected (from JWS payload claim): 7908c2b841764c4f4ba2b7f1... Got (recomputed from signature/mldsa-pubkey.json): a3f1d92e6c5b4783b9e0f4a8... Possible downgrade/swap attack on the ML-DSA pubkey.

Bundle INVALID. Possible downgrade attack on the ML-DSA pubkey.
Exit code: 2 (ECDSA_CHAIN_FINGERPRINT_MISMATCH)

To rehearse: open the bundle, swap signature/mldsa-pubkey.json with another bundle's mldsa-pubkey.json (different enclave instance), re-zip, run. Step 1 passes (ECDSA didn't change), step 3 fails (fingerprints diverge). This is the binding pattern's core defense — the ECDSA chain notarizes the ML-DSA public key by reference (fingerprint), so swapping the key breaks the chain.

Case (c) — modified ML-DSA-87 signature (post-quantum tamper)

If signature/mldsa.sig is altered (or the signed input is altered), step 1

  • step 3 pass (ECDSA chain + binding both intact) but step 4 fails:
GeoClear Receipt Verifier v1.2.0
Bundle: /path/to/tampered-mldsa-sig.zip

[OK]   ECDSA P-384 verified via KMS HSM anchor
[OK]   ECDSA chain anchors ML-DSA-87 public key fingerprint
[FAIL] ML-DSA-87 signature does not verify under CNSA 2.0-aligned federal crypto profile
       ML_DSA_SIGNATURE_INVALID: ML-DSA-87 signature does not verify against the public key + signed-input bytes in the bundle. Possible tamper on signature/mldsa.sig or signature/mldsa-signed-input.bytes.

Bundle INVALID. Quantum-Resistant Overlay verification failed.
Exit code: 3 (ML_DSA_SIGNATURE_INVALID)

To rehearse: flip a single byte in signature/mldsa.sig, re-zip, run. ECDSA chain is undisturbed (passes step 1 + step 3), but the ML-DSA-87 verifier catches the byte flip. This is the "harvest now, decrypt later" defense — even an attacker with a quantum computer cannot forge a new ML-DSA-87 signature without the ephemeral secret key that lived only in the Nitro Enclave's RAM.

Wire-format details

Bundle layout for Pattern 4B (additive to existing pre-Q-1284 layout):

evidence-bundle.zip
├── README.md
├── receipt/
│   └── receipt.jws              ← ECDSA P-384 JWS (unchanged from pre-v1.2)
├── payload/
│   ├── canonical-payload.bytes  ← response body canonicalized (unchanged)
│   ├── canonical-payload.json
│   └── canonical-payload.sha256
├── signature/                   ← NEW in v1.2 (when pqc_overlay active)
│   ├── mldsa.sig                ← raw 4627-byte ML-DSA-87 signature
│   ├── mldsa-pubkey.json        ← ephemeral pubkey descriptor (2592-byte pubkey base64)
│   └── mldsa-signed-input.bytes ← exact bytes both signatures cover
├── trust/
│   ├── jwks-snapshot.json
│   ├── key-fingerprint.txt
│   └── trust-anchor.txt
├── verify/
│   └── offline-verifier.html
└── audit/
    └── verification-report.json ← includes pqc_overlay block when hybrid

Pre-v1.2 bundles (no signature/mldsa.sig) verify cleanly via the ecdsa-only mode — back-compat preserved.

Programmatic API

const { verifyBundle } = require('@geoclear/verify-receipt');
const fs = require('node:fs');

const zip = fs.readFileSync('./evidence-bundle.zip');
const result = await verifyBundle({ zip });

if (result.mode === 'hybrid-ecdsa-p384-mldsa-87') {
  console.log('Hybrid PQC verified:', result.pqc_overlay);
}

On failure, verifyBundle() throws an Error with a specific .code field (ECDSA_VERIFY_FAILED, ECDSA_CHAIN_FINGERPRINT_MISMATCH, ML_DSA_SIGNATURE_INVALID, or ML_DSA_VERIFY_THREW) so callers can distinguish tamper modes.