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

@openvtc/vti-didcomm-js

v0.12.0

Published

Browser-side DIDComm v2 implementation for the Verifiable Trust Infrastructure. Focused subset: authcrypt/anoncrypt (ECDH-1PU/ECDH-ES + A256CBC-HS512) over X25519 and P-256, did:key + did:peer + did:webvh resolution, VTA REST auth, and ATM mediator transp

Downloads

1,222

Readme

vti-didcomm-js

Browser-side DIDComm v2 for the Verifiable Trust Infrastructure — a focused, dependency-light JavaScript implementation of the subset our auth flows need. ESM-only, runs in browsers and Node 20+.

Byte-compatible on the wire with affinidi-messaging-didcomm 0.13 (the same crate the VTA and the ATM mediator use) — every layer is verified by round-tripping through the Rust crate's unpack in CI.

Part of the wider Verifiable Trust Community project at github.com/OpenVTC, which builds open infrastructure for verifiable trust.

What it does

  • Authcrypt pack/unpack — ECDH-1PU + A256KW + A256CBC-HS512 (DIDComm v2's required-to-implement content encryption). Sender-bound. Key agreement over X25519 or P-256 (curve taken from the recipient's key).
  • Anoncrypt pack/unpack — ECDH-ES + A256KW + A256CBC-HS512. No sender identity; used for the routing/2.0/forward envelope to a mediator. X25519 or P-256.
  • DID resolution — did:key (Ed25519/X25519/P-256/secp256k1, in-tree), did:peer (numalgo 2, in-tree) and did:webvh (via didwebvh-ts, full hash-chain + Data-Integrity verification). Pluggable dispatcher for adding methods.
  • Forward routing — https://didcomm.org/routing/2.0/forward wrapping.
  • VTA REST auth — DIDComm-packed /auth/ challenge-response + JWT refresh (RFC 6749 §10.4 rotation).
  • Mediator transport — ATM challenge-response auth, browser WebSocket with subprotocol-bearer auth, message-pickup 3.0 live delivery, and sendAndWait request/response correlation.

Crypto comes from Web Crypto where possible (AES-CBC, HMAC, AES-KW, SHA-256) and @noble/curves for X25519/Ed25519/P-256/secp256k1. No hand-rolled symmetric crypto or curve math — only protocol-level orchestration.

Not implemented (additive if a flow needs them)

Multi-recipient JWEs, secp256k1/P-384/P-521 ECDH (key agreement is X25519 and P-256; secp256k1 is resolved as a signing key only), XChaCha20-Poly1305, did:peer numalgo 0/1/4 (only numalgo 2), signed-only (JWS) mode, BBS+, attachments beyond the forward envelope.

Install

npm install @openvtc/vti-didcomm-js

Usage

import { pack, unpack, packAnoncrypt, resolve } from "@openvtc/vti-didcomm-js";
import * as jwk from "@openvtc/vti-didcomm-js/jwk";

// Resolve a recipient and pack an authcrypt message to its keyAgreement.
const { didDocument } = await resolve("did:webvh:…:vta");
// … extract the keyAgreement X25519 key (see resolveX25519KeyAgreement) …

const jwe = await pack({
  message: { id, type, from: senderDid, to: [recipientDid], body },
  sender:    { kid: senderKid,    privateJwk },
  recipient: { kid: recipientKid, publicJwk  },
});

const { message, senderDid, senderKid, authenticated } = await unpack(jwe, {
  kid: recipientKid,
  privateJwk: recipientPrivateJwk,
}, { publicJwk: senderPublicJwk });

Who sent it: senderDid, not from

Authorise on senderDid — the DID of the key (skid) the authcrypt envelope was authenticated with, given that senderPublicJwk was resolved from that DID. message.from is a value the sender writes into the plaintext and is not an identity:

  • authcrypt — unpack refuses (SenderMismatchError, code: "E_SENDER_MISMATCH") any message whose from is missing or is not exactly senderDid, so a sender can only speak as the DID whose key it holds.
  • anoncrypt — nothing is authenticated; senderDid and senderKid are null whatever from claims.

MediatorSession hands the same proven identity to its consumers as a VerifiedSender ({ did, kid }): onMessage(message, thid, sender), onMediatorMessage(message, thid, sender), and waitFor(thid, timeoutMs, { from }), which resolves { message, sender } and — given from — only accepts a reply authenticated as that DID (or one of those DIDs). A thread id is not a secret, so pass from whenever you know who should answer.

Higher-level helpers:

  • @openvtc/vti-didcomm-js/vta-rest-auth — authenticate / refresh against a VTA's REST /auth/ surface.
  • @openvtc/vti-didcomm-js/vta-didcomm — connectVtaViaMediator → client.sendAndWait(type, body) over a mediator WebSocket.

Each module is also a subpath export (e.g. @openvtc/vti-didcomm-js/pack, @openvtc/vti-didcomm-js/resolver, @openvtc/vti-didcomm-js/mediator-transport).

Network policy for endpoints you did not choose

Mediator endpoints come from the mediator's DID document, and a VTA baseUrl usually comes from a QR code or config. The SDK checks both before dialing (@openvtc/vti-didcomm-js/net-guard). By default an endpoint must:

  • use https: or wss:;
  • carry no userinfo;
  • name a public host. Loopback, private, link-local, CGNAT and other non-public IP literals are refused, including their IPv4-mapped and NAT64 IPv6 forms, as are localhost, *.localhost, *.local, *.internal and *.home.arpa.

A refused endpoint throws BlockedEndpointError (code: "E_BLOCKED_ENDPOINT", with a reason). Auth requests never follow redirects.

Pass netPolicy to narrow or relax the policy:

// Production: accept only the hosts you expect.
await connectVtaViaMediator({
  ...args,
  netPolicy: { allowHosts: ["mediator.example.com", "*.vta.example.com"] },
});

// Local development against http://localhost.
await connectVtaViaMediator({
  ...args,
  netPolicy: { allowInsecure: true, allowPrivate: true },
});

allowInsecure only permits http: / ws:; it does not admit private hosts. allowHosts narrows the policy and never re-admits a blocked address. The same netPolicy option exists on authenticateToMediator, resolveMediator, MediatorSession, VTA REST authenticate / refresh, and DID resolution.

did:webvh resolution is on the same policy, because a webvh identifier names the host its log is fetched from — and identifiers arrive from elsewhere: an inbound frame's skid is resolved before the frame is authenticated, so a mediator-routed sender picks a host this client would GET. The log URL is checked before did.jsonl (and did-witness.json) is fetched, and so is the host of any nested did:webvh verification method inside the log:

// Refused before any request: BlockedEndpointError, reason private_name.
await resolve(`did:webvh:${scid}:localhost%3A8000`);

// A local webvh server, for development.
await resolve(did, { netPolicy: { allowInsecure: true, allowPrivate: true } });

A did:webvh host that merely contains localhost (say localhost.example.com) is refused too, unless allowPrivate is set: didwebvh-ts treats any such identifier as local and fetches it over plaintext http.

Browsers expose no DNS API, so a public name that resolves to a private address passes the URL check there; allowHosts is the strong control. In Node, guardedLookup also filters resolved addresses at connect time:

import { Agent } from "undici";
import { guardedLookup } from "@openvtc/vti-didcomm-js/net-guard/node";

const dispatcher = new Agent({ connect: { lookup: guardedLookup() } });
const fetch = (url, init) => globalThis.fetch(url, { ...init, dispatcher });
await authenticateToMediator({ ...args, fetch });

Module map

src/
  base64url.js          RFC 4648 §5 (no padding)
  multibase.js          base58btc + multicodec varints
  jwk.js                JWK ↔ raw bytes (OKP X25519/Ed25519, EC P-256)
  concat-kdf.js         NIST SP 800-56A Concat KDF (JOSE OtherInfo)
  x25519.js             X25519 key agreement (@noble/curves)
  p256.js               P-256 key agreement (@noble/curves)
  key-agreement.js      curve dispatcher (X25519 + P-256)
  ecdh-1pu.js           ECDH-1PU KEK (authcrypt; tag-bound key-wrap)
  ecdh-es.js            ECDH-ES KEK (anoncrypt)
  aes.js                AES-256-KW (RFC 3394)
  a256cbc-hs512.js      AES-256-CBC + HMAC-SHA-512 AEAD (RFC 7518 §5.2)
  pack.js               authcrypt JWE
  anoncrypt.js          anoncrypt JWE
  unpack.js             dual-mode unpack (authcrypt + anoncrypt)
  did-key.js            did:key resolver
  did-webvh.js          did:webvh resolver (via didwebvh-ts)
  did-peer.js           did:peer resolver (numalgo 2, in-tree)
  resolver.js           method dispatcher
  forward.js            routing/2.0/forward wrapping
  vta-rest-auth.js      VTA /auth/ + refresh
  forward.js, mediator-auth.js, mediator-transport.js, vta-didcomm.js
                        mediator transport (auth, WS live delivery, sendAndWait)
  net-guard.js          egress policy for untrusted endpoints (browser + Node)
  net-guard-node.js     guardedLookup: DNS-answer filtering (Node only)
  index.js              public re-exports

Tests

npm test          # node --test; 250+ tests

Includes RFC 7518 §B.3 (A256CBC-HS512) and §C (Concat KDF) known-answer vectors, the W3C did:key spec vectors, the DIF did:peer numalgo-2 example, did:webvh against real didwebvh-rs fixtures, and cross-implementation round-trips through the Rust affinidi-messaging-didcomm unpack (vti-didcomm-roundtrip-helper) for both X25519 and P-256. Some tests reach live infra / the Rust helper and skip cleanly when unavailable.

License

Apache-2.0