@anycon/protocol 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.
Files changed (49) hide show
  1. package/LICENSE +176 -0
  2. package/README.md +146 -0
  3. package/README.zh-CN.md +118 -0
  4. package/dist/constants.d.ts +151 -0
  5. package/dist/constants.d.ts.map +1 -0
  6. package/dist/constants.js +159 -0
  7. package/dist/constants.js.map +1 -0
  8. package/dist/crypto.d.ts +202 -0
  9. package/dist/crypto.d.ts.map +1 -0
  10. package/dist/crypto.js +218 -0
  11. package/dist/crypto.js.map +1 -0
  12. package/dist/envelope.d.ts +105 -0
  13. package/dist/envelope.d.ts.map +1 -0
  14. package/dist/envelope.js +390 -0
  15. package/dist/envelope.js.map +1 -0
  16. package/dist/errors.d.ts +5 -0
  17. package/dist/errors.d.ts.map +1 -0
  18. package/dist/errors.js +5 -0
  19. package/dist/errors.js.map +1 -0
  20. package/dist/generated.d.ts +2007 -0
  21. package/dist/generated.d.ts.map +1 -0
  22. package/dist/generated.js +4 -0
  23. package/dist/generated.js.map +1 -0
  24. package/dist/index.d.ts +17 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +16 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/method-types.d.ts +293 -0
  29. package/dist/method-types.d.ts.map +1 -0
  30. package/dist/method-types.js +2 -0
  31. package/dist/method-types.js.map +1 -0
  32. package/dist/rpc.d.ts +70 -0
  33. package/dist/rpc.d.ts.map +1 -0
  34. package/dist/rpc.js +310 -0
  35. package/dist/rpc.js.map +1 -0
  36. package/package.json +62 -0
  37. package/src/constants.ts +173 -0
  38. package/src/crypto.ts +427 -0
  39. package/src/envelope.ts +469 -0
  40. package/src/errors.ts +4 -0
  41. package/src/generated.ts +2094 -0
  42. package/src/index.ts +18 -0
  43. package/src/method-types.ts +183 -0
  44. package/src/rpc.ts +377 -0
  45. package/wasm/THIRD-PARTY-LICENSES.txt +1957 -0
  46. package/wasm/anycon_crypto.d.ts +248 -0
  47. package/wasm/anycon_crypto.js +1250 -0
  48. package/wasm/anycon_crypto_bg.wasm +0 -0
  49. package/wasm/anycon_crypto_bg.wasm.d.ts +48 -0
@@ -0,0 +1,159 @@
1
+ /**
2
+ * 常量。全部镜像 `crates/protocol`,`constants.test.ts` 会解析 Rust 源码做漂移检查:
3
+ * 改了 Rust 侧的方法名 / 版本号 / close code / 错误码数值,这里不同步测试就会红。
4
+ */
5
+ /** 内层 JSON-RPC 协议版本。相邻 minor 兼容,0.x 阶段可 break。 */
6
+ export const PROTOCOL_VERSION = 1;
7
+ /** 外层信封版本。极少改动。 */
8
+ export const ENVELOPE_VERSION = 1;
9
+ /** 短码字母表:大写字母数字去掉 0 O 1 I L。 */
10
+ export const PAIR_CODE_ALPHABET = '23456789ABCDEFGHJKMNPQRSTUVWXYZ';
11
+ /** 短码长度:8 位(约 40 bit),`anycon pair --short` 生成的;只适合自建 hub,经不信任的 hub 配对不要用。 */
12
+ export const PAIR_CODE_LEN = 8;
13
+ /** 长短码长度:16 位(约 79 bit),`anycon pair` 的**默认**长度,链接 / 二维码 / `--json` 都是它。与 `crates/crypto` 的 `PAIR_CODE_LEN_LONG` 一致。 */
14
+ export const PAIR_CODE_LEN_LONG = 16;
15
+ /** WebSocket close code(hub → 设备)。 */
16
+ export const CLOSE_CODE = {
17
+ /** 同一设备的新连接顶掉了这条。不要自动重连,否则两条连接会互相踢。 */
18
+ SUPERSEDED: 4001,
19
+ /** hub 优雅关停。1–3 秒抖动后重连,而不是指数退避。 */
20
+ RESTARTING: 4002,
21
+ /** 接收太慢,发送缓冲持续满。 */
22
+ SLOW_CONSUMER: 4003,
23
+ /** 设备已被吊销。 */
24
+ REVOKED: 4004,
25
+ /** 第一帧 auth 超时。 */
26
+ AUTH_TIMEOUT: 4005,
27
+ };
28
+ /** 内层方法名。`域.动作`。键名与 `crates/protocol/src/methods.rs` 的常量名一致。 */
29
+ export const METHODS = {
30
+ // pairing(握手刚完成、PairDone 之后,daemon → joiner 的第一条加密通知;joiner 收到并校验后才落盘)
31
+ /** D→C notification,params = PairAck */
32
+ PAIR_ACK: 'pair.ack',
33
+ // node
34
+ NODE_INFO: 'node.info',
35
+ NODE_DEVICES: 'node.devices',
36
+ NODE_REVOKE_DEVICE: 'node.revoke_device',
37
+ NODE_PAIR_BEGIN: 'node.pair_begin',
38
+ NODE_STATS: 'node.stats',
39
+ NODE_DOCTOR: 'node.doctor',
40
+ NODE_AUDIT: 'node.audit',
41
+ NODE_SET_AGENT_DEFAULTS: 'node.set_agent_defaults',
42
+ // 账号内接入(ADR-36)
43
+ /** 进行中的接入会话。 */
44
+ NODE_ENROLL_LIST: 'node.enroll_list',
45
+ /** 批准一个接入请求:`code` 返回 6 位码,`scan` 带二维码里的密钥。 */
46
+ NODE_ENROLL_APPROVE: 'node.enroll_approve',
47
+ NODE_ENROLL_DENY: 'node.enroll_deny',
48
+ /** 主动生成 6 位接入码(「添加设备」、`anycon pair --enroll`)。 */
49
+ NODE_ENROLL_CODE: 'node.enroll_code',
50
+ NODE_ENROLL_STATUS: 'node.enroll_status',
51
+ NODE_SET_ENROLL_PASSWORD: 'node.set_enroll_password',
52
+ NODE_SET_ENROLL_ENABLED: 'node.set_enroll_enabled',
53
+ /** D→C notification,params = EnrollInfo */
54
+ NODE_ENROLL_REQUESTED: 'node.enroll_requested',
55
+ /** D→C notification,params = NodeEnrollResolved */
56
+ NODE_ENROLL_RESOLVED: 'node.enroll_resolved',
57
+ /** D→C notification,params = NodeSecurityEvent */
58
+ NODE_SECURITY_EVENT: 'node.security_event',
59
+ // agent
60
+ AGENT_LIST: 'agent.list',
61
+ AGENT_DIAGNOSE: 'agent.diagnose',
62
+ /** D→C notification,params = AgentListResult */
63
+ AGENT_CHANGED: 'agent.changed',
64
+ // workspace
65
+ WORKSPACE_LIST: 'workspace.list',
66
+ WORKSPACE_CREATE: 'workspace.create',
67
+ WORKSPACE_CLONE: 'workspace.clone',
68
+ WORKSPACE_RENAME: 'workspace.rename',
69
+ WORKSPACE_TRASH: 'workspace.trash',
70
+ WORKSPACE_TRASH_LIST: 'workspace.trash_list',
71
+ WORKSPACE_RESTORE: 'workspace.restore',
72
+ WORKSPACE_PURGE: 'workspace.purge',
73
+ /** 把本机已有目录关联为项目(params = WorkspaceLinkParams → Project)。 */
74
+ WORKSPACE_LINK: 'workspace.link',
75
+ /** 解除关联(不删除目录;params = WorkspaceNameParams)。 */
76
+ WORKSPACE_UNLINK: 'workspace.unlink',
77
+ /** 列本机目录供选择(params = WorkspaceBrowseParams → WorkspaceBrowseResult)。 */
78
+ WORKSPACE_BROWSE: 'workspace.browse',
79
+ /** 列项目的分支(params = WorkspaceBranchesParams → WorkspaceBranchesResult,ADR-34)。 */
80
+ WORKSPACE_BRANCHES: 'workspace.branches',
81
+ /** 列 anycon 建的 worktree(params = WorkspaceWorktreesParams → WorkspaceWorktreesResult)。 */
82
+ WORKSPACE_WORKTREES: 'workspace.worktrees',
83
+ /** 删掉一个已保留 / 没有会话的 worktree(params = WorkspaceRemoveWorktreeParams)。 */
84
+ WORKSPACE_REMOVE_WORKTREE: 'workspace.remove_worktree',
85
+ /** D→C notification */
86
+ WORKSPACE_CHANGED: 'workspace.changed',
87
+ /** D→C notification */
88
+ JOB_PROGRESS: 'job.progress',
89
+ /** D→C notification */
90
+ JOB_DONE: 'job.done',
91
+ JOB_CANCEL: 'job.cancel',
92
+ // fs
93
+ FS_LIST: 'fs.list',
94
+ FS_READ: 'fs.read',
95
+ // session
96
+ SESSION_LIST: 'session.list',
97
+ SESSION_GET: 'session.get',
98
+ SESSION_CREATE: 'session.create',
99
+ SESSION_SEND: 'session.send',
100
+ SESSION_CANCEL: 'session.cancel',
101
+ SESSION_SET_SETTINGS: 'session.set_settings',
102
+ SESSION_WATCH: 'session.watch',
103
+ SESSION_UNWATCH: 'session.unwatch',
104
+ SESSION_HISTORY: 'session.history',
105
+ /** D→C notification,params = SessionEvent */
106
+ SESSION_EVENT: 'session.event',
107
+ /** D→C notification,params = SessionChanged */
108
+ SESSION_CHANGED: 'session.changed',
109
+ SESSION_RENAME: 'session.rename',
110
+ SESSION_ARCHIVE: 'session.archive',
111
+ SESSION_UNARCHIVE: 'session.unarchive',
112
+ SESSION_DELETE: 'session.delete',
113
+ SESSION_RESTART: 'session.restart',
114
+ SESSION_SEARCH: 'session.search',
115
+ SESSION_PROMOTE: 'session.promote',
116
+ /** params = SessionGitStatusParams → SessionGitStatus(ADR-34) */
117
+ SESSION_GIT_STATUS: 'session.git_status',
118
+ /** params = SessionDiffParams → SessionDiffResult */
119
+ SESSION_DIFF: 'session.diff',
120
+ /** params = SessionSyncBaseParams → SessionSyncBaseResult */
121
+ SESSION_SYNC_BASE: 'session.sync_base',
122
+ // blob(附件,ADR-35)
123
+ /** params = BlobPutBeginParams → BlobPutBeginResult */
124
+ BLOB_PUT_BEGIN: 'blob.put_begin',
125
+ /** params = BlobPutChunkParams */
126
+ BLOB_PUT_CHUNK: 'blob.put_chunk',
127
+ /** params = BlobIdParams → AttachmentRef */
128
+ BLOB_PUT_END: 'blob.put_end',
129
+ /** params = BlobGetParams → BlobGetResult */
130
+ BLOB_GET: 'blob.get',
131
+ /** params = BlobIdParams */
132
+ BLOB_DISCARD: 'blob.discard',
133
+ // permission
134
+ /** D→C notification,params = PermissionRequest */
135
+ PERMISSION_REQUEST: 'permission.request',
136
+ PERMISSION_RESPOND: 'permission.respond',
137
+ /** D→C notification,params = PermissionResolved */
138
+ PERMISSION_RESOLVED: 'permission.resolved',
139
+ PERMISSION_LIST: 'permission.list',
140
+ };
141
+ /**
142
+ * `ErrorCode` → JSON-RPC 数字 code(`crates/protocol/src/error.rs` 的 `rpc_code`)。
143
+ * 客户端只应依赖 `error.data.code`;数字 code 仅在对端没带 `data` 时做兜底。
144
+ */
145
+ export const ERROR_CODE_NUMBERS = {
146
+ invalid: -32602,
147
+ invalid_settings: -32001,
148
+ agent_unavailable: -32002,
149
+ node_busy: -32003,
150
+ busy: -32004,
151
+ already_resolved: -32005,
152
+ exists: -32006,
153
+ not_found: -32007,
154
+ forbidden: -32008,
155
+ unsupported: -32009,
156
+ protocol_mismatch: -32010,
157
+ internal: -32603,
158
+ };
159
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,202 @@
1
+ export type Role = 'initiator' | 'responder';
2
+ /** X25519 静态密钥对。纯数据,可序列化保存(`secret` 是敏感信息,0600 / IndexedDB)。 */
3
+ export interface Keypair {
4
+ readonly public: Uint8Array;
5
+ readonly secret: Uint8Array;
6
+ }
7
+ /** `derivePairSecrets` 的结果。`psk` 是拷贝,用完 `fill(0)`。 */
8
+ export interface PairSecrets {
9
+ /** 16 个 hex,发给 hub 撮合。 */
10
+ readonly matchId: string;
11
+ /** 32 字节预共享密钥,不离开本机。 */
12
+ readonly psk: Uint8Array;
13
+ }
14
+ /** 握手完成后的双向加密通道。一个 Transport 只能单线程顺序使用。 */
15
+ export interface Transport {
16
+ /** 加密任意长度明文(自动分片)。 */
17
+ encrypt(plaintext: Uint8Array): Uint8Array;
18
+ /** 解密 `encrypt` 的输出,还原整条明文。乱序 / 篡改会抛错。 */
19
+ decrypt(ciphertext: Uint8Array): Uint8Array;
20
+ /** 对端静态公钥。 */
21
+ readonly remotePublic: Uint8Array;
22
+ /** 释放 wasm 内存。之后不能再用。 */
23
+ free(): void;
24
+ }
25
+ /** 进行中的握手。 */
26
+ export interface Handshake {
27
+ /** 现在该我写消息吗。 */
28
+ isMyTurn(): boolean;
29
+ /** 写下一条握手消息(可带 payload,通常为空)。 */
30
+ write(payload?: Uint8Array): Uint8Array;
31
+ /** 读对方的握手消息,返回其 payload。 */
32
+ read(message: Uint8Array): Uint8Array;
33
+ isFinished(): boolean;
34
+ /** 对方静态公钥;握手进行到能知道的时候起可用(IK responder 读完第一条后就有)。 */
35
+ remotePublic(): Uint8Array | undefined;
36
+ /** 握手完成后转成 Transport,之后这个 Handshake 不能再用。 */
37
+ intoTransport(): Transport;
38
+ /** 握手中途放弃时释放。 */
39
+ free(): void;
40
+ }
41
+ export interface Crypto {
42
+ generateKeypair(): Keypair;
43
+ keypairFromSecret(secret: Uint8Array): Keypair;
44
+ /** 配对握手 `Noise_XXpsk3`。 */
45
+ pairing(role: Role, local: Keypair, psk: Uint8Array): Handshake;
46
+ /** 重连握手 `Noise_IK`。initiator 必须给 `remotePublic`,responder 不给。 */
47
+ reconnect(role: Role, local: Keypair, remotePublic?: Uint8Array): Handshake;
48
+ /**
49
+ * 短码 → `{ matchId, psk }`,**配对路径用它,只算一次**。一次 Argon2id(`salt = "anycon-pair-v2"`,
50
+ * m = 64 MiB、t = 3、p = 1,输出 64 字节):前 8 字节的 hex 是 hub 撮合用的 `matchId`(16 个 hex,hub 永远看不到短码
51
+ * 本身),后 32 字节是 PSK。8 位或 16 位短码都合法,否则抛错。
52
+ *
53
+ * 耗时:Node / 本机 wasm 约 0.1–0.3 s,浏览器 wasm 约 0.1–1 s,会同步占用线程(UI 要先显示「正在派生密钥…」
54
+ * 再调用)。返回的 `psk` 是拷贝,调用方用完 `fill(0)`。
55
+ */
56
+ derivePairSecrets(code: string): PairSecrets;
57
+ /** 短码 → PSK(一次完整 Argon2id,约等于 `derivePairSecrets` 的耗时)。配对请直接用 `derivePairSecrets`,别和 `deriveMatchId` 各算一遍。 */
58
+ derivePsk(code: string): Uint8Array;
59
+ /** 短码 → hub 撮合用的 match id(16 个 hex;一次完整 Argon2id)。8 位或 16 位都合法,否则抛错。 */
60
+ deriveMatchId(code: string): string;
61
+ /** 大写,去掉 `-` 和空白。 */
62
+ normalizeCode(code: string): string;
63
+ /** 生成 `XXXX-XXXX` 形式的 8 位短码(`anycon pair --short`,约 40 bit,只适合自建 hub)。 */
64
+ generatePairCode(): string;
65
+ /** 生成 `XXXX-XXXX-XXXX-XXXX` 形式的 16 位短码(约 79 bit,`anycon pair` 的默认长度)。 */
66
+ generatePairCodeLong(): string;
67
+ /** 公钥指纹:`3f:a2:…`。 */
68
+ fingerprint(publicKey: Uint8Array): string;
69
+ }
70
+ /** 一方的 CPace 状态(账号内接入,ADR-36)。新设备用 `'initiator'`,daemon 用 `'responder'`。 */
71
+ export interface Cpace {
72
+ /** 自己的份额(32 字节),放进 `EnrollFrame.cpace` / `share` 发给对方。 */
73
+ readonly share: Uint8Array;
74
+ /** 用对方的份额得到 32 字节 PSK(喂给 `pairing`)。份额不合法时抛 `invalid key`。之后这个对象不能再用。 */
75
+ finish(role: Role, peerShare: Uint8Array): Uint8Array;
76
+ /** 中途放弃时释放(私有标量随之清零)。 */
77
+ free(): void;
78
+ }
79
+ /**
80
+ * 账号内接入(ADR-36,protocol.md § 4.4)用到的密码学。`loadCrypto()` 返回的对象带这些方法;
81
+ * 只做配对 / 重连的 fake 可以只实现 `Crypto`。
82
+ */
83
+ export interface EnrollCrypto {
84
+ /** `prs`:口令的 `deriveEnrollPasswordPrs`,或规范化后 6 位码的 ASCII 字节;`ci`:`enrollChannelId`;`sid`:`enrollSid`。 */
85
+ cpace(prs: Uint8Array, ci: Uint8Array, sid: Uint8Array): Cpace;
86
+ /** 16 字节随机数(`EnrollHello.nonce`)。 */
87
+ generateEnrollNonce(): Uint8Array;
88
+ /** 32 字节扫码密钥(放进二维码)。 */
89
+ generateEnrollSecret(): Uint8Array;
90
+ /** 去掉空白和 `-`,必须是 6 个数字,否则抛错。 */
91
+ normalizeEnrollCode(code: string): string;
92
+ /** 设置口令时校验:NFC 之后至少 8 个字符,否则抛 `enroll password too short`。 */
93
+ validateEnrollPassword(password: string): void;
94
+ /** `Argon2id(NFC(口令), salt)`,浏览器里约 0.5–1 s,会同步占用线程。`salt` 是 `EnrollReply.salt`(16 字节)。 */
95
+ deriveEnrollPasswordPrs(password: string, salt: Uint8Array): Uint8Array;
96
+ /** 扫码接入的 PSK:`HKDF-BLAKE2s(secret, info = "anycon-enroll-scan-v1" ‖ nodeId)`。 */
97
+ deriveEnrollScanPsk(secret: Uint8Array, nodeId: string): Uint8Array;
98
+ /** CPace 信道标识,`method` 是 `'code'` 或 `'password'`。 */
99
+ enrollChannelId(nodeId: string, method: string): Uint8Array;
100
+ /** CPace 会话标识:新设备的 nonce ‖ daemon 的 nonce。 */
101
+ enrollSid(initiatorNonce: Uint8Array, responderNonce: Uint8Array): Uint8Array;
102
+ }
103
+ /** `loadCrypto()` 的结果:配对 / 重连 / 传输 + 账号内接入。 */
104
+ export type FullCrypto = Crypto & EnrollCrypto;
105
+ /** wasm 产物不存在或加载失败。 */
106
+ export declare class CryptoUnavailableError extends Error {
107
+ readonly name = "CryptoUnavailableError";
108
+ }
109
+ interface RawKeypair {
110
+ readonly public: Uint8Array;
111
+ readonly secret: Uint8Array;
112
+ free(): void;
113
+ }
114
+ interface RawTransport {
115
+ encrypt(plaintext: Uint8Array): Uint8Array;
116
+ decrypt(ciphertext: Uint8Array): Uint8Array;
117
+ readonly remotePublic: Uint8Array | (() => Uint8Array);
118
+ free(): void;
119
+ }
120
+ interface RawHandshake {
121
+ readonly isMyTurn: boolean | (() => boolean);
122
+ write(payload: Uint8Array): Uint8Array;
123
+ read(message: Uint8Array): Uint8Array;
124
+ readonly isFinished: boolean | (() => boolean);
125
+ readonly remotePublic: Uint8Array | undefined | (() => Uint8Array | undefined);
126
+ intoTransport(): RawTransport;
127
+ free(): void;
128
+ }
129
+ interface RawCpace {
130
+ readonly share: Uint8Array;
131
+ finish(role: Role, peerShare: Uint8Array): Uint8Array;
132
+ free(): void;
133
+ }
134
+ interface RawPairSecrets {
135
+ readonly matchId: string;
136
+ readonly psk: Uint8Array;
137
+ free(): void;
138
+ }
139
+ /** 动态 import 得到的模块。 */
140
+ export interface WasmModule {
141
+ default: (input?: unknown) => Promise<unknown>;
142
+ WasmKeypair: {
143
+ generate(): RawKeypair;
144
+ fromSecret(secret: Uint8Array): RawKeypair;
145
+ };
146
+ WasmHandshake: {
147
+ pairing(role: Role, local: RawKeypair, psk: Uint8Array): RawHandshake;
148
+ reconnect(role: Role, local: RawKeypair, remotePublic?: Uint8Array): RawHandshake;
149
+ };
150
+ derivePairSecrets(code: string): RawPairSecrets;
151
+ derivePsk(code: string): Uint8Array;
152
+ deriveMatchId(code: string): string;
153
+ normalizeCode(code: string): string;
154
+ generatePairCode(): string;
155
+ generatePairCodeLong(): string;
156
+ fingerprint(publicKey: Uint8Array): string;
157
+ WasmCpace: new (prs: Uint8Array, ci: Uint8Array, sid: Uint8Array) => RawCpace;
158
+ generateEnrollNonce(): Uint8Array;
159
+ generateEnrollSecret(): Uint8Array;
160
+ normalizeEnrollCode(code: string): string;
161
+ validateEnrollPassword(password: string): void;
162
+ deriveEnrollPasswordPrs(password: string, salt: Uint8Array): Uint8Array;
163
+ deriveEnrollScanPsk(secret: Uint8Array, nodeId: string): Uint8Array;
164
+ enrollChannelId(nodeId: string, method: string): Uint8Array;
165
+ enrollSid(initiatorNonce: Uint8Array, responderNonce: Uint8Array): Uint8Array;
166
+ }
167
+ /** `.wasm` 的来源:URL / 字节 / 已编译的 Module / fetch 的 Response。 */
168
+ export type WasmSource = string | URL | BufferSource | WebAssembly.Module | Response | Promise<Response | BufferSource | WebAssembly.Module>;
169
+ export interface LoadCryptoOptions {
170
+ /**
171
+ * `.wasm` 的位置。浏览器里交给打包器:
172
+ * `loadCrypto({ wasm: new URL('@anycon/protocol/wasm/anycon_crypto_bg.wasm', import.meta.url) })`
173
+ * 或 Vite 的 `?url` 导入。Node / Bun 下不给就自动读 `../wasm/anycon_crypto_bg.wasm`。
174
+ */
175
+ wasm?: WasmSource;
176
+ /** 直接给 wasm-bindgen 的 JS 模块(打包器想自己控制 import 路径时用)。 */
177
+ module?: WasmModule | Promise<WasmModule>;
178
+ }
179
+ /**
180
+ * 加载并初始化 wasm。进程内只初始化一次(第二次调用返回同一个 Promise;失败不缓存,可重试)。
181
+ * wasm 产物不存在时抛 `CryptoUnavailableError`,消息里写明要跑 `just gen-wasm`。
182
+ */
183
+ export declare function loadCrypto(options?: LoadCryptoOptions): Promise<FullCrypto>;
184
+ /** 短码规范化(纯 JS):大写,去掉 `-` 和空白。与 `crates/crypto` 的 `normalize_code` 一致。 */
185
+ export declare function normalizePairCode(code: string): string;
186
+ /** 短码格式校验:规范化后是 8 位或 16 位,且都在字母表内。纯 JS,不需要 wasm。 */
187
+ export declare function isValidPairCode(code: string): boolean;
188
+ /**
189
+ * `XXXXXXXX` → `XXXX-XXXX`,16 位 → `XXXX-XXXX-XXXX-XXXX`(每 4 位一组)。
190
+ * 长度既不是 8 也不是 16 时原样返回规范化结果。
191
+ */
192
+ export declare function formatPairCode(code: string): string;
193
+ /**
194
+ * 从用户输入里取出短码:接受裸短码(8 / 16 位,大小写、`-`、空白随意)、完整配对链接
195
+ * `https://hub.example/pair#code=XXXX-XXXX`、或只有 fragment 的 `#code=…` / `code=…`。
196
+ * 短码在 fragment 里,浏览器不会把它发给 hub。格式不合法返回 `undefined`。
197
+ */
198
+ export declare function extractPairCode(input: string): string | undefined;
199
+ export declare function hexOf(bytes: Uint8Array): string;
200
+ export declare function bytesOfHex(hex: string): Uint8Array;
201
+ export {};
202
+ //# sourceMappingURL=crypto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"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"}
package/dist/crypto.js ADDED
@@ -0,0 +1,218 @@
1
+ /**
2
+ * `crates/crypto` 的 wasm 封装。`loadCrypto()` 动态加载 `../wasm/anycon_crypto.js`(`just gen-wasm` 的产物,
3
+ * 已 gitignore),初始化后返回一个 Promise 无关的同步 API:`Uint8Array` 进出,没有 wasm-bindgen 的所有权语义。
4
+ *
5
+ * 为什么包一层:
6
+ * - 密钥对是纯数据(`public` / `secret`),每次握手时才在 wasm 里临时重建,不存在「传进去就被消费」的问题;
7
+ * - 隐藏 wasm-bindgen 产物的细节(getter / 方法、`init` 签名、Node 下读 .wasm 文件);
8
+ * - SDK 和测试可以注入别的实现(`Crypto` 接口),比如不需要真密码学的 fake。
9
+ *
10
+ * 角色:配对(XXpsk3)joiner 是 initiator、host(daemon)是 responder;重连(IK)客户端是 initiator。
11
+ */
12
+ import { PAIR_CODE_ALPHABET, PAIR_CODE_LEN, PAIR_CODE_LEN_LONG } from './constants.js';
13
+ /** wasm 产物不存在或加载失败。 */
14
+ export class CryptoUnavailableError extends Error {
15
+ name = 'CryptoUnavailableError';
16
+ }
17
+ function member(obj, name) {
18
+ const value = obj[name];
19
+ return (typeof value === 'function' ? value.call(obj) : value);
20
+ }
21
+ function wrapTransport(raw) {
22
+ const remote = member(raw, 'remotePublic').slice();
23
+ return {
24
+ encrypt: (plaintext) => raw.encrypt(plaintext),
25
+ decrypt: (ciphertext) => raw.decrypt(ciphertext),
26
+ remotePublic: remote,
27
+ free: () => raw.free(),
28
+ };
29
+ }
30
+ function wrapHandshake(raw) {
31
+ return {
32
+ isMyTurn: () => member(raw, 'isMyTurn'),
33
+ write: (payload) => raw.write(payload ?? new Uint8Array(0)),
34
+ read: (message) => raw.read(message),
35
+ isFinished: () => member(raw, 'isFinished'),
36
+ remotePublic: () => {
37
+ const key = member(raw, 'remotePublic');
38
+ return key ? key.slice() : undefined;
39
+ },
40
+ intoTransport: () => wrapTransport(raw.intoTransport()),
41
+ free: () => raw.free(),
42
+ };
43
+ }
44
+ function toKeypair(raw) {
45
+ const kp = { public: raw.public.slice(), secret: raw.secret.slice() };
46
+ raw.free();
47
+ return kp;
48
+ }
49
+ function wrapCpace(raw) {
50
+ let done = false;
51
+ return {
52
+ share: raw.share.slice(),
53
+ // wasm 侧 `finish` 按值消费对象,之后 `free` 是空操作
54
+ finish: (role, peerShare) => {
55
+ done = true;
56
+ return raw.finish(role, peerShare);
57
+ },
58
+ free: () => {
59
+ if (!done)
60
+ raw.free();
61
+ done = true;
62
+ },
63
+ };
64
+ }
65
+ function wrapModule(mod) {
66
+ // 传给 wasm 的 Keypair 每次临时重建。握手对象建好后释放它:wasm 若按值消费了它,free 是空操作。
67
+ const withKeypair = (kp, build) => {
68
+ const raw = mod.WasmKeypair.fromSecret(kp.secret);
69
+ try {
70
+ return wrapHandshake(build(raw));
71
+ }
72
+ finally {
73
+ try {
74
+ raw.free();
75
+ }
76
+ catch {
77
+ // 已被消费
78
+ }
79
+ }
80
+ };
81
+ return {
82
+ generateKeypair: () => toKeypair(mod.WasmKeypair.generate()),
83
+ keypairFromSecret: (secret) => toKeypair(mod.WasmKeypair.fromSecret(secret)),
84
+ pairing: (role, local, psk) => withKeypair(local, (raw) => mod.WasmHandshake.pairing(role, raw, psk)),
85
+ reconnect: (role, local, remotePublic) => withKeypair(local, (raw) => mod.WasmHandshake.reconnect(role, raw, remotePublic)),
86
+ derivePairSecrets: (code) => {
87
+ const raw = mod.derivePairSecrets(code);
88
+ try {
89
+ // `psk` getter 每次返回一份新的 JS 拷贝(wasm 内存里的原件随 `free()` 清零),所以直接交给调用方,
90
+ // 不再 `.slice()` 多复制一份——多出来的那份没人能清零。调用方用完要 `psk.fill(0)`。
91
+ return { matchId: raw.matchId, psk: raw.psk };
92
+ }
93
+ finally {
94
+ raw.free();
95
+ }
96
+ },
97
+ derivePsk: (code) => mod.derivePsk(code),
98
+ deriveMatchId: (code) => mod.deriveMatchId(code),
99
+ normalizeCode: (code) => mod.normalizeCode(code),
100
+ generatePairCode: () => mod.generatePairCode(),
101
+ generatePairCodeLong: () => mod.generatePairCodeLong(),
102
+ fingerprint: (publicKey) => mod.fingerprint(publicKey),
103
+ cpace: (prs, ci, sid) => wrapCpace(new mod.WasmCpace(prs, ci, sid)),
104
+ generateEnrollNonce: () => mod.generateEnrollNonce(),
105
+ generateEnrollSecret: () => mod.generateEnrollSecret(),
106
+ normalizeEnrollCode: (code) => mod.normalizeEnrollCode(code),
107
+ validateEnrollPassword: (password) => mod.validateEnrollPassword(password),
108
+ deriveEnrollPasswordPrs: (password, salt) => mod.deriveEnrollPasswordPrs(password, salt),
109
+ deriveEnrollScanPsk: (secret, nodeId) => mod.deriveEnrollScanPsk(secret, nodeId),
110
+ enrollChannelId: (nodeId, method) => mod.enrollChannelId(nodeId, method),
111
+ enrollSid: (a, b) => mod.enrollSid(a, b),
112
+ };
113
+ }
114
+ const isNode = () => typeof globalThis.process?.versions?.node ===
115
+ 'string';
116
+ async function importWasmModule() {
117
+ try {
118
+ // 字面量路径:打包器(Vite 等)能静态分析并把 wasm 胶水 js 打进来。
119
+ // @ts-ignore -- 产物由 `just gen-wasm` 生成,可能不存在,类型也由本文件的 WasmModule 描述。
120
+ return (await import('../wasm/anycon_crypto.js'));
121
+ }
122
+ catch (cause) {
123
+ throw new CryptoUnavailableError('anycon crypto wasm is not built: packages/protocol/wasm/anycon_crypto.js is missing. ' +
124
+ 'Run `just gen-wasm` (wasm-pack build of crates/crypto) first.', { cause });
125
+ }
126
+ }
127
+ async function nodeWasmBytes() {
128
+ // 变量指定符:不让浏览器打包器解析 node 内置模块。
129
+ const fsSpecifier = 'node:fs/promises';
130
+ const { readFile } = (await import(/* @vite-ignore */ fsSpecifier));
131
+ const url = new URL('../wasm/anycon_crypto_bg.wasm', import.meta.url);
132
+ try {
133
+ return await readFile(url);
134
+ }
135
+ catch (cause) {
136
+ throw new CryptoUnavailableError(`anycon crypto wasm binary not found at ${url.pathname}. Run \`just gen-wasm\`.`, { cause });
137
+ }
138
+ }
139
+ async function initialize(options) {
140
+ const mod = await (options.module ?? importWasmModule());
141
+ const input = options.wasm ?? (isNode() ? await nodeWasmBytes() : undefined);
142
+ // wasm-bindgen ≥ 0.2.93 的对象形参;不给 input 时浏览器走胶水 js 里的 `new URL(…, import.meta.url)`。
143
+ await mod.default(input === undefined ? undefined : { module_or_path: input });
144
+ return wrapModule(mod);
145
+ }
146
+ let cached;
147
+ /**
148
+ * 加载并初始化 wasm。进程内只初始化一次(第二次调用返回同一个 Promise;失败不缓存,可重试)。
149
+ * wasm 产物不存在时抛 `CryptoUnavailableError`,消息里写明要跑 `just gen-wasm`。
150
+ */
151
+ export function loadCrypto(options = {}) {
152
+ if (!cached) {
153
+ cached = initialize(options).catch((err) => {
154
+ cached = undefined;
155
+ throw err;
156
+ });
157
+ }
158
+ return cached;
159
+ }
160
+ /** 短码规范化(纯 JS):大写,去掉 `-` 和空白。与 `crates/crypto` 的 `normalize_code` 一致。 */
161
+ export function normalizePairCode(code) {
162
+ return code.replace(/[\s-]/g, '').toUpperCase();
163
+ }
164
+ /** 短码格式校验:规范化后是 8 位或 16 位,且都在字母表内。纯 JS,不需要 wasm。 */
165
+ export function isValidPairCode(code) {
166
+ const normalized = normalizePairCode(code);
167
+ if (normalized.length !== PAIR_CODE_LEN && normalized.length !== PAIR_CODE_LEN_LONG)
168
+ return false;
169
+ for (const ch of normalized)
170
+ if (!PAIR_CODE_ALPHABET.includes(ch))
171
+ return false;
172
+ return true;
173
+ }
174
+ /**
175
+ * `XXXXXXXX` → `XXXX-XXXX`,16 位 → `XXXX-XXXX-XXXX-XXXX`(每 4 位一组)。
176
+ * 长度既不是 8 也不是 16 时原样返回规范化结果。
177
+ */
178
+ export function formatPairCode(code) {
179
+ const normalized = normalizePairCode(code);
180
+ if (normalized.length !== PAIR_CODE_LEN && normalized.length !== PAIR_CODE_LEN_LONG) {
181
+ return normalized;
182
+ }
183
+ return normalized.match(/.{4}/g).join('-');
184
+ }
185
+ /**
186
+ * 从用户输入里取出短码:接受裸短码(8 / 16 位,大小写、`-`、空白随意)、完整配对链接
187
+ * `https://hub.example/pair#code=XXXX-XXXX`、或只有 fragment 的 `#code=…` / `code=…`。
188
+ * 短码在 fragment 里,浏览器不会把它发给 hub。格式不合法返回 `undefined`。
189
+ */
190
+ export function extractPairCode(input) {
191
+ const text = input.trim();
192
+ if (text === '')
193
+ return undefined;
194
+ let candidate = text;
195
+ const hashAt = text.indexOf('#');
196
+ if (hashAt >= 0) {
197
+ candidate = new URLSearchParams(text.slice(hashAt + 1)).get('code') ?? '';
198
+ }
199
+ else if (/^code=/i.test(text)) {
200
+ candidate = new URLSearchParams(text).get('code') ?? '';
201
+ }
202
+ return isValidPairCode(candidate) ? formatPairCode(candidate) : undefined;
203
+ }
204
+ export function hexOf(bytes) {
205
+ let out = '';
206
+ for (const b of bytes)
207
+ out += b.toString(16).padStart(2, '0');
208
+ return out;
209
+ }
210
+ export function bytesOfHex(hex) {
211
+ if (hex.length % 2 !== 0 || /[^0-9a-fA-F]/.test(hex))
212
+ throw new Error('invalid hex string');
213
+ const out = new Uint8Array(hex.length / 2);
214
+ for (let i = 0; i < out.length; i++)
215
+ out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
216
+ return out;
217
+ }
218
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"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"}