@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.
- package/.npmrc.publish +1 -0
- package/CHANGELOG.cn.md +39 -0
- package/CHANGELOG.md +39 -0
- package/CONTRIBUTING.cn.md +35 -0
- package/CONTRIBUTING.md +2 -0
- package/README.cn.md +155 -0
- package/README.md +5 -3
- package/dist/core/frames.d.ts +1 -0
- package/dist/core/frames.d.ts.map +1 -1
- package/dist/core/frames.js +1 -0
- package/dist/core/frames.js.map +1 -1
- package/dist/core/index.d.ts +6 -4
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +17 -5
- package/dist/core/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/ncp/frames.d.ts +18 -0
- package/dist/ncp/frames.d.ts.map +1 -1
- package/dist/ncp/frames.js +45 -0
- package/dist/ncp/frames.js.map +1 -1
- package/dist/ncp/registry.d.ts.map +1 -1
- package/dist/ncp/registry.js +2 -1
- package/dist/ncp/registry.js.map +1 -1
- package/doc/nps-sdk.core.cn.md +321 -0
- package/doc/nps-sdk.core.md +326 -0
- package/doc/nps-sdk.ncp.cn.md +270 -0
- package/doc/nps-sdk.ncp.md +276 -0
- package/doc/nps-sdk.ndp.cn.md +267 -0
- package/doc/nps-sdk.ndp.md +273 -0
- package/doc/nps-sdk.nip.cn.md +235 -0
- package/doc/nps-sdk.nip.md +242 -0
- package/doc/nps-sdk.nop.cn.md +329 -0
- package/doc/nps-sdk.nop.md +332 -0
- package/doc/nps-sdk.nwp.cn.md +217 -0
- package/doc/nps-sdk.nwp.md +224 -0
- package/doc/overview.cn.md +149 -0
- package/doc/overview.md +153 -0
- package/package.json +21 -4
- package/src/core/frames.ts +1 -0
- package/src/core/index.ts +37 -5
- package/src/index.ts +1 -1
- package/src/ncp/frames.ts +52 -0
- 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
|
+
```
|