开启你的首个 connect 会话(Open your first connect session)
本教程端到端地建立一个 SPOKE connect 会话:推导你的 peer_id、签署握手(hello)、对照 allowlist 校验对方的签名握手,并带关联(correlation)地调用一个 op。它使用 TypeScript 语言原生客户端(@42ch/spoke-connect)对接本地对端,并把对端一侧指向 Rust 参考 crate。
connect 是面向跨进程 SPOKE 主机的可选交互信封族(spoke-connect 能力标志)。建议先完成安装并创建你的第一条 KnowledgeEntry —— 本教程在数据/ops 叙事之上构建身份与会话概念。
1. 安装客户端
pnpm add @42ch/[email protected]该软件包提供同构核心、Node 版 connectClient、Noise 子路径与 RemoteAdapter 模块;各入口与对应辅助函数见从 TypeScript 客户端连接。
2. 推导你的对等节点身份
每个 connect 主机都有一对 Ed25519 密钥。线上的 peer_id(对等节点标识)由 32 字节公钥推导而来 —— libp2p PublicKey protobuf 的身份 multihash,再经 base58btc 编码。该推导在 TypeScript 客户端、Rust 参考实现与全部原生绑定之间字节一致(由共享 golden vectors 锁定)。
peer_id 是网络信任根 —— 与 manifest 内携带的咨询性 host_id(主机标识)不同。用 derivePeerIdFromEd25519Pubkey(getPublicKeyEd25519(seed)) 推导它;身份辅助函数见从 TypeScript 客户端连接。
3. 签署并校验握手
握手是一个已签名的 ConnectHello:双方各对一个 JCS 规范化对象签名 —— 发起方为 {protocol_version, peer_id, nonce, host},响应方为该对象加 peer_nonce(发起方的 nonce,即拨号绑定)—— 使用各自的 Ed25519 密钥,并以无填充 base64url 承载。
import { generateNonce, signHelloEd25519, verifyHelloEd25519 } from "@42ch/spoke-connect";
import type { HostCapabilityManifest } from "@42ch/spoke-schemas";
const seed = new TextEncoder().encode("..."); // 32-byte Ed25519 seed
const manifest: HostCapabilityManifest = {
schema_version: 1,
host_id: "host_tutorial",
roles: ["input-source"],
capabilities: ["spoke-baseline"],
namespaces: ["tutorial"],
extensions: {},
};
const nonce = generateNonce(); // 16 CSPRNG 字节,base64url —— 满足 16 字符线上下限
const hello = await signHelloEd25519(seed, nonce, manifest);
// 接收侧:对照发送方的公钥与其推导出的 peer_id 校验 ——
// 一个推导出不同 peer_id 的密钥无法为该对等节点的身份作证。
await verifyHelloEd25519(remotePubkey, remotePeerId, hello);nonce 按发送方单次使用:接收方记录每个已接受的 (peer_id, nonce) 对并拒绝重放。响应方用自身 nonce 加发起方 nonce(signHelloEd25519 的第四个参数)签署同一对象,发起方在验证时传入自己的 nonce —— 因此捕获的响应方 hello 无法重放进新的拨号。完整握手走查 —— 规范化字节、nonce 下限与 NonceStore 重放防护 —— 见从 TypeScript 客户端连接。
4. 配置 allowlist
准入是 fail-closed 的:空 allowlist 拒绝所有对端。接收主机只接受认证后的远端 peer_id 出现在列表中的连接。
import { isAllowlisted } from "@42ch/spoke-connect";
const allowlist = [remotePeerId];
if (!isAllowlisted(allowlist, remotePeerId)) {
throw new Error(`peer ${remotePeerId} is not allowlisted`);
}5. sequence 与 correlation
每个会话维护从 0 开始的按方向单调 sequence 计数器。每次 invoke 附带 request_id;响应必须回显 session_id、sequence 与 request_id,否则关联校验失败。
import { OutboundSequence, checkResponseCorrelation, correlationFromRequest, correlationFromResponse } from "@42ch/spoke-connect";
const outbound = new OutboundSequence();
const request = {
session_id: "sess_1",
sequence: outbound.allocate(), // 首次调用 → 0
request_id: crypto.randomUUID(),
op: "check",
payload: { scope: { scope_id: "book-harbor" } },
extensions: {},
};
// 响应到达时:
checkResponseCorrelation(correlationFromRequest(request), correlationFromResponse(response));6. 完整会话:connectClient
connectClient(位于 ./node 子路径)拨号一个 WebSocket,完成签名握手交换,校验会话快照(对端绑定、initial_sequence 为 0),并按 request_id 路由带关联的 invoke:
import { derivePeerIdFromEd25519Pubkey } from "@42ch/spoke-connect";
import { connectClient } from "@42ch/spoke-connect/node";
const remotePubkey = /* 对端的 32 字节 Ed25519 公钥 */;
const client = await connectClient({
url: "ws://127.0.0.1:8080",
identity: { seed },
manifest,
remotePubkey,
allowlist: [derivePeerIdFromEd25519Pubkey(remotePubkey)],
});
const response = await client.invoke("check", { scope: { scope_id: "book-harbor" } });
client.close();当远端 peer_id 不在 allowlist 中时,客户端会在握手开始前拒绝;当快照中的对端 id 与已认证握手不符时,客户端会拒绝该会话。
7. 对端一侧
connectClient 可连接任意在有序可靠流上使用 connect 信封族的 SPOKE 主机。Rust 参考 crate(crates.io 上的 spoke-connect)是参考主机实现 —— 它把信封族映射到 rust-libp2p(noise、yamux、request-response),并演示一个两节点会话:一个节点拨号另一个节点、交换签名握手、然后调用 check:
cargo add [email protected]
cargo run -p spoke-connect --example two_node_usage编译好的示例源码(examples/two_node_usage.rs)展示两侧:SpokeConnectNode::start 携带 peer_allowlist 与本地 manifest,然后 connect(addr) 与 session.invoke("check", payload) —— 与 TypeScript 客户端实现相同的会话规则。crate README(crates/spoke-connect/README.md)记载了完整流程,包括 capability-token 提权鉴权。
你现在掌握了
- 从 Ed25519 公钥推导
peer_id,以及它为何是信任根。 - 签名握手(
spoke-connect-hello-jcs-v1):对{protocol_version, peer_id, nonce, host}(发起方)/ 加peer_nonce(响应方)做 JCS,Ed25519 签名,base64url,以及拒绝重放响应方 hello 的拨号绑定。 - Fail-closed 的 allowlist 准入与单次使用 nonce 重放保护。
- 按会话的 sequence 与
request_id关联。
下一步
- 集成 RemoteAdapter 连接推理主机 —— 实现
Transport、用connectRemoteAdapter拨号,并针对 demo 模拟推理主机调用BaselinePorts面。 - 从 TypeScript 客户端连接 —— 完整客户端面、浏览器 vs Node,以及核心辅助函数。
- 从原生绑定连接 —— 从 C#、Kotlin、Swift、Go 或 Python 使用同一会话核心。
- connect 线上参考 —— 信封字段表与身份绑定规则。