@peerbit/trusted-network 6.0.102 → 6.0.104

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/src/v2.ts ADDED
@@ -0,0 +1,348 @@
1
+ import {
2
+ deserialize,
3
+ field,
4
+ fixedArray,
5
+ option,
6
+ serialize,
7
+ variant,
8
+ vec,
9
+ } from "@dao-xyz/borsh";
10
+ import { PublicSignKey, sha256Sync } from "@peerbit/crypto";
11
+ import {
12
+ Program,
13
+ type ProgramClient,
14
+ type ProgramInitializationOptions,
15
+ } from "@peerbit/program";
16
+ import { compare, concat, equals } from "uint8arrays";
17
+
18
+ /**
19
+ * Decode-only TrustedNetwork v2 codec scaffold.
20
+ *
21
+ * This module is intentionally absent from the package entry point. It pins
22
+ * the policy wire contract needed by the next slice without exposing a usable
23
+ * controller, policy engine, resource fence, or encryption path.
24
+ */
25
+
26
+ export const TRUSTED_NETWORK_V2_PROTOCOL_VERSION = 2;
27
+ export const TRUSTED_NETWORK_V2_POLICY_HASH_SHA256 = 1;
28
+
29
+ /**
30
+ * Profile 1 accepts only an EntryV0 whose normal toSignable() bytes (including
31
+ * causal metadata) have exactly one successfully verified signature. The
32
+ * signer's canonical public-key bytes must equal the descriptor authority.
33
+ */
34
+ export const TRUSTED_NETWORK_V2_ENTRY_V0_AUTHORITY_ONLY_SIGNATURE_PROFILE = 1;
35
+
36
+ export const TRUSTED_NETWORK_V2_NETWORK_ID_DOMAIN =
37
+ "peerbit/trusted-network/v2/network-id/v1";
38
+ export const TRUSTED_NETWORK_V2_POLICY_DIGEST_DOMAIN =
39
+ "peerbit/trusted-network/v2/policy-body/v1";
40
+
41
+ export const TrustedNetworkRole = Object.freeze({
42
+ ADMIN: 0x01,
43
+ WRITER: 0x02,
44
+ READER: 0x04,
45
+ REPLICATOR: 0x08,
46
+ } as const);
47
+
48
+ export const TRUSTED_NETWORK_V2_KNOWN_ROLE_BITS =
49
+ TrustedNetworkRole.ADMIN |
50
+ TrustedNetworkRole.WRITER |
51
+ TrustedNetworkRole.READER |
52
+ TrustedNetworkRole.REPLICATOR;
53
+
54
+ const ZERO_DIGEST = new Uint8Array(32);
55
+ const textEncoder = new TextEncoder();
56
+
57
+ const assertByte = (value: number, label: string): void => {
58
+ if (!Number.isInteger(value) || value < 0 || value > 0xff) {
59
+ throw new Error(`${label} must be a u8`);
60
+ }
61
+ };
62
+
63
+ const assertU16 = (value: number, label: string): void => {
64
+ if (!Number.isInteger(value) || value < 0 || value > 0xffff) {
65
+ throw new Error(`${label} must be a u16`);
66
+ }
67
+ };
68
+
69
+ const assertU64 = (value: bigint, label: string): void => {
70
+ if (typeof value !== "bigint" || value < 0n || value > 0xffffffffffffffffn) {
71
+ throw new Error(`${label} must be a u64`);
72
+ }
73
+ };
74
+
75
+ const assertBytes32 = (value: Uint8Array, label: string): void => {
76
+ if (!(value instanceof Uint8Array) || value.byteLength !== 32) {
77
+ throw new Error(`${label} must contain exactly 32 bytes`);
78
+ }
79
+ };
80
+
81
+ const lengthPrefix = (value: Uint8Array): Uint8Array => {
82
+ const length = new Uint8Array(4);
83
+ new DataView(length.buffer).setUint32(0, value.byteLength, true);
84
+ return concat([length, value]);
85
+ };
86
+
87
+ const encodeU16 = (value: number): Uint8Array => {
88
+ const bytes = new Uint8Array(2);
89
+ new DataView(bytes.buffer).setUint16(0, value, true);
90
+ return bytes;
91
+ };
92
+
93
+ const assertCanonicalEncoding = (bytes: Uint8Array, value: unknown): void => {
94
+ if (!equals(bytes, serialize(value))) {
95
+ throw new Error("TrustedNetwork v2 encoding is not canonical");
96
+ }
97
+ };
98
+
99
+ @variant([2, 0])
100
+ export class NetworkDescriptorV2 {
101
+ @field({ type: "u16" })
102
+ protocolVersion: number;
103
+
104
+ @field({ type: fixedArray("u8", 32) })
105
+ networkNonce: Uint8Array;
106
+
107
+ @field({ type: PublicSignKey })
108
+ policyAuthority: PublicSignKey;
109
+
110
+ @field({ type: fixedArray("u8", 32) })
111
+ genesisPolicyDigest: Uint8Array;
112
+
113
+ @field({ type: "u8" })
114
+ policyHashProfile: number;
115
+
116
+ @field({ type: "u8" })
117
+ entrySignatureProfile: number;
118
+
119
+ constructor(properties?: {
120
+ protocolVersion: number;
121
+ networkNonce: Uint8Array;
122
+ policyAuthority: PublicSignKey;
123
+ genesisPolicyDigest: Uint8Array;
124
+ policyHashProfile: number;
125
+ entrySignatureProfile: number;
126
+ }) {
127
+ if (properties) Object.assign(this, properties);
128
+ }
129
+ }
130
+
131
+ export class EncryptionKeyCommitmentV2 {
132
+ @field({ type: "u8" })
133
+ profile: number;
134
+
135
+ @field({ type: fixedArray("u8", 32) })
136
+ digest: Uint8Array;
137
+
138
+ constructor(properties?: { profile: number; digest: Uint8Array }) {
139
+ if (properties) Object.assign(this, properties);
140
+ }
141
+ }
142
+
143
+ export class PolicySubjectBindingV2 {
144
+ @field({ type: PublicSignKey })
145
+ signingKey: PublicSignKey;
146
+
147
+ @field({ type: "u8" })
148
+ roles: number;
149
+
150
+ @field({ type: option(EncryptionKeyCommitmentV2) })
151
+ encryptionKeyCommitment?: EncryptionKeyCommitmentV2;
152
+
153
+ constructor(properties?: {
154
+ signingKey: PublicSignKey;
155
+ roles: number;
156
+ encryptionKeyCommitment?: EncryptionKeyCommitmentV2;
157
+ }) {
158
+ if (properties) Object.assign(this, properties);
159
+ }
160
+ }
161
+
162
+ @variant([2, 1])
163
+ export class PolicySnapshotBodyV2 {
164
+ @field({ type: fixedArray("u8", 32) })
165
+ networkId: Uint8Array;
166
+
167
+ @field({ type: "u64" })
168
+ sequence: bigint;
169
+
170
+ @field({ type: fixedArray("u8", 32) })
171
+ previousPolicyDigest: Uint8Array;
172
+
173
+ @field({ type: vec(PolicySubjectBindingV2) })
174
+ bindings: PolicySubjectBindingV2[];
175
+
176
+ constructor(properties?: {
177
+ networkId: Uint8Array;
178
+ sequence: bigint;
179
+ previousPolicyDigest: Uint8Array;
180
+ bindings: PolicySubjectBindingV2[];
181
+ }) {
182
+ if (properties) Object.assign(this, properties);
183
+ }
184
+ }
185
+
186
+ @variant("trusted_network_v2")
187
+ export class TrustedNetworkV2 extends Program<never> {
188
+ @field({ type: NetworkDescriptorV2 })
189
+ descriptor: NetworkDescriptorV2;
190
+
191
+ constructor(properties?: { descriptor: NetworkDescriptorV2 }) {
192
+ super();
193
+ if (properties) this.descriptor = properties.descriptor;
194
+ }
195
+
196
+ override async beforeOpen(
197
+ _node: ProgramClient,
198
+ _options?: ProgramInitializationOptions<never, this>,
199
+ ): Promise<never> {
200
+ throw new Error(
201
+ "TrustedNetwork v2 is a decode-only codec and cannot be activated",
202
+ );
203
+ }
204
+
205
+ async open(_args?: never): Promise<never> {
206
+ throw new Error(
207
+ "TrustedNetwork v2 is a decode-only codec and cannot be activated",
208
+ );
209
+ }
210
+ }
211
+
212
+ export const assertNetworkDescriptorV2 = (
213
+ descriptor: NetworkDescriptorV2,
214
+ ): void => {
215
+ assertU16(descriptor.protocolVersion, "protocolVersion");
216
+ if (descriptor.protocolVersion !== TRUSTED_NETWORK_V2_PROTOCOL_VERSION) {
217
+ throw new Error("Unsupported TrustedNetwork protocol version");
218
+ }
219
+ assertBytes32(descriptor.networkNonce, "networkNonce");
220
+ if (!(descriptor.policyAuthority instanceof PublicSignKey)) {
221
+ throw new Error("policyAuthority must be a public signing key");
222
+ }
223
+ assertBytes32(descriptor.genesisPolicyDigest, "genesisPolicyDigest");
224
+ assertByte(descriptor.policyHashProfile, "policyHashProfile");
225
+ if (descriptor.policyHashProfile !== TRUSTED_NETWORK_V2_POLICY_HASH_SHA256) {
226
+ throw new Error("Unsupported TrustedNetwork policy hash profile");
227
+ }
228
+ assertByte(descriptor.entrySignatureProfile, "entrySignatureProfile");
229
+ if (
230
+ descriptor.entrySignatureProfile !==
231
+ TRUSTED_NETWORK_V2_ENTRY_V0_AUTHORITY_ONLY_SIGNATURE_PROFILE
232
+ ) {
233
+ throw new Error("Unsupported TrustedNetwork entry signature profile");
234
+ }
235
+ };
236
+
237
+ export const deriveNetworkIdV2 = (
238
+ descriptor: NetworkDescriptorV2,
239
+ ): Uint8Array => {
240
+ assertNetworkDescriptorV2(descriptor);
241
+ return sha256Sync(
242
+ concat([
243
+ lengthPrefix(textEncoder.encode(TRUSTED_NETWORK_V2_NETWORK_ID_DOMAIN)),
244
+ encodeU16(descriptor.protocolVersion),
245
+ descriptor.networkNonce,
246
+ lengthPrefix(serialize(descriptor.policyAuthority)),
247
+ ]),
248
+ );
249
+ };
250
+
251
+ export const digestPolicySnapshotBodyV2 = (
252
+ body: PolicySnapshotBodyV2,
253
+ ): Uint8Array => {
254
+ const bytes = serialize(body);
255
+ return sha256Sync(
256
+ concat([
257
+ lengthPrefix(textEncoder.encode(TRUSTED_NETWORK_V2_POLICY_DIGEST_DOMAIN)),
258
+ lengthPrefix(bytes),
259
+ ]),
260
+ );
261
+ };
262
+
263
+ export const assertPolicySnapshotBodyV2 = (
264
+ body: PolicySnapshotBodyV2,
265
+ descriptor: NetworkDescriptorV2,
266
+ ): void => {
267
+ assertNetworkDescriptorV2(descriptor);
268
+ assertBytes32(body.networkId, "networkId");
269
+ if (!equals(body.networkId, deriveNetworkIdV2(descriptor))) {
270
+ throw new Error("Policy snapshot belongs to another network");
271
+ }
272
+ assertU64(body.sequence, "sequence");
273
+ assertBytes32(body.previousPolicyDigest, "previousPolicyDigest");
274
+ if (body.sequence === 0n) {
275
+ if (!equals(body.previousPolicyDigest, ZERO_DIGEST)) {
276
+ throw new Error("Genesis policy must use the zero previous digest");
277
+ }
278
+ } else if (equals(body.previousPolicyDigest, ZERO_DIGEST)) {
279
+ throw new Error("A non-genesis policy must name its previous digest");
280
+ }
281
+ if (!Array.isArray(body.bindings) || body.bindings.length === 0) {
282
+ throw new Error("Policy snapshot must contain bindings");
283
+ }
284
+
285
+ let previousKeyBytes: Uint8Array | undefined;
286
+ let authorityBinding: PolicySubjectBindingV2 | undefined;
287
+ for (const binding of body.bindings) {
288
+ if (!(binding.signingKey instanceof PublicSignKey)) {
289
+ throw new Error("Policy subject must be a public signing key");
290
+ }
291
+ const keyBytes = serialize(binding.signingKey);
292
+ if (
293
+ previousKeyBytes !== undefined &&
294
+ compare(previousKeyBytes, keyBytes) >= 0
295
+ ) {
296
+ throw new Error("Policy bindings must be sorted and unique");
297
+ }
298
+ previousKeyBytes = keyBytes;
299
+
300
+ assertByte(binding.roles, "roles");
301
+ if (binding.roles === 0) {
302
+ throw new Error("Policy bindings with no roles are not canonical");
303
+ }
304
+ if ((binding.roles & ~TRUSTED_NETWORK_V2_KNOWN_ROLE_BITS) !== 0) {
305
+ throw new Error("Policy binding contains unknown role bits");
306
+ }
307
+ if (binding.encryptionKeyCommitment !== undefined) {
308
+ throw new Error(
309
+ "TrustedNetwork v2 encryption commitments are not supported yet",
310
+ );
311
+ }
312
+
313
+ if (binding.signingKey.equals(descriptor.policyAuthority)) {
314
+ authorityBinding = binding;
315
+ } else if ((binding.roles & TrustedNetworkRole.ADMIN) !== 0) {
316
+ throw new Error("Only the policy authority may hold ADMIN");
317
+ }
318
+ }
319
+ if (
320
+ authorityBinding === undefined ||
321
+ (authorityBinding.roles & TrustedNetworkRole.ADMIN) === 0
322
+ ) {
323
+ throw new Error("Policy authority must hold ADMIN");
324
+ }
325
+ if (
326
+ body.sequence === 0n &&
327
+ !equals(digestPolicySnapshotBodyV2(body), descriptor.genesisPolicyDigest)
328
+ ) {
329
+ throw new Error("Genesis policy digest does not match the descriptor");
330
+ }
331
+ };
332
+
333
+ export const decodeTrustedNetworkV2 = (bytes: Uint8Array): TrustedNetworkV2 => {
334
+ const network = deserialize(bytes, TrustedNetworkV2);
335
+ assertCanonicalEncoding(bytes, network);
336
+ assertNetworkDescriptorV2(network.descriptor);
337
+ return network;
338
+ };
339
+
340
+ export const decodePolicySnapshotBodyV2 = (
341
+ bytes: Uint8Array,
342
+ descriptor: NetworkDescriptorV2,
343
+ ): PolicySnapshotBodyV2 => {
344
+ const body = deserialize(bytes, PolicySnapshotBodyV2);
345
+ assertCanonicalEncoding(bytes, body);
346
+ assertPolicySnapshotBodyV2(body, descriptor);
347
+ return body;
348
+ };