@xiotbox/e2e-channel 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,165 @@
1
+ <p align="center">
2
+ <img src="docs/assets/xiotbox-logo.png" alt="XiotBox" width="112" />
3
+ </p>
4
+
5
+ <h1 align="center">XiotBox E2E Secure Channel</h1>
6
+
7
+ <p align="center">
8
+ <strong>A reusable end-to-end encrypted relay protocol for XiotBox runtimes and clients.</strong><br />
9
+ TypeScript/Node implementation built on standard cryptographic primitives with zero runtime dependencies beyond Node.js crypto.
10
+ </p>
11
+
12
+ <p align="center">
13
+ <img alt="Package" src="https://img.shields.io/badge/npm-%40xiotbox%2Fe2e--channel-CB3837" />
14
+ <img alt="Version" src="https://img.shields.io/badge/version-0.1.0-1f6feb" />
15
+ <img alt="TypeScript" src="https://img.shields.io/badge/TypeScript-5.6%2B-3178C6" />
16
+ <img alt="Crypto" src="https://img.shields.io/badge/Crypto-X25519%20%7C%20HKDF%20%7C%20AES--256--GCM-2ea043" />
17
+ <img alt="License" src="https://img.shields.io/badge/License-MIT-555" />
18
+ </p>
19
+
20
+ <p align="center">
21
+ English · <a href="README.zh-CN.md">Simplified Chinese</a>
22
+ </p>
23
+
24
+ `@xiotbox/e2e-channel` provides the shared secure-channel implementation used when XiotBox needs to relay traffic through infrastructure that should not be able to read the application payload.
25
+
26
+ The protocol is transport-agnostic: Gateway can carry encrypted frames over an existing relay path while endpoint identity, key agreement, encryption, and message authenticity remain end-to-end concerns.
27
+
28
+ ---
29
+
30
+ ## Why this exists
31
+
32
+ XiotBox increasingly connects clients, AI Hosts, DSH/OpenClaw runtimes, device services, and remote control surfaces across machines and networks.
33
+
34
+ A relay is useful for reachability, but it should not automatically become trusted with every conversation, credential, code fragment, or local device payload that crosses it.
35
+
36
+ The secure-channel model is therefore:
37
+
38
+ ```text
39
+ Endpoint A
40
+ │
41
+ │ encrypt + authenticate
42
+ ▼
43
+ XiotBox Gateway / relay
44
+ │ sees ciphertext
45
+ ▼
46
+ Endpoint B
47
+ │ decrypt + verify
48
+ ▼
49
+ Application payload
50
+ ```
51
+
52
+ Gateway remains responsible for routing and connection ownership. This package is responsible for the end-to-end cryptographic envelope.
53
+
54
+ ---
55
+
56
+ ## Package
57
+
58
+ ```text
59
+ @xiotbox/e2e-channel
60
+ ```
61
+
62
+ Current package metadata:
63
+
64
+ ```text
65
+ version: 0.1.0
66
+ runtime: Node.js
67
+ language: TypeScript
68
+ license: MIT
69
+ runtime dependencies: node:crypto only
70
+ ```
71
+
72
+ ---
73
+
74
+ ## Cryptographic direction
75
+
76
+ The current protocol is built from standard primitives:
77
+
78
+ - X25519 for ephemeral key agreement;
79
+ - HKDF for key derivation;
80
+ - AES-256-GCM for authenticated encryption;
81
+ - Ed25519 for endpoint identity signatures.
82
+
83
+ The repository should not invent custom cryptographic primitives. Protocol evolution should remain explicit, versioned, and reviewable.
84
+
85
+ Full protocol specification:
86
+
87
+ ```text
88
+ docs/e2e-secure-channel.md
89
+ ```
90
+
91
+ ---
92
+
93
+ ## Current consumers
94
+
95
+ The package is designed as shared infrastructure rather than a DSH-specific implementation.
96
+
97
+ Known integration direction includes:
98
+
99
+ - `dsh-channel-xiotbox` secure tunnel mode;
100
+ - `openclaw-channel-xiotbox` and future runtime integrations where the same secure relay model is appropriate;
101
+ - future XiotBox client/runtime channels that need relay confidentiality without trusting the relay with plaintext.
102
+
103
+ A consumer may choose a different transport while reusing the same secure-channel contract.
104
+
105
+ ---
106
+
107
+ ## Build and test
108
+
109
+ ```bash
110
+ npm install
111
+ npm run typecheck
112
+ npm test
113
+ ```
114
+
115
+ The test command builds the package and executes the Node.js test suite.
116
+
117
+ Build only:
118
+
119
+ ```bash
120
+ npm run build
121
+ ```
122
+
123
+ Output is emitted under `lib/` according to the package TypeScript configuration.
124
+
125
+ ---
126
+
127
+ ## Security boundaries
128
+
129
+ This package does **not** decide:
130
+
131
+ - which user owns a device;
132
+ - which runtime a conversation belongs to;
133
+ - whether a caller may execute a tool;
134
+ - whether a workspace is readable/writable/executable;
135
+ - whether an endpoint should be trusted in the first place.
136
+
137
+ Those decisions belong to Gateway, runtime policy, trust configuration, and governance layers.
138
+
139
+ Encryption must not be mistaken for authorization.
140
+
141
+ ---
142
+
143
+ ## Trust model
144
+
145
+ Secure channels require endpoint identity handling in addition to encryption.
146
+
147
+ Integrations may use pinned identity keys or TOFU-style enrollment depending on the product flow, but the resulting trust decision must remain explicit and inspectable. A relay delivering bytes successfully is not proof that the peer is the intended endpoint.
148
+
149
+ ---
150
+
151
+ ## Design principles
152
+
153
+ 1. **Use standard primitives, not bespoke cryptography.**
154
+ 2. **Treat the relay as a router, not an application-data reader.**
155
+ 3. **Keep encryption separate from authorization.**
156
+ 4. **Make identity and trust decisions explicit.**
157
+ 5. **Keep the package reusable across runtimes and transports.**
158
+ 6. **Version protocol changes deliberately.**
159
+ 7. **Fail closed on authentication or integrity errors.**
160
+
161
+ ---
162
+
163
+ ## Current direction
164
+
165
+ `xiotbox-e2e-ts` is the shared cryptographic seam for XiotBox remote-runtime channels. Its job is intentionally narrow: give higher-level integrations a small, auditable secure-channel implementation so DSH, OpenClaw, clients, and future AI Hosts do not each invent their own incompatible encryption layer.
@@ -0,0 +1,163 @@
1
+ <p align="center">
2
+ <img src="docs/assets/xiotbox-logo.png" alt="XiotBox" width="112" />
3
+ </p>
4
+
5
+ <h1 align="center">XiotBox E2E Secure Channel</h1>
6
+
7
+ <p align="center">
8
+ <strong>XiotBox Runtime 与客户端之间可复用的端到端加密中继协议。</strong><br />
9
+ 基于 TypeScript/Node 和标准密码学原语,运行时只依赖 Node.js 自带 crypto。
10
+ </p>
11
+
12
+ <p align="center">
13
+ <img alt="Package" src="https://img.shields.io/badge/npm-%40xiotbox%2Fe2e--channel-CB3837" />
14
+ <img alt="Version" src="https://img.shields.io/badge/version-0.1.0-1f6feb" />
15
+ <img alt="TypeScript" src="https://img.shields.io/badge/TypeScript-5.6%2B-3178C6" />
16
+ <img alt="Crypto" src="https://img.shields.io/badge/Crypto-X25519%20%7C%20HKDF%20%7C%20AES--256--GCM-2ea043" />
17
+ <img alt="License" src="https://img.shields.io/badge/License-MIT-555" />
18
+ </p>
19
+
20
+ <p align="center">
21
+ <a href="README.md">English</a> · 简体中文
22
+ </p>
23
+
24
+ `@xiotbox/e2e-channel` 是 XiotBox 统一的安全通道实现,用于那些“必须经过 Gateway/中继,但中继本身不应该看到明文”的场景。
25
+
26
+ 它的职责很窄:提供端到端加密、身份签名和完整性保护。设备属于谁、Runtime 属于哪个用户、是否允许执行工具,这些都不是加密库该决定的。
27
+
28
+ ---
29
+
30
+ ## 为什么需要这一层
31
+
32
+ XiotBox 的客户端、AI Host、DSH、OpenClaw 和设备服务越来越多地运行在不同机器和网络里。
33
+
34
+ Gateway 解决“怎么找到对方、怎么把数据送过去”,但如果所有内容都在 Gateway 解密,就意味着会话、代码、凭据和本地设备数据都把 Gateway 变成了一个过度信任点。
35
+
36
+ 目标结构是:
37
+
38
+ ```text
39
+ 端点 A
40
+ │ 加密 + 身份认证
41
+ ▼
42
+ Gateway / Relay
43
+ │ 只看到密文
44
+ ▼
45
+ 端点 B
46
+ │ 解密 + 验证
47
+ ▼
48
+ 应用数据
49
+ ```
50
+
51
+ Gateway 继续负责路由,E2E Channel 负责端到端密码学边界。
52
+
53
+ ---
54
+
55
+ ## 包信息
56
+
57
+ ```text
58
+ @xiotbox/e2e-channel
59
+ ```
60
+
61
+ 当前:
62
+
63
+ ```text
64
+ version: 0.1.0
65
+ runtime: Node.js
66
+ language: TypeScript
67
+ license: MIT
68
+ runtime dependencies: node:crypto only
69
+ ```
70
+
71
+ ---
72
+
73
+ ## 密码学方案
74
+
75
+ 当前协议使用:
76
+
77
+ - X25519:临时密钥协商;
78
+ - HKDF:密钥派生;
79
+ - AES-256-GCM:认证加密;
80
+ - Ed25519:端点身份签名。
81
+
82
+ 这里的原则非常明确:**不自己发明密码算法。**
83
+
84
+ 完整协议:
85
+
86
+ ```text
87
+ docs/e2e-secure-channel.md
88
+ ```
89
+
90
+ ---
91
+
92
+ ## 谁在使用它
93
+
94
+ 这不是 DSH 专用库,而是 XiotBox 的共享安全协议层。
95
+
96
+ 当前/规划中的消费者包括:
97
+
98
+ - `dsh-channel-xiotbox` 的安全隧道;
99
+ - `openclaw-channel-xiotbox` 等需要同类安全中继的 Runtime;
100
+ - 未来 XiotBox Client / AI Host 的安全远程通道。
101
+
102
+ 底层传输可以是不同的,只要上层需要同一套端到端安全语义,就不应该再造一份加密实现。
103
+
104
+ ---
105
+
106
+ ## 构建和测试
107
+
108
+ ```bash
109
+ npm install
110
+ npm run typecheck
111
+ npm test
112
+ ```
113
+
114
+ 只构建:
115
+
116
+ ```bash
117
+ npm run build
118
+ ```
119
+
120
+ ---
121
+
122
+ ## 它不负责什么
123
+
124
+ E2E Channel 不负责:
125
+
126
+ - 用户和设备归属;
127
+ - Runtime / conversation ownership;
128
+ - Tool 权限;
129
+ - Workspace 读写执行权限;
130
+ - 谁可以获得管理员能力;
131
+ - 业务身份和审批。
132
+
133
+ 这些属于 Gateway、Runtime policy、MCP 和治理系统。
134
+
135
+ **加密不等于授权。** 一条连接可以是强加密的,同时连接的人仍然可能没有执行某个动作的权限。
136
+
137
+ ---
138
+
139
+ ## 身份与信任
140
+
141
+ 只有“加密成功”还不够,还需要确认对端到底是谁。
142
+
143
+ 不同产品流程可以使用 pinned identity 或 TOFU,但最终的信任结果必须是明确、可检查的。Gateway 能把包送到某个连接,不等于这个连接天然就是用户想要的那台设备。
144
+
145
+ ---
146
+
147
+ ## 核心原则
148
+
149
+ 1. **只使用标准密码学原语。**
150
+ 2. **中继负责送数据,不默认拥有明文。**
151
+ 3. **加密和授权分离。**
152
+ 4. **身份和 trust decision 必须明确。**
153
+ 5. **协议应被 DSH、OpenClaw、客户端等多种消费者复用。**
154
+ 6. **协议升级必须有版本和兼容边界。**
155
+ 7. **身份、完整性或认证失败时 fail closed。**
156
+
157
+ ---
158
+
159
+ ## 当前方向
160
+
161
+ `xiotbox-e2e-ts` 会成为 XiotBox 远程 Runtime 安全通信的共享密码学接缝。
162
+
163
+ 它不需要做成一个庞大的安全平台。恰恰相反,这一层越小、越清晰、越容易审计越好。这样未来增加新的 Agent Runtime 时,接入同一套安全协议即可,而不是每个团队重新发明一次“我们的 AES 包装方式”。
Binary file
@@ -0,0 +1,177 @@
1
+ # XiotBox E2E Secure Channel — Protocol Specification v1
2
+
3
+ Status: **Implemented** (TypeScript/Node + Dart client; plugin-side DSH tunnel
4
+ integrated). Cross-language implementations must match this document exactly —
5
+ any change here is a breaking wire change.
6
+
7
+ ## 1. Purpose
8
+
9
+ The XiotBox Gateway relay (`relay_service.py`) is an opaque byte pipe: it
10
+ forwards frames between a host agent (DSH, OpenClaw, future Codex / Claude
11
+ Code runtimes) and a XiotBox client without interpreting them. The E2E Secure
12
+ Channel sits **between an application protocol and that relay**, providing
13
+ end-to-end security against the Gateway itself (its operator, a compromise, or
14
+ a legal demand):
15
+
16
+ - **Confidentiality** — the Gateway only ever sees ciphertext frames.
17
+ - **Integrity** — AES-256-GCM AEAD rejects any modified frame.
18
+ - **Anti-replay / anti-reorder** — per-direction sequence counters bound in
19
+ the AAD.
20
+ - **Forward secrecy** — a fresh ephemeral X25519 key pair per channel; a
21
+ leaked session key never affects other channels.
22
+ - **Peer authentication** — Ed25519 identity signatures over the full
23
+ handshake transcript, pinned TOFU by each application.
24
+
25
+ ## 2. Transport assumptions
26
+
27
+ - The relay delivers **JSON frames**, base64-encoded as the relay `payload`
28
+ field (existing DERP wire format, unchanged).
29
+ - One SecureChannel instance = one relay channel. The session key lives in
30
+ memory only and is destroyed on close.
31
+ - All multi-byte values are **big-endian**; all binary payloads are base64.
32
+
33
+ ## 3. Identity & registration
34
+
35
+ Every device (host) owns one long-term identity, shared across all its agent
36
+ channels:
37
+
38
+ | Item | Algorithm | Purpose |
39
+ | --- | --- | --- |
40
+ | Long-term X25519 keypair | X25519 | `kid` in the handshake; registered with Lite via `POST /openclaw/devices/e2e/peer_key` |
41
+ | Ed25519 identity keypair | Ed25519 | signs the handshake transcript; fingerprint shown to the user for out-of-band verification |
42
+
43
+ - Key id: `sha256(pub)[:16]` hex.
44
+ - Identity fingerprint: full `sha256(SPKI-DER(pub))` hex.
45
+ - The **client** registers its own key + identity via
46
+ `POST /openclaw/bots/{bot_id}/e2e/client_key` and learns the host's
47
+ `peer_key_id`/`peer_public_key` from the response.
48
+ - The **host** learns the client's `client_key_id` and
49
+ `client_identity_public_key` from the `peer_key` registration response.
50
+ - Registration flows through the Gateway, which proxies to Lite. Lite's
51
+ device record is the trust anchor; see §8 for residual risks.
52
+
53
+ ## 4. Handshake (once per relay channel)
54
+
55
+ The client is always the **initiator**; the host plugin is the **responder**.
56
+
57
+ ```
58
+ C → P: {t:'hello', v:1, pub: <eph X25519 pub b64>, kid: <C key id>, nonce: <16B hex>}
59
+ P → C: {t:'hello_ack', v:1, pub: <eph X25519 pub b64>, kid: <P key id>, nonce: <16B hex>}
60
+ C → P: {t:'ident', v:1, id: <C Ed25519 pub raw b64>, fp: <fingerprint>, sig: <sig b64>}
61
+ P → C: {t:'ident_ack', v:1, id: <P Ed25519 pub raw b64>, fp: <fingerprint>, sig: <sig b64>}
62
+ ```
63
+
64
+ Both sides then derive:
65
+
66
+ ```
67
+ shared = ECDH(my_eph_priv, peer_eph_pub)
68
+ session_key = HKDF-SHA256(shared, salt=channel_id, info='xiotbox-e2e|v=1|stream', 32)
69
+ ```
70
+
71
+ ### 4.1 Validations
72
+
73
+ - `pub` must be exactly 32 bytes; `kid` and `nonce` non-empty.
74
+ - `kid` is checked against the peer's **registered** key id when known;
75
+ mismatch → close with `e2e_peer_kid_mismatch`.
76
+ - The `ident`/`ident_ack` signature is verified over the **handshake
77
+ transcript**; the claimed identity is then checked against the
78
+ application's trust policy (TOFU pin, registered identity, …). Failure →
79
+ close with `e2e_identity_sig_invalid` / `e2e_peer_identity_mismatch` /
80
+ `e2e_peer_identity_changed`.
81
+ - Handshake frames are plaintext but carry no secrets (public keys + nonces).
82
+ - Timeout: if the handshake does not complete in 10 s (configurable), the
83
+ channel closes with `e2e_handshake_timeout`.
84
+
85
+ ### 4.2 Handshake transcript (canonical, role-agnostic)
86
+
87
+ Both peers must compute the **identical string** for the same `dir`, so the
88
+ field order is canonical (`c_` = client/initiator, `p_` = plugin/responder):
89
+
90
+ ```
91
+ xiotbox-e2e|v=1|channel={channel_id}|dir={dir}|c_eph={C_eph_pub_b64}|p_eph={P_eph_pub_b64}|
92
+ c_kid={C_key_id}|p_kid={P_key_id}|c_nonce={C_nonce}|p_nonce={P_nonce}
93
+ ```
94
+
95
+ `dir` is `c2p` for the client's claim and `p2c` for the plugin's claim.
96
+
97
+ ## 5. Frame encryption
98
+
99
+ After the handshake, every application frame is wrapped:
100
+
101
+ ```
102
+ {t:'enc', v:1, iv: <12B b64>, tag: <16B b64>, ct: <b64>}
103
+ ```
104
+
105
+ - `ct` = AES-256-GCM ciphertext of the **application frame JSON**.
106
+ - `tag` = the GCM authentication tag (16 bytes).
107
+ - AAD (authenticated, not encrypted):
108
+
109
+ ```
110
+ xiotbox-e2e|v=1|stream|{channel_id}|{dir}|{seq}
111
+ ```
112
+
113
+ where `dir` is the direction the frame **travels** (`c2p` client→plugin,
114
+ `p2c` plugin→client) and `seq` is a per-direction counter starting at 1,
115
+ incremented per encrypted frame. This binds every frame to its channel,
116
+ direction and position → replay, reorder and cross-channel injection all
117
+ fail tag verification (`e2e_frame_auth_failed`).
118
+
119
+ ### 5.1 Plaintext control frames
120
+
121
+ Application keepalives (e.g. the tunnel's `{k:'ping'}` / `{k:'pong'}`) stay
122
+ outside the E2E layer and are relayed plaintext: they carry no sensitive
123
+ content and avoiding per-keepalive encryption keeps heartbeats cheap.
124
+
125
+ ## 6. Failure semantics
126
+
127
+ | Condition | Close reason |
128
+ | --- | --- |
129
+ | Encrypted frame before handshake | `enc_before_handshake` |
130
+ | Business frame in plaintext during handshake | `e2e_handshake_incomplete` |
131
+ | Business frame in plaintext after handshake (required mode) | `e2e_plaintext_rejected` |
132
+ | AEAD verification failure | `e2e_frame_auth_failed` |
133
+ | Peer key id mismatch | `e2e_peer_kid_mismatch` |
134
+ | Identity signature invalid | `e2e_identity_sig_invalid` |
135
+ | Identity rejected by trust policy | `e2e_peer_identity_mismatch` / `e2e_peer_identity_changed` |
136
+ | Handshake not completed in time | `e2e_handshake_timeout` |
137
+
138
+ Any close tears down the whole relay channel and destroys the session key.
139
+
140
+ ## 7. Implementations
141
+
142
+ | Platform | Location | Status |
143
+ | --- | --- | --- |
144
+ | TypeScript / Node (agents) | `src/` of this repo — `crypto.ts`, `secure-channel.ts` | ✅ tested |
145
+ | Dart (Flutter client) | `xiotbox-client-ui/lib/core/crypto/secure_channel.dart` | ✅ tested |
146
+ | Consumers | `dsh-channel-xiotbox` (DSH web tunnel) | ✅ integrated |
147
+ | Future: Python / Rust (e.g. Claude Code agents) | — | open |
148
+
149
+ All implementations must pass equivalent tests: handshake round-trip, tamper
150
+ rejection, replay rejection, cross-channel rejection, identity policy
151
+ rejection, timeout.
152
+
153
+ ## 8. Known residual risks (be honest with customers)
154
+
155
+ 1. **TOFU on first connection** — a MITM on the *first* handshake (before any
156
+ pin exists) can substitute its own identity. Mitigation: display the
157
+ fingerprint in the client UI and verify it out-of-band on the host
158
+ (SSH-style). This is the single most important follow-up.
159
+ 2. **Registration trust anchor** — key registration is proxied through the
160
+ Gateway to Lite. A Gateway that also compromises Lite's device records can
161
+ substitute keys at registration time; out-of-band fingerprint verification
162
+ is the countermeasure.
163
+ 3. **`e2e: off` downgrade path** — kept for fleet compatibility; must be
164
+ removed after all hosts and clients are upgraded.
165
+ 4. **No mid-session key rotation** — session keys live for one channel. For
166
+ very long-lived channels (>1 GB transferred or >10 min), add rotation on
167
+ the same AAD/seq machinery (documented as optional follow-up).
168
+ 5. **No formal verification / no third-party audit yet** — the construction
169
+ uses standard primitives in standard patterns (cf. Noise / WireGuard /
170
+ TLS 1.3), but a professional audit is required before commercial
171
+ "audited" claims.
172
+
173
+ ## 9. Versioning
174
+
175
+ - `v` field = protocol version, currently 1. Frame/AAD/transcript strings
176
+ embed `v=1` for domain separation.
177
+ - Breaking changes bump `v`; implementations must reject unknown versions.
@@ -0,0 +1,126 @@
1
+ /**
2
+ * XiotBox E2E Secure Channel — generic cryptographic primitives.
3
+ *
4
+ * Application-agnostic building blocks for the `xiotbox-e2e` protocol:
5
+ * X25519 key agreement, HKDF-SHA256 session-key derivation, AES-256-GCM
6
+ * frame sealing, Ed25519 identity signing, and per-device key persistence.
7
+ *
8
+ * Nothing here knows about DSH, OpenClaw, tunnels or chat: any agent channel
9
+ * that relays opaque bytes through the XiotBox Gateway can reuse this module.
10
+ * The wire protocol is documented in `docs/e2e-secure-channel.md`.
11
+ */
12
+ import crypto from 'node:crypto';
13
+ export declare const E2E_PROTOCOL = "xiotbox-e2e";
14
+ export declare const E2E_VERSION = 1;
15
+ export declare const E2E_MODE_STREAM = "stream";
16
+ /** Ed25519 SPKI DER prefix: SEQUENCE { SEQUENCE { OID 1.3.101.112 }, BIT STRING } */
17
+ export declare const ED25519_SPKI_PREFIX: Buffer<ArrayBuffer>;
18
+ export interface X25519Keypair {
19
+ priv: Buffer;
20
+ pub: Buffer;
21
+ keyId: string;
22
+ }
23
+ export interface Ed25519Identity {
24
+ privKey: crypto.KeyObject;
25
+ pubRaw: Buffer;
26
+ pubDer: Buffer;
27
+ fingerprint: string;
28
+ }
29
+ export interface StoredDeviceKeys {
30
+ v: 1;
31
+ x25519: {
32
+ priv_b64: string;
33
+ pub_b64: string;
34
+ key_id: string;
35
+ };
36
+ identity: {
37
+ priv_der_b64: string;
38
+ pub_der_b64: string;
39
+ fingerprint: string;
40
+ };
41
+ }
42
+ export declare function x25519Keypair(): {
43
+ priv: Buffer;
44
+ pub: Buffer;
45
+ };
46
+ export declare function x25519Shared(priv: Buffer, pub: Buffer): Buffer;
47
+ export declare function b64e(value: Buffer): string;
48
+ export declare function b64d(value: unknown): Buffer | undefined;
49
+ /** Key id: first 16 hex chars of SHA-256(public key) — matches OpenClaw / client. */
50
+ export declare function computeKeyId(pubRaw: Buffer): string;
51
+ /** Identity fingerprint: full SHA-256 hex of the SPKI DER public key. */
52
+ export declare function computeIdentityFingerprint(pubDer: Buffer): string;
53
+ /**
54
+ * Human-readable fingerprint for out-of-band verification: uppercase hex in
55
+ * groups of 4, e.g. `A1B2 C3D4 E5F6 …`. Host logs and client UIs must format
56
+ * identically so a user can compare them.
57
+ */
58
+ export declare function formatFingerprint(fingerprint: string): string;
59
+ /** Extract the raw 32-byte Ed25519 public key from SPKI DER. */
60
+ export declare function ed25519RawFromDer(pubDer: Buffer): Buffer;
61
+ /** Normalize a peer Ed25519 public key (raw 32B or SPKI DER 44B) to raw 32B. */
62
+ export declare function normalizeEd25519Pub(value: Buffer): Buffer | undefined;
63
+ /**
64
+ * Derive the per-channel session key.
65
+ * session_key = HKDF-SHA256(ECDH(eph), salt=channel_id, info='xiotbox-e2e|v=1|stream')
66
+ */
67
+ export declare function deriveSessionKey(shared: Buffer, channelId: string): Buffer;
68
+ /**
69
+ * AAD bound to every encrypted frame:
70
+ * 'xiotbox-e2e|v=1|stream|{channel_id}|{dir}|{seq}'
71
+ * `dir` is the direction the frame travels ('c2p' client→plugin, 'p2c' plugin→client).
72
+ */
73
+ export declare function buildFrameAad(channelId: string, dir: 'c2p' | 'p2c', seq: number): Buffer;
74
+ /**
75
+ * Handshake transcript signed by each side's Ed25519 identity key.
76
+ *
77
+ * Fields use a canonical role-agnostic order (`c_` = client/initiator side,
78
+ * `p_` = plugin/responder side) so both peers compute the identical string
79
+ * for the same `dir`, regardless of which side is signing.
80
+ */
81
+ export declare function buildHandshakeTranscript(opts: {
82
+ channelId: string;
83
+ dir: 'c2p' | 'p2c';
84
+ cEphPubB64: string;
85
+ pEphPubB64: string;
86
+ cKid: string;
87
+ pKid: string;
88
+ cNonce: string;
89
+ pNonce: string;
90
+ }): Buffer;
91
+ /**
92
+ * Registration identity claim, mirroring the OpenClaw `ocid|...` canonical
93
+ * format so Lite/clients that already verify bot identity claims accept the
94
+ * same signature scheme.
95
+ */
96
+ export declare function buildRegistrationSigPayload(opts: {
97
+ deviceId: string;
98
+ peerPubB64: string;
99
+ keyId: string;
100
+ ts: string;
101
+ nonce: string;
102
+ }): Buffer;
103
+ /** AES-256-GCM seal: returns { iv, tag, ct } all base64 for wire transport. */
104
+ export declare function sealFrame(key: Buffer, plaintext: Buffer, aad: Buffer): {
105
+ iv: string;
106
+ tag: string;
107
+ ct: string;
108
+ };
109
+ /** AES-256-GCM open: returns the plaintext Buffer, or undefined on auth failure. */
110
+ export declare function openFrame(key: Buffer, frame: {
111
+ iv: string;
112
+ tag: string;
113
+ ct: string;
114
+ }, aad: Buffer): Buffer | undefined;
115
+ export declare function generateEd25519Identity(): Ed25519Identity;
116
+ export declare function signMessage(privKey: crypto.KeyObject, message: Buffer): Buffer;
117
+ /** Verify an Ed25519 signature against a raw (32B) or SPKI DER (44B) public key. */
118
+ export declare function verifyMessage(pubKey: Buffer, message: Buffer, signature: Buffer): boolean;
119
+ /** Default key directory: `~/.xiotbox/e2e`. Consumers may pass their own. */
120
+ export declare function defaultE2EKeyDir(): string;
121
+ export declare function deviceKeyPath(keyDir: string, deviceId: string): string;
122
+ /** Load persisted keys for a device, creating them on first run. */
123
+ export declare function loadOrCreateDeviceKeys(deviceId: string, keyDir?: string, log?: (level: 'debug' | 'info' | 'warn', message: string) => void): {
124
+ keypair: X25519Keypair;
125
+ identity: Ed25519Identity;
126
+ };