Skip to main content
Maturity: Limited preview (0.7.0). Every verification in a relying-party integration happens here.
Server-side only. This package carries a scoped machine-to-machine (M2M) API key and authenticates server to server. Never ship it to a mobile app or a browser.

Two entry points

The package root is framework-free. Everything Fastify lives behind /fastify, which is what keeps the optional peer dependency real rather than aspirational.

Signing

Both signers use the same registered presenter key. The typ is what distinguishes the artifacts and routes them on the device. OKP/Ed25519 and EC/P-256 keys are both accepted. The proof signer is structural, so anything with sign({ htm, htu, grant, body? }) works. You can sign from your own key management service.

Verifying a login token

Checks, in order: the explicit typ and algorithm pin, and rejection of jku, x5u and unknown critical headers; the signature against the embedded did:jwk; audience and issuer; the holder-of-key binding; the single-use nonce; the subject mode against expectedMode; and, for chain-aware tokens, self-certification plus the Baby Jubjub Schnorr key-binding signature. The last two hard-fail and never downgrade the tier. expectedMode is required. Pass the mode you asked for: "disclosed" if the request set scope: "arrayid", "pairwise" if it did not. A token of the other mode throws subject_mode_mismatch before any proof work. This matters most if you key accounts on the ArrayID. A pairwise token’s sub is not bound to the signing key unless a membership proof verifies, so without the check anyone could sign a pairwise token naming someone else’s ArrayID and be accepted as them. A tier floor doesn’t replace it, because a pairwise token with a valid proof reaches membership_proven. selfIssuedPreHandler takes the same options, so it needs expectedMode too. Given an idFacetProof config the SDK additionally checks six obligations and gates unique on all six. You implement none of them. Two are worth knowing about because they are what makes the proof non-transferable: the scope is recomputed from your own client_id, and the payload hash binds the specific nonce you burned. A proof therefore fails at any other RP and cannot be replayed at yours. IdFacetProofError.code is one of groth16, root, scope, slot, payload, mode, tag, replay, malformed, verifier_unavailable.
verdict.mode and verdict.idFacet are server-internal. The facet is the user’s stable pseudonym at your RP, so keep it out of response bodies and per-user log lines.

The roots provider

It polls in the background and verifies every entry against your pinned key, so the verification path makes no network call. The feed is transport, never authority, so a tampering relay can stall you but cannot forge for you. The key set the feed serves is diagnostic and is never a verification input. Freshness is positional, not time-based: the last N distinct roots, not roots newer than some age. Roots advance only on tree activity, so a time rule would reject valid proofs during quiet periods.
Gate on warm upstream of your verifier. A cold provider throws, and the right answer is 503, not 401. Cold is routine (fresh deploy, disabled poller, pins not yet issued), whereas a 401 tells the device its credential is bad. selfIssuedPreHandler maps any unrecognised throw to 401 verify_failed, by which point the nonce is consumed.

Nonce and state stores

The single-use nonce your verifier burns. You run this, and Array never hosts it. It is a pure library with no calls to Array.
consume returns true exactly once per issued value. A backend outage makes consume return false (fail-closed). issue throws rather than hand out a value that was never durably stored. MemoryNonceBackend covers tests, and the two-method NonceBackend interface takes a Redis or Postgres implementation.
Use the exported isConditionalCheckFailure to tell a refusal from an outage. Do not test err.name yourself. The DynamoDB marker’s location is not stable across @aws-sdk versions, so a name-only check reads every refusal as an outage on one version and works on another, turning a replayed proof into a 503 instead of a 401.

Also exported

The verdict ladder (VERDICT_TIERS, tierAtLeast); verify-side constructors with byte-parity against the wire contract (decodeDidJwk, selfCertifies, schnorrVerifyFr and others); and typed errors, namely SelfIssuedVerifyError, DpopVerifyError, PresenterProofError and ArrayIdentityError. The wire contract’s types and constants are re-exported, so you rarely need to import @vibechecklabs/array-identity-contract directly.