@42ch/spoke-connect
v0.11.1
Published
SPOKE connect client library — pure-TS identity derivation, Ed25519 hello signing, and RFC 8785 JCS canonicalization
Readme
@42ch/spoke-connect
SPOKE connect client library (TypeScript) — peer identity derivation, Ed25519 hello signing, RFC 8785 JCS canonicalization, one-JSON-per-message WebSocket framing, and the pure session-core port (sequence, correlation, dispatch gate, nonce store, allowlist).
Published on npm as @42ch/spoke-connect; the version tracks the monorepo lockstep SemVer (asserted by verify:version, bumped by release:bump). The transport is a direct WebSocket ordered reliable stream using plain JSON + WebSocket framing; transport and crypto are dependency-light by design.
What it provides
- Identity —
derivePeerIdFromEd25519Pubkey: protobuf PublicKey → identity multihash0x00→ base58btc. Ported fromtooling/connect-identity-proof/proof.mjs; the normative formula lives in.mstar/specs/spoke-connect.md§ Identity binding. - Crypto — Ed25519 sign/verify over raw 32-byte keys, WebCrypto primary with an
@noble/ed25519fallback on the same code path; base64url without padding. - JCS —
canonicalHelloBytes(peerId, nonce, host, peerNonce?): RFC 8785 canonicalization of the signed hello object via the pinnedcanonicalizepackage —{protocol_version, peer_id, nonce, host}for the initiator hello (4 fields), pluspeer_noncefor the responder hello (5 fields, dial binding). Absent optional members are omitted; only present members appear in the canonical object. - Session core (
src/core/) — behavior port ofcrates/spoke-connect/src/core/:OutboundSequence/InboundSequence(start 0, exhaustion instead of wrap past2^53−1), response correlation (session_id / sequence / request_id echo), op dispatch gate (capability ⊆ negotiated, unknown op fails closed), per-senderNonceStore, fail-closed allowlist, thinSessionhelper,PROTOCOL_VERSION. - Node client (
src/node/) —connectClient({ url, identity, manifest, remotePubkey, allowlist }): dials a WebSocket, performs the signed hello exchange, validates the session snapshot (peer binding,initial_sequence0), then routes correlated invokes byrequest_idwith bounded waits. Node-only because it usesws; the isomorphicsrc/modules stay browser-swappable.
Golden-vector parity: tests assert peer_id / JCS bytes / signature byte-identical to the Rust golden vectors. The golden hello vector has a single cross-language source of truth at crates/spoke-connect/tests/fixtures/golden-hello.json; this package loads its registered byte-identical copy (tests/fixtures/golden-hello.json) through the thin test-only loader src/golden.ts (not part of the package exports or npm files).
Install
pnpm add @42ch/spoke-connectUsage
import { derivePeerIdFromEd25519Pubkey } from "@42ch/spoke-connect";
import { connectClient } from "@42ch/spoke-connect/node";
const seed = new TextEncoder().encode("..."); // 32-byte Ed25519 seed
const remotePubkey = /* the server's 32-byte Ed25519 public key */;
const client = await connectClient({
url: "ws://127.0.0.1:8080",
identity: { seed },
manifest: {
capabilities: ["spoke-baseline"],
extensions: {},
host_id: "host_primary",
namespaces: ["toy_world"],
roles: ["data-store"],
schema_version: 1,
},
remotePubkey,
allowlist: [derivePeerIdFromEd25519Pubkey(remotePubkey)],
});
const response = await client.invoke("check", { /* op payload */ });
client.close();Core helpers are importable without the client: signHelloEd25519 / verifyHelloEd25519, OutboundSequence, checkResponseCorrelation, dispatchAllowed, NonceStore, isAllowlisted, Session — all from @42ch/spoke-connect.
Test
From the repo root:
pnpm run test:connect-tsFrom this directory:
pnpm test
pnpm run typecheckThe two-node interop test (tests/two-node.test.ts) runs an in-process ws server and client over 127.0.0.1:<ephemeral> with bounded waits only. CI runs the suite on Node 20.x — Node ≥ 20.19 takes the WebCrypto Ed25519 path; older patches fall back to @noble/ed25519. The package engine floor is Node ≥ 20.19.0 (@noble/hashes floor, and the first Node line that accepts WebCrypto Ed25519).
Scope
- Published on npm (
@42ch/spoke-connect); ships a builtdist/via tsup (dual CJS/ESM, two isolated entries — browser-safe.and Node./node). - The package consumes the existing connect schemas as-is (
@42ch/spoke-schemasruntime dependency; types only from the consumer's perspective); the schema inventory is unchanged. - Envelope-level interop over any ordered reliable stream; framing is direct WebSocket per
.mstar/specs/spoke-connect.md§ Transport framing. - The client targets the direct ordered-stream transport.
connectClientlives in the Nodesrc/node/subpath (usesws); the isomorphicsrc/modules are browser-swappable with the native WebSocket.
Publishing
Published via the monorepo release.yml on stable lockstep tags (npm Trusted Publishing OIDC). The build emits dist/ (tsup, dual CJS/ESM); the tarball is packed with pnpm (rewrites workspace:*) and published with the npm CLI.
- Entry points — two subpaths:
.(isomorphic core: identity, crypto, JCS, session core) and./node(the NodeconnectClient, which depends onws). Browser consumers import.only. - License — Apache-2.0 via the
licensefield; authoritative text at repository rootLICENSE. - Versioning — lockstep SemVer with the monorepo (
verify:version,release:bump);@42ch/spoke-schemasresolves at the same version from npm. - Installation —
pnpm add @42ch/spoke-connectinstalls from the npm registry at the lockstep version.
