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

@identities-ai/ratify-protocol

v1.0.0-alpha.20

Published

Delegated-authority proofs for human-agent and agent-agent interactions — TypeScript reference SDK for the Ratify Protocol.

Readme

@identities-ai/ratify-protocol

TypeScript reference SDK of the Ratify Protocol v1 — delegated-authority proofs for human-agent and agent-agent interactions.

Quantum-safe by design: every signature is hybrid Ed25519 + ML-DSA-65 (NIST FIPS 204). Both must verify.

Byte-identical interoperability with the Go, Python, Rust, and C/C++ reference implementations. Validated against the 79 canonical test vectors on every CI run.

What is Ratify Protocol?

Ratify is an open cryptographic protocol that answers the question: "Is this AI agent authorized to act, by whom, for what, and under what constraints?"

A human issues a signed delegation cert to an agent. The agent presents a proof bundle when acting. Any third party can verify the proof — offline, without contacting a server — and get a cryptographically certain answer.

Beyond the one-shot delegate → present → verify round trip, this SDK implements the full v1.1 feature set for continuous and multi-party interactions: session-bound challenges and stream sequence numbers (replay and reorder detection across a multi-turn conversation), single-use challenge acceptance through a pluggable challenge store (SPEC §10), the SessionToken fast path (one hybrid signature verification per turn instead of N+1 — practical for live voice and video) with scope, single-use, and binding enforcement on the streamed path, canonical operation- and session-context binding for middleware custody deployments (SPEC §6.4.9, §15.2.1), push-based revocation, multi-party transaction receipts, witness append-only logs, and key rotation statements. All normative in the spec.

Install

npm install @identities-ai/[email protected]

Three verbs, three examples

1. DELEGATE — a human authorizes an agent

import {
  generateHumanRoot,
  generateAgent,
  issueDelegation,
  PROTOCOL_VERSION,
  SCOPE_MEETING_ATTEND,
  SCOPE_MEETING_SPEAK,
  type DelegationCert,
} from "@identities-ai/ratify-protocol";

// Alice creates her root (once, ever)
const { root, privateKey: alicePriv } = await generateHumanRoot();

// Her agent has its own keypair
const { agent, privateKey: agentPrivateKey } = await generateAgent("Alice's Scheduler", "custom");

// Alice signs a delegation
const cert: DelegationCert = {
  cert_id: crypto.randomUUID(),
  version: PROTOCOL_VERSION,
  issuer_id: root.id,
  issuer_pub_key: root.public_key,
  subject_id: agent.id,
  subject_pub_key: agent.public_key,
  scope: [SCOPE_MEETING_ATTEND, SCOPE_MEETING_SPEAK],
  issued_at: Math.floor(Date.now() / 1000),
  expires_at: Math.floor(Date.now() / 1000) + 7 * 24 * 3600, // 7 days
  constraints: [],
  signature: { ed25519: new Uint8Array(0), ml_dsa_65: new Uint8Array(0) }, // filled in by issueDelegation
};
await issueDelegation(cert, alicePriv);

2. PRESENT — an agent builds a proof bundle

import {
  signChallenge,
  type ProofBundle,
} from "@identities-ai/ratify-protocol";

// Challenge comes from the verifier, over the wire
const challenge = /* received from verifier */ new Uint8Array(32);
const challengeAt = Math.floor(Date.now() / 1000);

const bundle: ProofBundle = {
  agent_id: agent.id,
  agent_pub_key: agent.public_key,
  delegations: [cert],
  challenge,
  challenge_at: challengeAt,
  challenge_sig: await signChallenge(challenge, challengeAt, agentPrivateKey),
};

// Send bundle (as canonical JSON) over HTTP / your transport

3. VERIFY — any third party checks the proof

import { verifyBundle, SCOPE_MEETING_ATTEND } from "@identities-ai/ratify-protocol";

const result = await verifyBundle(bundle, {
  required_scope: SCOPE_MEETING_ATTEND,
});

if (!result.valid) {
  console.log("rejected:", result.identity_status, result.error_reason);
} else {
  console.log("authorized agent:", result.agent_id, "for", result.human_id);
  console.log("effective scope:", result.granted_scope);
}

Key custody

The protocol supports three key-custody modes with different trust tradeoffs. See SPEC.md §15.2 for the full model.

Self-custody (strongest)

The user generates and holds their own keypair. No third party can sign on their behalf.

import { generateHumanRoot, issueDelegation } from "@identities-ai/ratify-protocol";

// User generates keypair on their own device — private key never leaves
const { root, privateKey } = await generateHumanRoot();

// User signs delegations locally
const cert = { /* ... */ };
await issueDelegation(cert, privateKey);

// Only the public root.id and root.public_key are shared with registries

Custodial

A registry operator generates and stores the keypair server-side (envelope-encrypted with KMS). The user never touches keys directly. The operator calls the same SDK functions on the user's behalf.

Self-custody upgrade

A user who started in custodial mode can migrate to self-custody at any time using KeyRotationStatement:

import {
  generateHumanRoot,
  issueKeyRotationStatement,
} from "@identities-ai/ratify-protocol";

// User generates a NEW keypair on their device
const { root: newRoot, privateKey: newPrivateKey } = await generateHumanRoot();

// Rotation statement signed by BOTH old (custodial) and new (device) keys
const stmt = {
  version: 1,
  old_id: oldRoot.id,
  old_pub_key: oldRoot.public_key,
  new_id: newRoot.id,
  new_pub_key: newRoot.public_key,
  rotated_at: Math.floor(Date.now() / 1000),
  reason: "routine" as const,
  signature_old: { ed25519: new Uint8Array(0), ml_dsa_65: new Uint8Array(0) },
  signature_new: { ed25519: new Uint8Array(0), ml_dsa_65: new Uint8Array(0) },
};
await issueKeyRotationStatement(stmt, oldCustodialPrivateKey, newPrivateKey);

// From now on, only the user's device key can sign delegations.
// Auditors verify continuity via the rotation statement.

Canonical serialization

Signed payloads follow Ratify's canonical JSON rules (see SPEC.md §6). The SDK exposes:

import { canonicalJSON, delegationSignBytes, challengeSignBytes } from "@identities-ai/ratify-protocol";

These produce byte-identical output to the Go reference implementation. The test/conformance.test.ts suite runs the 79 published test vectors through the TS code and asserts byte-for-byte equivalence.

Wire transport

Signed Ratify structures travel as canonical JSON. The wire codec turns typed structures into those bytes and back, so integrators never hand-roll the base64 and byte-length handling.

Sending a proof bundle

import { encodeProofBundle } from "@identities-ai/ratify-protocol";

const body = encodeProofBundle(bundle); // canonical JSON string
await fetch("https://verifier.example.com/verify", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body,
});

Receiving a proof bundle

import { decodeProofBundle, verifyBundle, SCOPE_MEETING_ATTEND } from "@identities-ai/ratify-protocol";

const bundle = decodeProofBundle(requestBody); // string or Uint8Array
const result = await verifyBundle(bundle, { required_scope: SCOPE_MEETING_ATTEND });

Session tokens

SessionTokens cross the wire the same way (DelegationCerts too, via encodeDelegationCert / decodeDelegationCert):

import { encodeSessionToken, decodeSessionToken } from "@identities-ai/ratify-protocol";

// Verifier issues the token after the first full verify and sends it out:
const tokenJSON = encodeSessionToken(token);

// The agent presents it on later turns; the verifier decodes and checks it:
const presented = decodeSessionToken(tokenJSON);

Strict decoding

Decoders fail closed. A document is rejected — with an error naming the offending field — if it carries malformed UTF-8 or a byte-order mark; malformed or non-canonical base64; a wrong byte length for a key, signature, challenge, or 32-byte binding field; a missing or mistyped required field; an integer outside the IEEE-754 safe-integer range [-(2^53-1), 2^53-1] (SPEC §6.2); an empty delegation chain; unpaired stream fields; duplicated JSON object keys (at any nesting depth, with string escapes decoded before comparison); or an unknown field in a signed structure. Anything a conformant implementation would not have produced is treated as malformed at the transport boundary instead of surfacing later as a confusing verification failure.

The strictness applies to the signed structures themselves — ProofBundle, DelegationCert, SessionToken reject unknown fields because every byte of them is protocol surface. Your application's transport envelope is a different thing: it may carry whatever integration metadata you need, as long as that metadata stays outside the signed structure. For example, {"proof_bundle": {...}, "app_metadata": {...}} is fine — decode proof_bundle strictly with decodeProofBundle and treat the rest of the envelope as your own; a request_id inside the bundle itself would (correctly) be rejected.

Vocabulary discovery

Consoles and policy editors that present scope choices should derive them from the protocol rather than hardcoding strings, so UI vocabularies cannot drift:

import { vocabulary, scopeWildcards } from "@identities-ai/ratify-protocol";

vocabulary();     // all 54 canonical scopes, lex-sorted
scopeWildcards(); // wildcard shorthand -> non-sensitive member scopes

Scope vocabulary

import {
  SCOPE_MEETING_ATTEND,     // "meeting:attend"
  SCOPE_FILES_WRITE,         // sensitive — never rides a wildcard
  expandScopes,
  intersectScopes,
  isSensitive,
  validateScopes,
} from "@identities-ai/ratify-protocol";

expandScopes(["meeting:*"]);
// ["meeting:attend", "meeting:chat", "meeting:share_screen", "meeting:speak", "meeting:video"]

intersectScopes(["meeting:*"], ["meeting:attend", "meeting:speak"]);
// ["meeting:attend", "meeting:speak"]

Ratify v1 ships 54 canonical scopes plus 14 wildcards and a custom: extension pattern for application-specific scopes. See SPEC.md §9 for the full table including sensitivity flags and wildcard expansions.

For app-specific needs not covered by the canonical vocabulary, use the custom: prefix:

import { CUSTOM_SCOPE_PREFIX, validateScopes } from "@identities-ai/ratify-protocol";

validateScopes(["custom:acme:inventory:read"]); // → null (valid)

Custom scopes pass through expandScopes unchanged and are non-sensitive by default.

Running the conformance tests

From this SDK directory:

npm install
npm test

The conformance suite loads every fixture from the canonical test vectors and runs it through the TypeScript implementation. It checks:

  • Canonical signing bytes match the committed hex for every cert
  • Challenge signing bytes match
  • verifyBundle produces the same VerifyResult as the Go reference
  • Scope expansion is deterministic and matches
  • Revocation list signatures verify

A single failure means TypeScript and the Go reference have drifted.

Security posture

  • Ed25519 via @noble/ed25519 — audited, zero native deps, universal.
  • ML-DSA-65 via @noble/post-quantum — NIST FIPS 204, post-quantum lattice signature.
  • SHA-256 via @noble/hashes — same author, same posture.
  • WebCrypto for secure random (32-byte challenges).

No network code in this package. HTTP concerns (challenge issuance, revocation list fetching, API auth) live one layer up.

Signing mode. @noble/post-quantum signs ML-DSA-65 with hedged randomization by default, so two signings of the same message produce different bytes. This does NOT affect interop: the signatures verify in Go, Python, Rust, and C/C++, and vice versa. What must match across languages is the canonical signable bytes (SPEC §8.3), and those match byte-for-byte. Only the Go reference generator signs deterministically, so that fixtures regenerate reproducibly; passing { extraEntropy: false } would make this SDK deterministic too, but hedged signing is side-channel hardening and is kept as the default.

API added on main (ships in alpha.16, release unpublished)

The following surface is merged to main and ships in alpha.16. The tag is not yet published; install from main to use it ahead of the release.

  • Resource-bound verification. The resource_path constraint (the 8th constraint type, SPEC §5.7.3) binds authority to a named resource via a constraint's resource_id and optional path_prefix. At verify time the application supplies requested_resource_id, requested_path, and has_resource on VerifierContext (SPEC §5.16); verifyBundle(bundle, { context }) evaluates them. Helpers: normalizeResourcePath, resourcePathMatches, validateResourceConstraints.
  • Operation / session verifier context. OperationContext and SessionContextInputs, with operationContextBytes, operationContextHash, sessionContextBytes, and buildSessionContext; verifierContextHash produces the canonical hash bound into a VerificationReceipt.
  • VerificationReceipt wire codecs. encodeVerificationReceipt and decodeVerificationReceipt.
  • Streamed-turn verify options. StreamedTurn and StreamedVerifyOptions, with verifyStreamedTurnWithOptions (the options-object streamed fast path, SPEC §5.13).
  • Extension-constraint params. A constraint's params carries parameters for non-canonical constraint types (SPEC §5.7.1), validated by validateParamsValue; isCanonicalConstraintType guards which types may carry them.
  • Deeper delegation chains. MAX_DELEGATION_CHAIN_DEPTH is raised from 3 to 8 (SPEC §5.1).
  • Input bounds constants. MAX_PROOF_BUNDLE_BYTES, MAX_SCOPES_PER_CERT, MAX_CONSTRAINTS_PER_CERT, MAX_SCOPE_LENGTH_BYTES, MAX_IDENTIFIER_LENGTH_BYTES, MAX_AGENT_NAME_LENGTH_BYTES, MAX_JSON_NESTING_DEPTH.

License

Apache-2.0