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

@emilia-protocol/verify

v3.8.1

Published

Zero-dependency offline verification for EP authorization receipts: Ed25519 receipts, Merkle anchors, commitment proofs, Class-A WebAuthn device signoffs, multi-party quorum (M-of-N / ordered, two-person rule), portable revocation, trusted-time attestatio

Downloads

3,417

Readme

@emilia-protocol/verify

Zero-dependency offline verification for EP trust receipts.

Verify Ed25519-signed trust receipts, Merkle anchor proofs, and commitment proofs using only Node.js built-in crypto. No EP infrastructure required. No API key. No account. Just math.

This is the core primitive that makes EP a protocol, not an API.

Install

npm install @emilia-protocol/verify

Quick Start

import { verifyReceipt } from '@emilia-protocol/verify';

// Load a receipt document (EP-RECEIPT-v1 format)
const receipt = JSON.parse(fs.readFileSync('receipt.json', 'utf8'));

// Get the signer's public key (from /.well-known/ep-keys.json)
const publicKey = 'MFYwEAYHKoZIzj0CAQYFK4EEAA...'; // base64url SPKI DER

const result = verifyReceipt(receipt, publicKey);
console.log(result);
// { valid: true, checks: { version: true, signature: true, anchor: null } }

In the browser, edge, or Deno

The default entry uses Node's crypto. For any runtime with the W3C Web Crypto API — every modern browser, Deno, Cloudflare Workers, Vercel Edge — import the /web build instead. Same inputs, same { valid, checks } output (proven byte-for-byte in web.test.js); the functions are async because Web Crypto is.

import { verifyReceipt, verifyWebAuthnSignoff } from '@emilia-protocol/verify/web';

const r = await verifyReceipt(receipt, publicKey);          // Ed25519
const s = await verifyWebAuthnSignoff(signoff, approverKey, // ECDSA P-256
  { rpId: 'emiliaprotocol.ai' });

This is what powers emiliaprotocol.ai/verify: a relying party verifies a receipt entirely in their own tab — nothing uploaded, no server trusted. Receipts use Ed25519; Class-A device signoffs use ECDSA P-256 over a WebAuthn assertion (the /web build converts the DER signature to the raw form Web Crypto expects). Call isSupported() to feature-detect.

API

verifyReceipt(doc, publicKeyBase64url)

Verify an EP-RECEIPT-v1 document. Performs three independent checks:

  1. Version — Document format is EP-RECEIPT-v1
  2. Signature — Ed25519 signature over canonical payload
  3. Anchor (if present) — Merkle proof reconstructs claimed root

Returns { valid, checks, error? }.

verifyMerkleAnchor(leafHash, proof, expectedRoot)

Verify a Merkle inclusion proof. The root can be independently checked on Base L2 via Basescan.

Returns boolean.

verifyCommitmentProof(proof, publicKeyBase64url)

Verify an EP-PROOF-v1 commitment proof. Checks expiry and signature.

Returns { valid, claim, error? }.

verifyReceiptBundle(bundle, publicKeyBase64url)

Verify all receipts in an EP-BUNDLE-v1 document.

Returns { valid, total, verified, failed }.

verifyWebAuthnSignoff(signoff, approverPublicKeySpkiB64u, { rpId? })

Verify a Class-A (device-bound key) signoff fully offline: the WebAuthn challenge equals SHA-256(JCS(context)) for the exact signed context, the authenticator asserted user presence + verification, and the ECDSA P-256 signature verifies against the enrolled approver key.

Returns { valid, checks, error? }.

verifyTrustReceipt(receipt, { approverKeys, logPublicKey })requires 1.3.0

The full offline verification algorithm from the Internet-Draft (draft-schrock-ep-authorization-receipts, Section 6.3) over a Section 6.2 Trust Receipt — all six steps, no network:

  1. Recompute the action hash from the canonical Action Object
  2. Recompute each context hash; confirm it commits to the action hash, the policy hash, and a distinct approver
  3. Verify each signoff signature (Class-A WebAuthn or Class-B Ed25519) against the pinned approver key, checking the key's validity window
  4. Separation of duties — initiator in no approver slot, approvers pairwise distinct, approval count ≥ required_approvals
  5. Merkle inclusion of the receipt leaf against the checkpoint root, and the checkpoint signature against the trusted log key
  6. signed_at / committed_at within [issued_at, expires_at]

Returns { valid, checks, errors, attestation, strict } and fails closed on any missing input.

Strict verifier mode — requires 1.5.0

For deployment gates and hostile-environment verification, opt into strict mode:

const r = verifyTrustReceipt(receipt, {
  approverKeys,
  logPublicKey,
  strict: true,
  rpId: 'www.emiliaprotocol.ai',
  expectedPolicyHash: 'sha256:...',
});

Strict mode preserves the frozen Section 6.3 checks object, then adds r.strict as a second gate. When strict: true, valid requires both the base checks and:

  • pinned_keys — every signer and the log are locally pinned.
  • rp_id — Class-A WebAuthn rpIdHash matches the caller-pinned RP ID.
  • user_presence / user_verification — Class-A signoffs asserted UP + UV.
  • key_windows — every approver key has parseable valid_from / valid_to and was valid at issued_at.
  • policy_hash — every context matches expectedPolicyHash.
  • no_unsigned — critical action, context, signoff, consumption, and log proof fields are present.

Without strict: true, strict is { enabled: false, valid: true, checks: {}, errors: [] }, so existing verification and conformance semantics are unchanged.

Advisory: the PIP-007 initiator escalation attestation — requires 1.4.0

When the contexts carry a PIP-007 initiator_attestation, the result includes an advisory report:

const r = verifyTrustReceipt(receipt, { approverKeys, logPublicKey });
r.attestation; // { present, consistent, issues: [] }
  • present — a context carries an attestation.
  • consistent — it is present in every context with an identical canonical form (the cross-context identity rule the protocol flags to catch a divide-and-misinform orchestrator showing different approvers different reasons).
  • issues — any PIP-007 §1 malformations: unknown members, a statement over 280 characters, escalation_trigger of policy_rule without a policy_basis, or a bad enum value.

The advisory never affects valid or any member of checks — by design (PIP-007 §2): a receipt carrying a malformed attestation still verifies cryptographically, exactly as it does on a verifier that predates this PIP. The attestation is a claim by the initiator — identified but never trusted — so a policy engine MUST NOT use it to relax any check or raise any trust score.

Opt-in transparency and currency knobs (requires 3.5.0)

Five additive, opt-in checks extend verifyTrustReceipt in the same shape as priorCheckpoint: each runs only when you pass its option, adds one member to checks when active, folds into valid by conjunction, and fails closed with a distinct reason. Pass none of them and the result is byte-for-byte what a pre-3.5.0 verifier returns (the frozen seven checks members, no extra top-level members).

const r = verifyTrustReceipt(receipt, {
  approverKeys, logPublicKey,

  // 1. Witness quorum (EP-WITNESS-v1): k distinct pinned witnesses cosigned the head.
  witnessQuorum: { cosignatures, pinnedWitnessKeys, k: 2 },

  // 2. Trusted-time proof (RFC 3161): a pinned TSA timestamped a digest you choose.
  timestampProof: { token, expectedDigest, pinnedTsaKeys },

  // 3. Currency (EP-CURRENCY-v1): passes ONLY on a proven-fresh signed head.
  currency: { now, maxStalenessSeconds, freshHead, freshHeadRequired },

  // 4. Consumption proof (EP-SMT-CONSUME-v1): a nonce went absent -> present once.
  consumptionProof: bundle,

  // 5. Initiator-software attestation (EP-INITIATOR-ATTESTATION-v1).
  requireInitiatorAttestation: true,
});
// checks.witness_quorum / .timestamp_proof / .currency / .consumption /
// .initiator_attestation are added only for the options you passed, and the
// full module result is surfaced under the matching top-level member.

Honesty boundaries (also stated in each module):

  • Witness quorum proves k trusted witnesses saw one head (the local, single-view half of equivocation detection). It does not prove no different head was shown elsewhere; that cross-view gossip is the deployment's responsibility.
  • Timestamp proof proves a TSA asserted the digest existed at gen_time (the bytes predate gen_time). It is authentic-as-of-token only and says nothing about current TSA-certificate validity or revocation, and it does not prove the action was correct or authorized.
  • Currency is a separate axis from offline authenticity. checks.currency passes only on status fresh; both stale and the honest offline default unknown fail the opted-in gate, because offline verification can never establish currency. Read result.currency.currency_at_T to tell unknown (offline only) apart from stale.
  • Consumption proof proves the tree-shaped consumption facts only. Checkpoint signatures and currency of the later head are the caller's responsibility.
  • Initiator attestation says which software asked; it does not prove the software behaved (the labels are self-asserted, and the digest is authentic-as-supplied, not proof of correct execution).

Both the witness and consumption profiles now ship a verifier and a reference emitter, so the emit/verify loop is closed at reference level. A third party can PRODUCE these artifacts, not only check them:

  • Witness (EP-WITNESS-v1). The reference witness emitter is the cosigner service in witness/ (witness/server.mjs). It imports the signing digest and domain tag from this package (witness.js), so a cosignature it emits is byte-identical to what verifyWitnessCosignature() / requireWitnessQuorum() check.
  • Consumption (EP-SMT-CONSUME-v1). The reference issuer-side emitter is ReferenceConsumptionTree in consumption-proof.js, exported as @emilia-protocol/verify/consumption-proof.js. It maintains the sparse consumption tree and emits the non-inclusion / inclusion sub-proofs in the exact wire format verifyConsumptionProof() accepts, so anyone can reproduce a full bundle.

Reference emitters pin the wire format; they are not production infrastructure. A production issuer maintains its own sparse consumption ledger (not the in-memory reference tree), and the security of the witness leg comes from RUNNING several independent witnesses under separate operators and comparing their views. That ecosystem step is deployment, not reference code.

All five of these profiles (EP-WITNESS-v1, EP-CURRENCY-v1, EP-SMT-CONSUME-v1, EP-INITIATOR-ATTESTATION-v1, and timestamp proof (RFC 3161)) are now ported to Python (packages/python-verify) and Go (packages/go-verify) and run cross-language in conformance/run.mjs over shared vector suites (currency.v1.json, initiator-attestation.v1.json, consumption-proof.v1.json, witness.v1.json, timestamp-proof.v1.json), where the JavaScript, Python, and Go verifiers must agree. The RFC 3161 timestamp-proof ports keep the package's dependency posture: the JS minimal DER/CMS reader was hand-ported to pure Python (with cryptography used only for the RSA/ECDSA signature verify, so no new dependency) and to pure-stdlib Go, and all three lanes agree over real openssl-minted TimeStampTokens, including the exact per-vector refusal path. As always, this is one team's three-language ports (a consistency check), not clean-room independent implementations.

Federation (PIP-006) — requires 1.3.0

Cross-operator verification: accept a receipt issued by a different EP operator using only its published discovery surfaces.

import { verifyFederatedReceipt, verifyFederatedReceiptOffline } from '@emilia-protocol/verify';

// Online: resolves the issuer's keys from a caller-pinned discovery URL and
// checks its revocation surface. Treat receipt.signature.key_discovery as a
// hint, not a trust root.
const verdict = await verifyFederatedReceipt(receipt, {
  keyDiscoveryUrl: 'https://op-a.example/.well-known/ep-keys.json',
});
// { accepted, verified, revoked, signer, keyMatched: 'current'|'historical', checks }

// Air-gapped: supply the issuer's ep-keys.json + revocation set yourself.
const offline = verifyFederatedReceiptOffline(receipt, discoveryDoc, { revokedReceiptIds });

resolveOperatorKeys(discoveryDoc, signerId) is also exported (current keys first, then historical_keys for rotation safety). See docs/FEDERATION-REGISTRY.md for the operator discovery convention.

Design Principles

  • Zero dependencies — Only node:crypto. No supply chain risk.
  • Offline-first — No network calls (the federation online path takes an injectable fetch). No EP server needed.
  • Deterministic — Canonical JSON serialization for reproducible signatures.
  • Auditable — A few small files, ~1,000 lines total. Read the entire thing in an hour.

How It Works

Receipt Document (EP-RECEIPT-v1)
├── payload (canonical JSON)
├── signature
│   ├── algorithm: "Ed25519"
│   ├── signer: "ep_entity_..."
│   └── value: base64url signature
└── anchor (optional)
    ├── leaf_hash: SHA-256 of receipt
    ├── merkle_proof: [{hash, position}, ...]
    ├── merkle_root: root hash
    └── chain: "base-sepolia"

Verification:
1. Canonicalize payload → sorted-key JSON
2. Verify Ed25519(canonical_payload, signature, public_key)
3. If anchor: reconstruct Merkle root from proof, compare

Getting Public Keys

Signer public keys are discoverable at /.well-known/ep-keys.json on any EP operator:

curl https://ep.example.com/.well-known/ep-keys.json

Reliance gap reports (acceptance preflight)

reliance-gap.js wraps the reliance kernel (reliance.js) into a diagnostic: given a de-identified action packet and a relying party's pinned EP-RELIANCE-PROFILE-v1, it emits one deterministic EP-RELIANCE-GAP-REPORT-v1 with the kernel verdict passed through verbatim, a missing-evidence list (each entry: requirement, why it matters, how to close it), the JCS+sha256 action digest, the pinned profile digest, a plain-language control mapping (authority, identity, freshness, revocation, consumption, signoff, audit trail), a closed limitations list, and the exact command that reproduces the report offline.

import { buildRelianceGapReport } from '@emilia-protocol/verify/reliance-gap';

const report = buildRelianceGapReport(
  { action, evidence, context },       // the packet
  profile,                             // the relying party's pinned rule
  { now: '2026-07-08T15:00:00Z' },     // evaluation time (never the wall clock)
);

The packet's evidence is an array of artifacts, either { type, artifact } envelopes or bare artifacts detected by shape (receipt, quorum, authority_proof, revocation_state, consumption). Artifact types with no registered verifier are recorded as unverifiable_present and never count toward satisfaction. The packet's context carries the relying party's verification material: approver_keys, log_public_key, rp_id, revoker_keys. The profile argument accepts a bare profile or a signed EP-RELIANCE-PROFILE-REGISTRY-v1 entry (unwrapped; the entry's profile_id is reported).

Determinism contract: no wall-clock reads (evaluation time comes only from opts.now or packet.evaluated_at; absent both, the builder refuses with a reason), keys sorted, arrays stable, so the same inputs reproduce the same bytes. buildMultiPartyRelianceGapReport evaluates the SAME packet against several profiles and emits one combined EP-RELIANCE-GAP-MULTI-v1 report.

From the CLI:

npx @emilia-protocol/verify reliance-gap packet.json --profile profile.json
npx @emilia-protocol/verify reliance-gap packet.json --profiles ./profiles \
  --now 2026-07-08T15:00:00Z --out report.json

Exit codes: 0 = rely (all rely in --profiles mode), 2 = any do_not_rely_*, 1 = operational error. Fully offline; no network access. A worked five-relying-party example lives in examples/reliance-gap/ at the repository root. A single gap report is the per-action preflight; EP-ASSURANCE-PACKAGE-v1 (packages/gate/reports/assurance-package.js) bundles a population of such reliance decisions so an independent assurer can re-perform every verdict offline.

License

Apache-2.0