@serfab/cadre-core 1.16.0 → 1.17.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 +1 -1
- package/dist/bootstrap-peer-store-file.js +1 -1
- package/dist/bootstrap-peer-store.d.ts +1 -1
- package/dist/bootstrap-peer-store.js +4 -4
- package/dist/cadre-invite-protocol.d.ts +55 -138
- package/dist/cadre-invite-protocol.js +94 -449
- package/dist/cadre-invite-protocol.js.map +1 -1
- package/dist/cadre-join-request.d.ts +15 -2
- package/dist/cadre-join-request.js +22 -3
- package/dist/cadre-join-request.js.map +1 -1
- package/dist/cadre-node.d.ts +530 -247
- package/dist/cadre-node.js +1046 -525
- package/dist/cadre-node.js.map +1 -1
- package/dist/control-authorization.d.ts +36 -14
- package/dist/control-authorization.js +20 -3
- package/dist/control-authorization.js.map +1 -1
- package/dist/control-cohort.d.ts +22 -23
- package/dist/control-cohort.js +24 -24
- package/dist/control-cohort.js.map +1 -1
- package/dist/control-database.d.ts +416 -136
- package/dist/control-database.js +757 -378
- package/dist/control-database.js.map +1 -1
- package/dist/control-protocol-guard.d.ts +5 -4
- package/dist/control-protocol-guard.js +6 -3
- package/dist/control-protocol-guard.js.map +1 -1
- package/dist/control-schema-version.d.ts +80 -0
- package/dist/control-schema-version.js +203 -0
- package/dist/control-schema-version.js.map +1 -0
- package/dist/control-schema.d.ts +45 -5
- package/dist/control-schema.js +254 -70
- package/dist/control-schema.js.map +1 -1
- package/dist/control-write-retry.d.ts +5 -5
- package/dist/control-write-retry.js +10 -8
- package/dist/control-write-retry.js.map +1 -1
- package/dist/delegate-admission.d.ts +13 -0
- package/dist/delegate-admission.js +27 -0
- package/dist/delegate-admission.js.map +1 -1
- package/dist/ed25519-key.d.ts +10 -7
- package/dist/ed25519-key.js +23 -20
- package/dist/ed25519-key.js.map +1 -1
- package/dist/edge-node/config.d.ts +122 -0
- package/dist/edge-node/config.js +199 -0
- package/dist/edge-node/config.js.map +1 -0
- package/dist/edge-node/dissolve.d.ts +95 -0
- package/dist/edge-node/dissolve.js +184 -0
- package/dist/edge-node/dissolve.js.map +1 -0
- package/dist/edge-node/edge-join.d.ts +44 -0
- package/dist/edge-node/edge-join.js +227 -0
- package/dist/edge-node/edge-join.js.map +1 -0
- package/dist/edge-node/edge-node.d.ts +295 -0
- package/dist/edge-node/edge-node.js +704 -0
- package/dist/edge-node/edge-node.js.map +1 -0
- package/dist/edge-node/index.d.ts +18 -0
- package/dist/edge-node/index.js +18 -0
- package/dist/edge-node/index.js.map +1 -0
- package/dist/edge-node/join-credential.d.ts +11 -0
- package/dist/edge-node/join-credential.js +11 -0
- package/dist/edge-node/join-credential.js.map +1 -0
- package/dist/edge-node/lazy-raw-storage.d.ts +40 -0
- package/dist/edge-node/lazy-raw-storage.js +134 -0
- package/dist/edge-node/lazy-raw-storage.js.map +1 -0
- package/dist/edge-node/node-local.d.ts +60 -0
- package/dist/edge-node/node-local.js +58 -0
- package/dist/edge-node/node-local.js.map +1 -0
- package/dist/edge-node/options.d.ts +83 -0
- package/dist/edge-node/options.js +110 -0
- package/dist/edge-node/options.js.map +1 -0
- package/dist/edge-node/owner-grant.d.ts +41 -0
- package/dist/edge-node/owner-grant.js +38 -0
- package/dist/edge-node/owner-grant.js.map +1 -0
- package/dist/edge-node/platform.d.ts +140 -0
- package/dist/edge-node/platform.js +12 -0
- package/dist/edge-node/platform.js.map +1 -0
- package/dist/edge-node/strand-move.d.ts +115 -0
- package/dist/edge-node/strand-move.js +223 -0
- package/dist/edge-node/strand-move.js.map +1 -0
- package/dist/edge-node/strands.d.ts +28 -0
- package/dist/edge-node/strands.js +60 -0
- package/dist/edge-node/strands.js.map +1 -0
- package/dist/index.d.ts +16 -7
- package/dist/index.js +26 -7
- package/dist/index.js.map +1 -1
- package/dist/joined-strand-store.d.ts +3 -3
- package/dist/joined-strand-store.js +3 -3
- package/dist/joined-strand-store.js.map +1 -1
- package/dist/key-store.d.ts +9 -4
- package/dist/key-store.js +9 -4
- package/dist/key-store.js.map +1 -1
- package/dist/machine-removal.d.ts +19 -39
- package/dist/machine-removal.js +24 -98
- package/dist/machine-removal.js.map +1 -1
- package/dist/member-redemption.d.ts +250 -0
- package/dist/member-redemption.js +485 -0
- package/dist/member-redemption.js.map +1 -0
- package/dist/owner-anchor-sync.d.ts +4 -4
- package/dist/owner-anchor-sync.js +16 -15
- package/dist/owner-anchor-sync.js.map +1 -1
- package/dist/owner-grant-protocol.d.ts +247 -0
- package/dist/owner-grant-protocol.js +330 -0
- package/dist/owner-grant-protocol.js.map +1 -0
- package/dist/owner-key-export.d.ts +70 -0
- package/dist/owner-key-export.js +279 -0
- package/dist/owner-key-export.js.map +1 -0
- package/dist/owner-key-flows.d.ts +55 -0
- package/dist/owner-key-flows.js +60 -0
- package/dist/owner-key-flows.js.map +1 -0
- package/dist/owner-key-store.d.ts +31 -0
- package/dist/owner-key-store.js +81 -0
- package/dist/owner-key-store.js.map +1 -0
- package/dist/owner-keys.d.ts +78 -0
- package/dist/owner-keys.js +133 -0
- package/dist/owner-keys.js.map +1 -0
- package/dist/owner-signer.d.ts +54 -0
- package/dist/owner-signer.js +51 -0
- package/dist/owner-signer.js.map +1 -0
- package/dist/peer-authorization.d.ts +83 -18
- package/dist/peer-authorization.js +123 -53
- package/dist/peer-authorization.js.map +1 -1
- package/dist/provider/rent-node.js +1 -1
- package/dist/provider/rent-node.js.map +1 -1
- package/dist/seed-bootstrap.d.ts +131 -109
- package/dist/seed-bootstrap.js +260 -248
- package/dist/seed-bootstrap.js.map +1 -1
- package/dist/seed-trust-policy.js +4 -4
- package/dist/strand-addr-protocol.d.ts +4 -1
- package/dist/strand-addr-protocol.js +8 -2
- package/dist/strand-addr-protocol.js.map +1 -1
- package/dist/strand-cohort.d.ts +3 -0
- package/dist/strand-cohort.js.map +1 -1
- package/dist/strand-database.d.ts +1 -0
- package/dist/strand-database.js +3 -0
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-first-sync-gate.d.ts +4 -3
- package/dist/strand-first-sync-gate.js +4 -3
- package/dist/strand-first-sync-gate.js.map +1 -1
- package/dist/strand-instance-manager.d.ts +1 -1
- package/dist/strand-membership-writer.d.ts +13 -2
- package/dist/strand-membership-writer.js +52 -16
- package/dist/strand-membership-writer.js.map +1 -1
- package/dist/strand-move.d.ts +6 -5
- package/dist/strand-move.js +31 -30
- package/dist/strand-move.js.map +1 -1
- package/dist/strand-peer-redial.js +2 -3
- package/dist/strand-peer-redial.js.map +1 -1
- package/dist/types.d.ts +286 -103
- package/dist/types.js.map +1 -1
- package/package.json +11 -2
- package/src/bootstrap-peer-store-file.ts +44 -44
- package/src/bootstrap-peer-store.ts +214 -214
- package/src/cadre-invite-protocol.ts +652 -1052
- package/src/cadre-join-request.ts +24 -3
- package/src/cadre-node.ts +1166 -541
- package/src/control-authorization.ts +47 -14
- package/src/control-cohort.ts +127 -128
- package/src/control-database.ts +940 -456
- package/src/control-protocol-guard.ts +7 -4
- package/src/control-schema-version.ts +251 -0
- package/src/control-schema.ts +265 -70
- package/src/control-write-retry.ts +569 -567
- package/src/delegate-admission.ts +37 -0
- package/src/ed25519-key.ts +174 -171
- package/src/edge-node/config.ts +277 -0
- package/src/edge-node/dissolve.ts +251 -0
- package/src/edge-node/edge-join.ts +319 -0
- package/src/edge-node/edge-node.ts +1045 -0
- package/src/edge-node/index.ts +59 -0
- package/src/edge-node/join-credential.ts +16 -0
- package/src/edge-node/lazy-raw-storage.ts +178 -0
- package/src/edge-node/node-local.ts +84 -0
- package/src/edge-node/options.ts +179 -0
- package/src/edge-node/owner-grant.ts +63 -0
- package/src/edge-node/platform.ts +151 -0
- package/src/edge-node/strand-move.ts +270 -0
- package/src/edge-node/strands.ts +72 -0
- package/src/index.ts +886 -789
- package/src/joined-strand-store.ts +4 -4
- package/src/key-store.ts +10 -4
- package/src/machine-removal.ts +26 -111
- package/src/member-redemption.ts +642 -0
- package/src/owner-anchor-sync.ts +19 -18
- package/src/owner-grant-protocol.ts +532 -0
- package/src/owner-key-export.ts +348 -0
- package/src/owner-key-flows.ts +109 -0
- package/src/owner-key-store.ts +85 -0
- package/src/owner-keys.ts +178 -0
- package/src/owner-signer.ts +94 -0
- package/src/peer-authorization.ts +558 -488
- package/src/provider/rent-node.ts +1 -1
- package/src/seed-bootstrap.ts +301 -312
- package/src/seed-trust-policy.ts +4 -4
- package/src/strand-addr-protocol.ts +547 -538
- package/src/strand-cohort.ts +4 -0
- package/src/strand-database.ts +3 -0
- package/src/strand-first-sync-gate.ts +4 -3
- package/src/strand-instance-manager.ts +1 -1
- package/src/strand-membership-writer.ts +61 -16
- package/src/strand-move.ts +45 -36
- package/src/strand-peer-redial.ts +2 -3
- package/src/types.ts +304 -103
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
import debug from 'debug';
|
|
43
43
|
import { multiaddr } from '@multiformats/multiaddr';
|
|
44
44
|
import { peerIdFromString } from '@libp2p/peer-id';
|
|
45
|
+
import type { StrandAddrOutcome } from './strand-addr-protocol.js';
|
|
45
46
|
|
|
46
47
|
const log = debug('sereus:cadre:delegate-admission');
|
|
47
48
|
|
|
@@ -238,6 +239,42 @@ export function dueRelayAnnounces(
|
|
|
238
239
|
);
|
|
239
240
|
}
|
|
240
241
|
|
|
242
|
+
/**
|
|
243
|
+
* Fold one announce pass's outcomes into `refusing`, the relay peerIds remembered
|
|
244
|
+
* as refusing the strand-addr protocol. A relay whose negotiation refused it
|
|
245
|
+
* (`unsupported`) is added; one that replied with any status is removed, because
|
|
246
|
+
* it serves the protocol now. A relay that did not reply (`unreachable`) is left
|
|
247
|
+
* as it was: being down says nothing about what it serves. Only `relayPeerIds`
|
|
248
|
+
* are read, so a sibling asked in the same pass is never recorded. Mutates in
|
|
249
|
+
* place.
|
|
250
|
+
*/
|
|
251
|
+
export function recordRelayRefusals(
|
|
252
|
+
refusing: Set<string>,
|
|
253
|
+
relayPeerIds: readonly string[],
|
|
254
|
+
outcomes: ReadonlyMap<string, StrandAddrOutcome>
|
|
255
|
+
): void {
|
|
256
|
+
for (const relayPeerId of relayPeerIds) {
|
|
257
|
+
switch (outcomes.get(relayPeerId)) {
|
|
258
|
+
case 'unsupported':
|
|
259
|
+
refusing.add(relayPeerId);
|
|
260
|
+
break;
|
|
261
|
+
case 'answered':
|
|
262
|
+
case 'empty':
|
|
263
|
+
case 'unavailable':
|
|
264
|
+
refusing.delete(relayPeerId);
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** Of `relays`, the ones not remembered in `refusing` (see {@link recordRelayRefusals}). */
|
|
271
|
+
export function relaysNotRefusing(
|
|
272
|
+
relays: readonly CircuitRelayTarget[],
|
|
273
|
+
refusing: ReadonlySet<string>
|
|
274
|
+
): CircuitRelayTarget[] {
|
|
275
|
+
return relays.filter((relay) => !refusing.has(relay.relayPeerId));
|
|
276
|
+
}
|
|
277
|
+
|
|
241
278
|
/**
|
|
242
279
|
* Drop the entries of a {@link peerStrandKey}-keyed map whose strand is no longer
|
|
243
280
|
* running, and — when `livePeerIds` is given — whose peer is not among them. A
|
package/src/ed25519-key.ts
CHANGED
|
@@ -1,171 +1,174 @@
|
|
|
1
|
-
import { toString as uint8ArrayToString, fromString as uint8ArrayFromString } from 'uint8arrays';
|
|
2
|
-
import { getPublicKey } from '@optimystic/quereus-plugin-crypto';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* `@
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* @
|
|
33
|
-
* @
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* (
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* @
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
*
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* the
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}
|
|
1
|
+
import { toString as uint8ArrayToString, fromString as uint8ArrayFromString } from 'uint8arrays';
|
|
2
|
+
import { getPublicKey } from '@optimystic/quereus-plugin-crypto';
|
|
3
|
+
import { peerIdFromString } from '@libp2p/peer-id';
|
|
4
|
+
import type { PrivateKey } from '@libp2p/interface';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* An Ed25519 keypair expressed in the base64url form that
|
|
8
|
+
* `@optimystic/quereus-plugin-crypto` (`sign`/`verify`/`getPublicKey`) consumes.
|
|
9
|
+
* Generic bridge type — reused for owner keys, member keys, and any other
|
|
10
|
+
* seed→public keypair the control/strand databases sign with.
|
|
11
|
+
*/
|
|
12
|
+
export interface Ed25519KeyPair {
|
|
13
|
+
/** 32-byte Ed25519 seed, base64url-encoded — the crypto-plugin private key. */
|
|
14
|
+
privateKeyB64: string;
|
|
15
|
+
/** 32-byte Ed25519 public key, base64url-encoded. */
|
|
16
|
+
publicKeyB64: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Bridge a libp2p Ed25519 private key into the base64url keypair used by the
|
|
21
|
+
* control- and strand-database signing constraints.
|
|
22
|
+
*
|
|
23
|
+
* libp2p stores an Ed25519 private key as 64 raw bytes: the first 32 are the
|
|
24
|
+
* seed (the actual scalar source), the last 32 are the public key — see
|
|
25
|
+
* `@libp2p/crypto`'s `Ed25519PrivateKey`. `@optimystic/quereus-plugin-crypto`
|
|
26
|
+
* (via `@noble/curves`) treats the 32-byte seed *as* the private key and
|
|
27
|
+
* derives the public key from it with standard Ed25519. The two derivations
|
|
28
|
+
* agree, so the node's peer identity and its signing key are one keypair:
|
|
29
|
+
* `getPublicKey(privateKeyB64)` === `publicKeyB64`.
|
|
30
|
+
*
|
|
31
|
+
* @param privateKey - The node's libp2p Ed25519 private key.
|
|
32
|
+
* @returns The base64url seed/public-key pair for signing operations.
|
|
33
|
+
* @throws If the key is not Ed25519 or the raw bytes aren't the expected length.
|
|
34
|
+
*/
|
|
35
|
+
export function ed25519KeyPairFromLibp2p(privateKey: PrivateKey): Ed25519KeyPair {
|
|
36
|
+
if (privateKey.type !== 'Ed25519') {
|
|
37
|
+
throw new Error(`ed25519KeyPairFromLibp2p requires an Ed25519 key, got ${privateKey.type}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const raw = privateKey.raw;
|
|
41
|
+
if (raw.length !== 64) {
|
|
42
|
+
throw new Error(`Expected a 64-byte Ed25519 raw private key (seed||public), got ${raw.length} bytes`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const seed = raw.subarray(0, 32);
|
|
46
|
+
const publicKeyRaw = privateKey.publicKey.raw;
|
|
47
|
+
if (publicKeyRaw.length !== 32) {
|
|
48
|
+
throw new Error(`Expected a 32-byte Ed25519 public key, got ${publicKeyRaw.length} bytes`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
privateKeyB64: uint8ArrayToString(seed, 'base64url'),
|
|
53
|
+
publicKeyB64: uint8ArrayToString(publicKeyRaw, 'base64url'),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Derive the base64url Ed25519 public key from a base64url 32-byte private seed
|
|
59
|
+
* — the same derivation the seed-bootstrap signer uses internally
|
|
60
|
+
* (`SeedBootstrapService` constructor). Use this to enroll a standalone
|
|
61
|
+
* (non-libp2p) key into the control DB before minting an invite, when the
|
|
62
|
+
* key is *not* the node's peer identity (so `ed25519KeyPairFromLibp2p`,
|
|
63
|
+
* which needs a libp2p key object, does not apply).
|
|
64
|
+
*
|
|
65
|
+
* @param privateKeyB64 - The base64url-encoded 32-byte Ed25519 seed.
|
|
66
|
+
* @returns The base64url-encoded Ed25519 public key.
|
|
67
|
+
*/
|
|
68
|
+
export function ed25519PublicKeyFromPrivate(privateKeyB64: string): string {
|
|
69
|
+
return getPublicKey(privateKeyB64, 'ed25519', 'base64url', 'base64url') as string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Reject a value that isn't a base64url-encoded 32-byte Ed25519 seed, the private-key form
|
|
74
|
+
* {@link ed25519PublicKeyFromPrivate} and the crypto plugin's `sign` take. Unlike
|
|
75
|
+
* {@link requireEd25519PublicKeyB64}, no message names the value: it is a private key.
|
|
76
|
+
*
|
|
77
|
+
* @param value - Candidate base64url-encoded Ed25519 seed; surrounding whitespace is ignored.
|
|
78
|
+
* @param label - Human-readable name of the field, used in error messages.
|
|
79
|
+
* @returns The seed re-encoded as canonical base64url, so two spellings of one key compare equal.
|
|
80
|
+
*/
|
|
81
|
+
export function requireEd25519PrivateKeyB64(value: string, label: string): string {
|
|
82
|
+
let decoded: Uint8Array;
|
|
83
|
+
try {
|
|
84
|
+
decoded = uint8ArrayFromString(value.trim(), 'base64url');
|
|
85
|
+
} catch (error) {
|
|
86
|
+
throw new Error(`The ${label} must be a base64url-encoded Ed25519 private key (it does not decode as base64url)`, { cause: error });
|
|
87
|
+
}
|
|
88
|
+
if (decoded.length !== 32) {
|
|
89
|
+
throw new Error(`The ${label} must be a base64url-encoded 32-byte Ed25519 private key (it decoded to ${decoded.length} bytes)`);
|
|
90
|
+
}
|
|
91
|
+
return uint8ArrayToString(decoded, 'base64url');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Derive the base64url ed25519 public key embedded in an Ed25519 libp2p PeerId.
|
|
96
|
+
*
|
|
97
|
+
* An Ed25519 PeerId is an identity multihash of the public key, so
|
|
98
|
+
* `peerIdFromString(id).publicKey.raw` is the 32-byte ed25519 key whose
|
|
99
|
+
* base64url form matches the `OwnerKey.Key` representation (and
|
|
100
|
+
* `ed25519KeyPairFromLibp2p().publicKeyB64`). Returns null for a non-Ed25519
|
|
101
|
+
* id, a missing embedded key, or any parse failure — callers treat null as
|
|
102
|
+
* "not an owner" rather than throwing.
|
|
103
|
+
*
|
|
104
|
+
* Lives here, beside the other key bridges, so the lightweight verifiers in
|
|
105
|
+
* `peer-authorization.ts` can check a stored `PeerKey` against a row's `PeerId`
|
|
106
|
+
* without importing the seed-bootstrap runtime.
|
|
107
|
+
*/
|
|
108
|
+
export function ed25519PublicKeyB64FromPeerId(peerId: string): string | null {
|
|
109
|
+
try {
|
|
110
|
+
const parsed = peerIdFromString(peerId);
|
|
111
|
+
if (parsed.type !== 'Ed25519' || !parsed.publicKey) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
return uint8ArrayToString(parsed.publicKey.raw, 'base64url');
|
|
115
|
+
} catch {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Longest rejected key value echoed back in an error — a real key is 43 base64url chars. */
|
|
121
|
+
const REJECTED_VALUE_ECHO_LIMIT = 64;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Render a rejected value for an error message, capped: owner keys now reach this check
|
|
125
|
+
* from remote-supplied fields (a cadre invitation's `ownerKeys`, a donation request's
|
|
126
|
+
* `ownerKeys`), so an unbounded echo would let a peer turn one junk string into a
|
|
127
|
+
* megabyte of log line.
|
|
128
|
+
*/
|
|
129
|
+
function describeRejected(value: string): string {
|
|
130
|
+
return value.length <= REJECTED_VALUE_ECHO_LIMIT
|
|
131
|
+
? value
|
|
132
|
+
: `${value.slice(0, REJECTED_VALUE_ECHO_LIMIT)}… (${value.length} chars)`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Reject a value that isn't shaped like a base64url-encoded 32-byte Ed25519 public key
|
|
137
|
+
* before it reaches the control database, where a malformed key would otherwise sit
|
|
138
|
+
* indistinguishable from a real one until some later, unrelated signature check fails.
|
|
139
|
+
*
|
|
140
|
+
* Trims first, reusing the blank-value message for an empty result so that case keeps its
|
|
141
|
+
* existing wording instead of a confusing base64url error. Every rejection names the offending
|
|
142
|
+
* value (capped, see {@link describeRejected}) — with several keys in one batch, the message is
|
|
143
|
+
* the only thing that says WHICH one. Does not check the decoded bytes
|
|
144
|
+
* are a valid point on the Ed25519 curve — a well-formed-but-off-curve key still fails
|
|
145
|
+
* signature verification later exactly like any other wrong key.
|
|
146
|
+
*
|
|
147
|
+
* @param value - Candidate base64url-encoded Ed25519 public key.
|
|
148
|
+
* @param label - Human-readable name of the field, used in error messages.
|
|
149
|
+
* @returns The trimmed value, so callers write the same bytes they validated.
|
|
150
|
+
*/
|
|
151
|
+
export function requireEd25519PublicKeyB64(value: string, label: string): string {
|
|
152
|
+
const trimmed = value.trim();
|
|
153
|
+
if (trimmed.length === 0) {
|
|
154
|
+
throw new Error(`A ${label} is required (received an empty or whitespace-only value)`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let decoded: Uint8Array;
|
|
158
|
+
try {
|
|
159
|
+
decoded = uint8ArrayFromString(trimmed, 'base64url');
|
|
160
|
+
} catch (error) {
|
|
161
|
+
throw new Error(
|
|
162
|
+
`A ${label} must be a base64url-encoded Ed25519 public key (could not decode "${describeRejected(trimmed)}" as base64url)`,
|
|
163
|
+
{ cause: error },
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (decoded.length !== 32) {
|
|
168
|
+
throw new Error(
|
|
169
|
+
`A ${label} must be a base64url-encoded 32-byte Ed25519 public key ("${describeRejected(trimmed)}" decoded to ${decoded.length} bytes)`,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return trimmed;
|
|
174
|
+
}
|