@labacacia/nps-sdk 1.0.0-alpha.1 → 1.0.0-alpha.2

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 (46) hide show
  1. package/.npmrc.publish +1 -0
  2. package/CHANGELOG.cn.md +39 -0
  3. package/CHANGELOG.md +39 -0
  4. package/CONTRIBUTING.cn.md +35 -0
  5. package/CONTRIBUTING.md +2 -0
  6. package/README.cn.md +155 -0
  7. package/README.md +5 -3
  8. package/dist/core/frames.d.ts +1 -0
  9. package/dist/core/frames.d.ts.map +1 -1
  10. package/dist/core/frames.js +1 -0
  11. package/dist/core/frames.js.map +1 -1
  12. package/dist/core/index.d.ts +6 -4
  13. package/dist/core/index.d.ts.map +1 -1
  14. package/dist/core/index.js +17 -5
  15. package/dist/core/index.js.map +1 -1
  16. package/dist/index.d.ts +1 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +1 -1
  19. package/dist/index.js.map +1 -1
  20. package/dist/ncp/frames.d.ts +18 -0
  21. package/dist/ncp/frames.d.ts.map +1 -1
  22. package/dist/ncp/frames.js +45 -0
  23. package/dist/ncp/frames.js.map +1 -1
  24. package/dist/ncp/registry.d.ts.map +1 -1
  25. package/dist/ncp/registry.js +2 -1
  26. package/dist/ncp/registry.js.map +1 -1
  27. package/doc/nps-sdk.core.cn.md +321 -0
  28. package/doc/nps-sdk.core.md +326 -0
  29. package/doc/nps-sdk.ncp.cn.md +270 -0
  30. package/doc/nps-sdk.ncp.md +276 -0
  31. package/doc/nps-sdk.ndp.cn.md +267 -0
  32. package/doc/nps-sdk.ndp.md +273 -0
  33. package/doc/nps-sdk.nip.cn.md +235 -0
  34. package/doc/nps-sdk.nip.md +242 -0
  35. package/doc/nps-sdk.nop.cn.md +329 -0
  36. package/doc/nps-sdk.nop.md +332 -0
  37. package/doc/nps-sdk.nwp.cn.md +217 -0
  38. package/doc/nps-sdk.nwp.md +224 -0
  39. package/doc/overview.cn.md +149 -0
  40. package/doc/overview.md +153 -0
  41. package/package.json +21 -4
  42. package/src/core/frames.ts +1 -0
  43. package/src/core/index.ts +37 -5
  44. package/src/index.ts +1 -1
  45. package/src/ncp/frames.ts +52 -0
  46. package/src/ncp/registry.ts +2 -1
@@ -0,0 +1,235 @@
1
+ [English Version](./nps-sdk.nip.md) | 中文版
2
+
3
+ # `@labacacia/nps-sdk/nip` — 类与方法参考
4
+
5
+ > 规范:[NPS-3 NIP v0.2](https://github.com/labacacia/NPS-Release/blob/main/spec/NPS-3-NIP.md)
6
+
7
+ NIP 是 NPS 的 TLS/PKI。本模块暴露三个身份帧
8
+ (`IdentFrame`、`TrustFrame`、`RevokeFrame`)、元数据接口,
9
+ 以及拥有 Ed25519 密钥对(可选 AES-256-GCM + PBKDF2-SHA256 密钥文件加密)的
10
+ `NipIdentity` 辅助类。
11
+
12
+ ---
13
+
14
+ ## 目录
15
+
16
+ - [`IdentMetadata`](#identmetadata)
17
+ - [`IdentFrame` (0x20)](#identframe-0x20)
18
+ - [`TrustFrame` (0x21)](#trustframe-0x21)
19
+ - [`RevokeFrame` (0x22)](#revokeframe-0x22)
20
+ - [`NipIdentity`](#nipidentity)
21
+ - [规范化 JSON 与签名格式](#规范化-json-与签名格式)
22
+
23
+ ---
24
+
25
+ ## `IdentMetadata`
26
+
27
+ ```typescript
28
+ interface IdentMetadata {
29
+ issuer: string;
30
+ issuedAt: string;
31
+ expiresAt?: string;
32
+ capabilities?: readonly string[];
33
+ scopes?: readonly string[];
34
+ }
35
+ ```
36
+
37
+ 附在 `IdentFrame.metadata`。由 `unsignedDict()` 产生的签名 payload
38
+ **排除**此字段 —— 元数据是运行时可变的,不属于 agent 身份本身。
39
+
40
+ ---
41
+
42
+ ## `IdentFrame` (0x20)
43
+
44
+ Agent 身份证书。作为任何认证 session 的开场帧发送。
45
+
46
+ ```typescript
47
+ class IdentFrame {
48
+ readonly frameType: FrameType.IDENT;
49
+ readonly preferredTier: EncodingTier.MSGPACK;
50
+
51
+ constructor(
52
+ public readonly nid: string, // urn:nps:agent:{authority}:{name}
53
+ public readonly pubKey: string, // "ed25519:{hex}"
54
+ public readonly metadata: IdentMetadata,
55
+ public readonly signature: string, // "ed25519:{base64}"
56
+ );
57
+
58
+ unsignedDict(): Record<string, unknown>; // { nid, pub_key, metadata } — 签名 payload
59
+ toDict(): Record<string, unknown>; // unsignedDict + signature
60
+
61
+ static fromDict(data: Record<string, unknown>): IdentFrame;
62
+ }
63
+ ```
64
+
65
+ `unsignedDict()` 是规范签名 payload —— 它省略 `signature` 字段。
66
+ 与 `NipIdentity.sign()` 配对使用以产生自签名 `signature`。
67
+
68
+ ---
69
+
70
+ ## `TrustFrame` (0x21)
71
+
72
+ 跨 CA 信任证书。
73
+
74
+ ```typescript
75
+ class TrustFrame {
76
+ readonly frameType: FrameType.TRUST;
77
+ readonly preferredTier: EncodingTier.MSGPACK;
78
+
79
+ constructor(
80
+ public readonly issuerNid: string,
81
+ public readonly subjectNid: string,
82
+ public readonly scopes: readonly string[],
83
+ public readonly expiresAt: string, // ISO 8601 UTC
84
+ public readonly signature: string, // "ed25519:{base64}"
85
+ );
86
+
87
+ toDict(): Record<string, unknown>;
88
+ static fromDict(data: Record<string, unknown>): TrustFrame;
89
+ }
90
+ ```
91
+
92
+ ---
93
+
94
+ ## `RevokeFrame` (0x22)
95
+
96
+ 证书吊销。
97
+
98
+ ```typescript
99
+ class RevokeFrame {
100
+ readonly frameType: FrameType.REVOKE;
101
+ readonly preferredTier: EncodingTier.MSGPACK;
102
+
103
+ constructor(
104
+ public readonly nid: string,
105
+ public readonly reason?: string, // 如 "key_compromise"
106
+ public readonly revokedAt?: string, // ISO 8601 UTC
107
+ );
108
+
109
+ toDict(): Record<string, unknown>;
110
+ static fromDict(data: Record<string, unknown>): RevokeFrame;
111
+ }
112
+ ```
113
+
114
+ 由签发 CA 签名。验证者**必须**拒绝其 `nid` 被有效 `RevokeFrame` 覆盖的
115
+ 任何 `IdentFrame`。
116
+
117
+ ---
118
+
119
+ ## `NipIdentity`
120
+
121
+ Ed25519 密钥对管理器,可选加密密钥文件持久化。基于
122
+ `@noble/ed25519` + `node:crypto`。
123
+
124
+ ```typescript
125
+ class NipIdentity {
126
+ // 工厂
127
+ static generate(): NipIdentity;
128
+ static fromPrivateKey(privKey: Uint8Array): NipIdentity;
129
+ static load(path: string, passphrase: string): NipIdentity;
130
+
131
+ // 持久化
132
+ save(path: string, passphrase: string): void;
133
+
134
+ // 签名
135
+ sign(payload: Record<string, unknown>): string; // "ed25519:{base64}"
136
+ verify(payload: Record<string, unknown>, signature: string): boolean;
137
+
138
+ // 公钥访问
139
+ readonly pubKey: Uint8Array; // 32 字节
140
+ readonly pubKeyString: string; // "ed25519:{hex}"
141
+ }
142
+ ```
143
+
144
+ ### 密钥文件格式
145
+
146
+ `save` / `load` 写入一个版本化的 JSON 信封,包含:
147
+
148
+ ```
149
+ {
150
+ version: 1,
151
+ salt: hex(16 B),
152
+ iv: hex(12 B),
153
+ ciphertext: hex( AES-GCM(privateKey) || authTag(16 B) ),
154
+ pubKey: hex(32 B)
155
+ }
156
+ ```
157
+
158
+ 密钥派生:**PBKDF2-SHA256**,600 000 次迭代。
159
+ 加密:**AES-256-GCM** —— 16 字节认证 tag 追加到 `ciphertext` 字段内的
160
+ 密文后。
161
+
162
+ ### `generate()`
163
+
164
+ 生成新的 Ed25519 密钥对。不接触磁盘。
165
+
166
+ ### `fromPrivateKey(priv)`
167
+
168
+ 包装已有的 32 字节 Ed25519 私钥(派生匹配的公钥)。
169
+
170
+ ### `load(path, passphrase)`
171
+
172
+ 读取并解密先前保存的密钥文件。若 JSON 信封格式错误、auth tag
173
+ 无效,或 passphrase 错误,则抛异常。
174
+
175
+ ### `save(path, passphrase)`
176
+
177
+ 加密并写入密钥对到 `path`。文件已存在时被覆盖 —— 先备份。
178
+
179
+ ### `sign(payload)` / `verify(payload, signature)`
180
+
181
+ 规范化 `payload`(键排序、紧凑分隔符),运行 Ed25519,发出
182
+ `"ed25519:{base64}"`。`verify` 任何失败时返回 `false` —— 从不抛异常。
183
+
184
+ ---
185
+
186
+ ## 规范化 JSON 与签名格式
187
+
188
+ SDK 以如下方式规范化签名 payload:
189
+
190
+ ```js
191
+ JSON.stringify(payload, Object.keys(payload).sort());
192
+ ```
193
+
194
+ - 每一层键按字典序排序。
195
+ - `undefined` 键由 `JSON.stringify` 隐式丢弃。
196
+ - token 之间无空白。
197
+ - 输出 UTF-8 字节馈入 Ed25519 原语。
198
+
199
+ 对 `IdentFrame`,将 `unsignedDict()` 作为 payload —— 它已省略 `signature`。
200
+
201
+ ---
202
+
203
+ ## 端到端示例
204
+
205
+ ```typescript
206
+ import {
207
+ IdentFrame, IdentMetadata, NipIdentity,
208
+ } from "@labacacia/nps-sdk/nip";
209
+
210
+ // 1) 一次性:创建密钥对并持久化
211
+ const id = NipIdentity.generate();
212
+ id.save("./agent.key", "correct horse battery");
213
+
214
+ // 2) 构造并签名 IdentFrame
215
+ const meta: IdentMetadata = {
216
+ issuer: "urn:nps:ca:example.com:root",
217
+ issuedAt: new Date().toISOString(),
218
+ expiresAt: new Date(Date.now() + 30 * 86400_000).toISOString(),
219
+ capabilities: ["nwp:query", "nop:delegate"],
220
+ };
221
+
222
+ const unsigned = new IdentFrame(
223
+ "urn:nps:agent:example.com:agent-001",
224
+ id.pubKeyString,
225
+ meta,
226
+ "placeholder",
227
+ );
228
+
229
+ const signature = id.sign(unsigned.unsignedDict());
230
+ const signed = new IdentFrame(unsigned.nid, unsigned.pubKey, meta, signature);
231
+
232
+ // 3) 任何持有相同密钥对(或等价 pubKey)的一方均可验证
233
+ const ok = id.verify(signed.unsignedDict(), signed.signature);
234
+ // → true
235
+ ```
@@ -0,0 +1,242 @@
1
+ English | [中文版](./nps-sdk.nip.cn.md)
2
+
3
+ # `@labacacia/nps-sdk/nip` — Class and Method Reference
4
+
5
+ > Spec: [NPS-3 NIP v0.2](https://github.com/labacacia/NPS-Release/blob/main/spec/NPS-3-NIP.md)
6
+
7
+ NIP is the TLS/PKI of NPS. This module exposes the three identity frames
8
+ (`IdentFrame`, `TrustFrame`, `RevokeFrame`), the metadata interface, and
9
+ the `NipIdentity` helper that owns an Ed25519 keypair with optional
10
+ AES-256-GCM + PBKDF2-SHA256 key-file encryption.
11
+
12
+ ---
13
+
14
+ ## Table of contents
15
+
16
+ - [`IdentMetadata`](#identmetadata)
17
+ - [`IdentFrame` (0x20)](#identframe-0x20)
18
+ - [`TrustFrame` (0x21)](#trustframe-0x21)
19
+ - [`RevokeFrame` (0x22)](#revokeframe-0x22)
20
+ - [`NipIdentity`](#nipidentity)
21
+ - [Canonical JSON & signing format](#canonical-json--signing-format)
22
+
23
+ ---
24
+
25
+ ## `IdentMetadata`
26
+
27
+ ```typescript
28
+ interface IdentMetadata {
29
+ issuer: string;
30
+ issuedAt: string;
31
+ expiresAt?: string;
32
+ capabilities?: readonly string[];
33
+ scopes?: readonly string[];
34
+ }
35
+ ```
36
+
37
+ Attached to `IdentFrame.metadata`. Excluded from the signed payload
38
+ produced by `unsignedDict()` — metadata is runtime-mutable and not part
39
+ of the agent's identity.
40
+
41
+ ---
42
+
43
+ ## `IdentFrame` (0x20)
44
+
45
+ Agent identity certificate. Sent as the opening frame on any
46
+ authenticated session.
47
+
48
+ ```typescript
49
+ class IdentFrame {
50
+ readonly frameType: FrameType.IDENT;
51
+ readonly preferredTier: EncodingTier.MSGPACK;
52
+
53
+ constructor(
54
+ public readonly nid: string, // urn:nps:agent:{authority}:{name}
55
+ public readonly pubKey: string, // "ed25519:{hex}"
56
+ public readonly metadata: IdentMetadata,
57
+ public readonly signature: string, // "ed25519:{base64}"
58
+ );
59
+
60
+ unsignedDict(): Record<string, unknown>; // { nid, pub_key, metadata } — signing payload
61
+ toDict(): Record<string, unknown>; // unsignedDict + signature
62
+
63
+ static fromDict(data: Record<string, unknown>): IdentFrame;
64
+ }
65
+ ```
66
+
67
+ `unsignedDict()` is the canonical signing payload — it omits the
68
+ `signature` field. Pair it with `NipIdentity.sign()` to produce the
69
+ self-signed `signature`.
70
+
71
+ ---
72
+
73
+ ## `TrustFrame` (0x21)
74
+
75
+ Inter-CA trust certificate.
76
+
77
+ ```typescript
78
+ class TrustFrame {
79
+ readonly frameType: FrameType.TRUST;
80
+ readonly preferredTier: EncodingTier.MSGPACK;
81
+
82
+ constructor(
83
+ public readonly issuerNid: string,
84
+ public readonly subjectNid: string,
85
+ public readonly scopes: readonly string[],
86
+ public readonly expiresAt: string, // ISO 8601 UTC
87
+ public readonly signature: string, // "ed25519:{base64}"
88
+ );
89
+
90
+ toDict(): Record<string, unknown>;
91
+ static fromDict(data: Record<string, unknown>): TrustFrame;
92
+ }
93
+ ```
94
+
95
+ ---
96
+
97
+ ## `RevokeFrame` (0x22)
98
+
99
+ Certificate revocation.
100
+
101
+ ```typescript
102
+ class RevokeFrame {
103
+ readonly frameType: FrameType.REVOKE;
104
+ readonly preferredTier: EncodingTier.MSGPACK;
105
+
106
+ constructor(
107
+ public readonly nid: string,
108
+ public readonly reason?: string, // e.g. "key_compromise"
109
+ public readonly revokedAt?: string, // ISO 8601 UTC
110
+ );
111
+
112
+ toDict(): Record<string, unknown>;
113
+ static fromDict(data: Record<string, unknown>): RevokeFrame;
114
+ }
115
+ ```
116
+
117
+ Signed by the issuing CA. Verifiers MUST refuse any `IdentFrame` whose
118
+ `nid` is covered by a valid `RevokeFrame`.
119
+
120
+ ---
121
+
122
+ ## `NipIdentity`
123
+
124
+ Ed25519 keypair manager with optional encrypted-keyfile persistence.
125
+ Built on `@noble/ed25519` + `node:crypto`.
126
+
127
+ ```typescript
128
+ class NipIdentity {
129
+ // Factory
130
+ static generate(): NipIdentity;
131
+ static fromPrivateKey(privKey: Uint8Array): NipIdentity;
132
+ static load(path: string, passphrase: string): NipIdentity;
133
+
134
+ // Persist
135
+ save(path: string, passphrase: string): void;
136
+
137
+ // Signing
138
+ sign(payload: Record<string, unknown>): string; // "ed25519:{base64}"
139
+ verify(payload: Record<string, unknown>, signature: string): boolean;
140
+
141
+ // Public key access
142
+ readonly pubKey: Uint8Array; // 32 bytes
143
+ readonly pubKeyString: string; // "ed25519:{hex}"
144
+ }
145
+ ```
146
+
147
+ ### Key-file format
148
+
149
+ `save` / `load` write a JSON envelope (versioned) containing:
150
+
151
+ ```
152
+ {
153
+ version: 1,
154
+ salt: hex(16 B),
155
+ iv: hex(12 B),
156
+ ciphertext: hex( AES-GCM(privateKey) || authTag(16 B) ),
157
+ pubKey: hex(32 B)
158
+ }
159
+ ```
160
+
161
+ Key derivation: **PBKDF2-SHA256**, 600 000 iterations.
162
+ Cipher: **AES-256-GCM** — the 16-byte auth tag is appended to the
163
+ ciphertext inside the `ciphertext` field.
164
+
165
+ ### `generate()`
166
+
167
+ Produces a fresh Ed25519 keypair. Does not touch disk.
168
+
169
+ ### `fromPrivateKey(priv)`
170
+
171
+ Wraps an existing 32-byte Ed25519 private key (derives the matching
172
+ public key).
173
+
174
+ ### `load(path, passphrase)`
175
+
176
+ Reads & decrypts a previously saved keyfile. Throws if the JSON envelope
177
+ is malformed, if the auth tag is invalid, or if the passphrase is wrong.
178
+
179
+ ### `save(path, passphrase)`
180
+
181
+ Encrypts and writes the keypair to `path`. The file is overwritten if it
182
+ exists — back up first.
183
+
184
+ ### `sign(payload)` / `verify(payload, signature)`
185
+
186
+ Canonicalises `payload` (sorted keys, compact separators), runs Ed25519,
187
+ and emits `"ed25519:{base64}"`. `verify` returns `false` on any failure —
188
+ it never throws.
189
+
190
+ ---
191
+
192
+ ## Canonical JSON & signing format
193
+
194
+ The SDK normalises signing payloads with:
195
+
196
+ ```js
197
+ JSON.stringify(payload, Object.keys(payload).sort());
198
+ ```
199
+
200
+ - Keys are sorted lexicographically at every level.
201
+ - `undefined` keys are dropped implicitly by `JSON.stringify`.
202
+ - No whitespace between tokens.
203
+ - Output UTF-8 bytes feed the Ed25519 primitive.
204
+
205
+ For `IdentFrame`, use `unsignedDict()` as the payload — it already omits
206
+ `signature`.
207
+
208
+ ---
209
+
210
+ ## End-to-end example
211
+
212
+ ```typescript
213
+ import {
214
+ IdentFrame, IdentMetadata, NipIdentity,
215
+ } from "@labacacia/nps-sdk/nip";
216
+
217
+ // 1) One-off: create a keypair and persist it
218
+ const id = NipIdentity.generate();
219
+ id.save("./agent.key", "correct horse battery");
220
+
221
+ // 2) Build & sign an IdentFrame
222
+ const meta: IdentMetadata = {
223
+ issuer: "urn:nps:ca:example.com:root",
224
+ issuedAt: new Date().toISOString(),
225
+ expiresAt: new Date(Date.now() + 30 * 86400_000).toISOString(),
226
+ capabilities: ["nwp:query", "nop:delegate"],
227
+ };
228
+
229
+ const unsigned = new IdentFrame(
230
+ "urn:nps:agent:example.com:agent-001",
231
+ id.pubKeyString,
232
+ meta,
233
+ "placeholder",
234
+ );
235
+
236
+ const signature = id.sign(unsigned.unsignedDict());
237
+ const signed = new IdentFrame(unsigned.nid, unsigned.pubKey, meta, signature);
238
+
239
+ // 3) Anyone with the same keypair (or equivalent pubKey) can verify
240
+ const ok = id.verify(signed.unsignedDict(), signed.signature);
241
+ // → true
242
+ ```