@serfab/cadre-core 1.16.0 → 1.18.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 +4 -3
- 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 +81 -142
- package/dist/cadre-invite-protocol.js +168 -458
- 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 +1089 -475
- package/dist/cadre-node.js +2170 -1037
- 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 +88 -32
- package/dist/control-cohort.js +91 -33
- package/dist/control-cohort.js.map +1 -1
- package/dist/control-database.d.ts +856 -318
- package/dist/control-database.js +1422 -701
- package/dist/control-database.js.map +1 -1
- package/dist/control-formation-recorder.d.ts +46 -43
- package/dist/control-formation-recorder.js +77 -78
- package/dist/control-formation-recorder.js.map +1 -1
- package/dist/control-protocol-guard.d.ts +11 -4
- package/dist/control-protocol-guard.js +20 -5
- package/dist/control-protocol-guard.js.map +1 -1
- package/dist/control-schema-version.d.ts +97 -0
- package/dist/control-schema-version.js +225 -0
- package/dist/control-schema-version.js.map +1 -0
- package/dist/control-schema.d.ts +45 -5
- package/dist/control-schema.js +262 -78
- package/dist/control-schema.js.map +1 -1
- package/dist/control-write-retry.d.ts +45 -10
- package/dist/control-write-retry.js +92 -22
- 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/enrolled-machine-store.d.ts +3 -3
- package/dist/enrolled-machine-store.js +3 -3
- package/dist/formation-token.d.ts +48 -0
- package/dist/formation-token.js +26 -0
- package/dist/formation-token.js.map +1 -0
- package/dist/index.d.ts +27 -17
- package/dist/index.js +39 -17
- package/dist/index.js.map +1 -1
- package/dist/joined-strand-store.d.ts +29 -12
- package/dist/joined-strand-store.js +66 -27
- 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/link-budget.d.ts +9 -7
- package/dist/link-budget.js +9 -7
- package/dist/link-budget.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 +256 -0
- package/dist/member-redemption.js +490 -0
- package/dist/member-redemption.js.map +1 -0
- package/dist/membership-view.d.ts +91 -0
- package/dist/membership-view.js +110 -0
- package/dist/membership-view.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 +251 -0
- package/dist/owner-grant-protocol.js +344 -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-addr-book.d.ts +10 -1
- package/dist/peer-addr-book.js +4 -3
- package/dist/peer-addr-book.js.map +1 -1
- 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/peer-join-backfill.d.ts +89 -20
- package/dist/peer-join-backfill.js +265 -108
- package/dist/peer-join-backfill.js.map +1 -1
- package/dist/pending-join-runner.d.ts +48 -18
- package/dist/pending-join-runner.js +74 -56
- package/dist/pending-join-runner.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 +162 -133
- package/dist/seed-bootstrap.js +300 -283
- 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 +24 -2
- package/dist/strand-database.js +29 -6
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-first-sync-gate.d.ts +176 -88
- package/dist/strand-first-sync-gate.js +288 -121
- package/dist/strand-first-sync-gate.js.map +1 -1
- package/dist/strand-formation-deadlines.d.ts +9 -9
- package/dist/strand-formation-deadlines.js +9 -9
- package/dist/strand-formation-manager.d.ts +23 -17
- package/dist/strand-formation-manager.js +46 -44
- package/dist/strand-formation-manager.js.map +1 -1
- package/dist/strand-formation-protocol.d.ts +5 -8
- package/dist/strand-formation-protocol.js +101 -16
- package/dist/strand-formation-protocol.js.map +1 -1
- package/dist/strand-instance-manager.d.ts +27 -18
- package/dist/strand-instance-manager.js +119 -50
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-membership-reconciler.d.ts +16 -13
- package/dist/strand-membership-reconciler.js +30 -22
- package/dist/strand-membership-reconciler.js.map +1 -1
- package/dist/strand-membership-writer.d.ts +45 -22
- package/dist/strand-membership-writer.js +226 -131
- package/dist/strand-membership-writer.js.map +1 -1
- package/dist/strand-move.d.ts +6 -5
- package/dist/strand-move.js +36 -32
- 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/strand-revocation-enforcer.d.ts +134 -60
- package/dist/strand-revocation-enforcer.js +144 -71
- package/dist/strand-revocation-enforcer.js.map +1 -1
- package/dist/strand-solicitation.d.ts +50 -39
- package/dist/strand-solicitation.js +10 -3
- package/dist/strand-solicitation.js.map +1 -1
- package/dist/strand-watcher.d.ts +80 -13
- package/dist/strand-watcher.js +152 -48
- package/dist/strand-watcher.js.map +1 -1
- package/dist/table-change-watch.d.ts +63 -0
- package/dist/table-change-watch.js +209 -0
- package/dist/table-change-watch.js.map +1 -0
- package/dist/timeout-scheduler.d.ts +4 -3
- package/dist/timeout-scheduler.js +4 -3
- package/dist/timeout-scheduler.js.map +1 -1
- package/dist/types.d.ts +374 -150
- package/dist/types.js +4 -4
- package/dist/types.js.map +1 -1
- package/package.json +17 -8
- package/src/bootstrap-peer-store-file.ts +44 -44
- package/src/bootstrap-peer-store.ts +214 -214
- package/src/cadre-invite-protocol.ts +740 -1052
- package/src/cadre-join-request.ts +24 -3
- package/src/cadre-node.ts +2402 -1079
- package/src/control-authorization.ts +47 -14
- package/src/control-cohort.ts +204 -128
- package/src/control-database.ts +1749 -820
- package/src/control-formation-recorder.ts +359 -353
- package/src/control-protocol-guard.ts +23 -6
- package/src/control-schema-version.ts +301 -0
- package/src/control-schema.ts +273 -78
- package/src/control-write-retry.ts +641 -567
- package/src/delegate-admission.ts +351 -314
- 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/enrolled-machine-store.ts +3 -3
- package/src/formation-token.ts +63 -0
- package/src/index.ts +899 -789
- package/src/joined-strand-store.ts +73 -28
- package/src/key-store.ts +10 -4
- package/src/link-budget.ts +9 -7
- package/src/machine-removal.ts +26 -111
- package/src/member-redemption.ts +649 -0
- package/src/membership-view.ts +144 -0
- package/src/owner-anchor-sync.ts +19 -18
- package/src/owner-grant-protocol.ts +547 -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-addr-book.ts +4 -3
- package/src/peer-authorization.ts +558 -488
- package/src/peer-join-backfill.ts +971 -761
- package/src/pending-join-runner.ts +76 -53
- package/src/provider/rent-node.ts +1 -1
- package/src/seed-bootstrap.ts +343 -349
- 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 +352 -319
- package/src/strand-first-sync-gate.ts +334 -133
- package/src/strand-formation-deadlines.ts +227 -227
- package/src/strand-formation-manager.ts +731 -725
- package/src/strand-formation-protocol.ts +122 -24
- package/src/strand-instance-manager.ts +2092 -2026
- package/src/strand-membership-reconciler.ts +33 -25
- package/src/strand-membership-writer.ts +2093 -1974
- package/src/strand-move.ts +50 -38
- package/src/strand-peer-redial.ts +2 -3
- package/src/strand-revocation-enforcer.ts +740 -654
- package/src/strand-solicitation.ts +61 -34
- package/src/strand-watcher.ts +175 -56
- package/src/table-change-watch.ts +285 -0
- package/src/timeout-scheduler.ts +29 -28
- package/src/types.ts +391 -152
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
|
+
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The edge node's `CadreNodeConfig` and how a start settles its owner key. Nothing here needs a
|
|
3
|
+
* node started, so a test can build the node shape an edge device runs.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { DEFAULT_IDENTITY_KEY_ID, type KeyStore } from '../key-store.js';
|
|
7
|
+
import { createOwnerKey, loadOwnerSigner } from '../owner-key-store.js';
|
|
8
|
+
import { OWNER_KEY_MAX_LABEL } from '../owner-key-export.js';
|
|
9
|
+
import type { OwnerSigner } from '../owner-signer.js';
|
|
10
|
+
import type { CadreNode } from '../cadre-node.js';
|
|
11
|
+
import type { CadreNodeConfig } from '../types.js';
|
|
12
|
+
import type { BootstrapPeerStore } from '../bootstrap-peer-store.js';
|
|
13
|
+
import type { EnrolledMachineStore } from '../enrolled-machine-store.js';
|
|
14
|
+
import type { StrandNetworkStateStore } from '../strand-network-state.js';
|
|
15
|
+
import type { TrustedOwnerStore } from '../trusted-owner-store.js';
|
|
16
|
+
import type { IRawStorage, Libp2pTransports, NoiseCryptoInterface } from '@optimystic/db-p2p';
|
|
17
|
+
import type { EdgeNodeOptions } from './options.js';
|
|
18
|
+
|
|
19
|
+
/** What {@link buildEdgeNodeConfig} takes: one start's options plus what the platform built. */
|
|
20
|
+
export interface EdgeNodeConfigInputs extends EdgeNodeOptions {
|
|
21
|
+
/** `undefined` keeps libp2p-noise's own crypto. */
|
|
22
|
+
noiseCrypto?: NoiseCryptoInterface;
|
|
23
|
+
/**
|
|
24
|
+
* Holds the node identity, which the node loads on start, generating it on first run, and the
|
|
25
|
+
* owner key in its own slot (`DEFAULT_OWNER_KEY_ID`), which only {@link runOwnerGenesis} creates.
|
|
26
|
+
*/
|
|
27
|
+
keyStore: KeyStore;
|
|
28
|
+
/**
|
|
29
|
+
* Raw block storage per cadre-core storage scope: `controlStorageScope(partyId)` for the
|
|
30
|
+
* control network, the strand id for each strand. The key is already safe as a database-name
|
|
31
|
+
* segment — use it verbatim, do not parse it.
|
|
32
|
+
*/
|
|
33
|
+
storageProvider: (scope: string) => IRawStorage;
|
|
34
|
+
/** libp2p transport factories: what the node dials with, and what listens on `listenAddrs`. */
|
|
35
|
+
transports: Libp2pTransports;
|
|
36
|
+
/**
|
|
37
|
+
* Listen addresses besides the bare `/p2p-circuit` entry cadre-core adds for a named relay.
|
|
38
|
+
* Default `[]`: an edge device accepts no inbound connection. See `EdgeNodePlatform.listenAddrs`.
|
|
39
|
+
*/
|
|
40
|
+
listenAddrs?: string[];
|
|
41
|
+
/** See `EdgeNodePlatform.allowPrivateDial`. */
|
|
42
|
+
allowPrivateDial: boolean;
|
|
43
|
+
trustedOwnerStore: TrustedOwnerStore;
|
|
44
|
+
bootstrapPeerStore: BootstrapPeerStore;
|
|
45
|
+
enrolledMachineStore: EnrolledMachineStore;
|
|
46
|
+
strandNetworkStateStore: StrandNetworkStateStore;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The edge node's config: transaction profile (Ring Zulu only, intermittent connectivity), no
|
|
51
|
+
* listen address unless the platform names one, reachability through the configured relays (if
|
|
52
|
+
* any) but never a hard requirement for one, every strand the control network lists, and no
|
|
53
|
+
* automatic hibernation (the app's lifecycle runner hibernates on background). Schema signing is
|
|
54
|
+
* left at cadre-core's fail-closed default; an app with unsigned schemas relaxes it in
|
|
55
|
+
* `EdgeNodePlatform.configure`.
|
|
56
|
+
*/
|
|
57
|
+
export function buildEdgeNodeConfig(inputs: EdgeNodeConfigInputs): CadreNodeConfig {
|
|
58
|
+
return {
|
|
59
|
+
// Identity comes from the key store (loaded when present, generated and persisted on first
|
|
60
|
+
// run). Mutually exclusive with `privateKey`, which would take it out of the key store.
|
|
61
|
+
keyStore: inputs.keyStore,
|
|
62
|
+
identityKeyId: DEFAULT_IDENTITY_KEY_ID,
|
|
63
|
+
controlNetwork: {
|
|
64
|
+
partyId: inputs.partyId,
|
|
65
|
+
bootstrapNodes: inputs.bootstrapAddrs,
|
|
66
|
+
},
|
|
67
|
+
profile: 'transaction',
|
|
68
|
+
storage: {
|
|
69
|
+
provider: inputs.storageProvider,
|
|
70
|
+
},
|
|
71
|
+
network: {
|
|
72
|
+
transports: inputs.transports,
|
|
73
|
+
// An edge device accepts no inbound connection, so by default it listens on nothing:
|
|
74
|
+
// the only address it ever has is the `/p2p-circuit` address a relay reservation earns
|
|
75
|
+
// it. An explicitly empty `listenAddrs` stays empty through cadre-core's derivation, and
|
|
76
|
+
// naming a relay adds the bare `/p2p-circuit` search entry to it
|
|
77
|
+
// (`cadre-core/src/relay-addrs.ts` → `resolveListenAddrs`). A browser adds `/webrtc`.
|
|
78
|
+
listenAddrs: inputs.listenAddrs ?? [],
|
|
79
|
+
// Through this config field rather than `CadreNode.reserveRelays()`, which reaches the
|
|
80
|
+
// control node only: formation has the invitee dial the control node and then the strand
|
|
81
|
+
// nodes, so both need a circuit address.
|
|
82
|
+
//
|
|
83
|
+
// NOTE: naming a relay starts a reservation supervisor that runs for as long as the
|
|
84
|
+
// control node does, including while backgrounded (hibernation stops strands, not the
|
|
85
|
+
// control node): a 5 s local check while the reservation holds, a dial every backoff
|
|
86
|
+
// interval (2 s → 60 s) while it does not. If background battery use becomes a complaint,
|
|
87
|
+
// raise `checkMs`/`maxBackoffMs` in cadre-core rather than stopping the supervisor here,
|
|
88
|
+
// which would leave a device that silently stopped being invitable.
|
|
89
|
+
relayAddrs: inputs.relayAddrs,
|
|
90
|
+
// An edge device must come up with its relay down — on a plane, on dead Wi-Fi, or with
|
|
91
|
+
// none configured. A first reservation attempt that lands nothing is logged and retried in
|
|
92
|
+
// the background instead of failing `start()`. A malformed entry still throws.
|
|
93
|
+
requireRelay: false,
|
|
94
|
+
// The platform's Noise primitives (native SHA-256 / ChaCha20-Poly1305, and X25519 in
|
|
95
|
+
// `full` mode, on a phone); only local primitives change, not the wire protocol.
|
|
96
|
+
noiseCrypto: inputs.noiseCrypto,
|
|
97
|
+
// libp2p's `connection-gater` points its `react-native` and `browser` fields at a build
|
|
98
|
+
// that refuses to dial plain `ws://`, loopback and private addresses. A cadre-host node
|
|
99
|
+
// claimed by its code on the same Wi-Fi, an emulator reaching `10.0.2.2` and a self-run
|
|
100
|
+
// relay without TLS are all of those. Only the dial is permitted: the connection is still
|
|
101
|
+
// Noise, the pinned `/p2p/<id>` decides which machine answers, and membership is still
|
|
102
|
+
// gated by `denyDialPeer` and the inbound hooks. cadre-core hands this gater to strand nodes too.
|
|
103
|
+
...(inputs.allowPrivateDial ? { connectionGater: { denyDialMultiaddr: () => false } } : {}),
|
|
104
|
+
},
|
|
105
|
+
strandFilter: { mode: 'all' },
|
|
106
|
+
hibernation: { enabled: false },
|
|
107
|
+
trustedOwners: { store: inputs.trustedOwnerStore },
|
|
108
|
+
bootstrapPeers: { store: inputs.bootstrapPeerStore },
|
|
109
|
+
enrolledMachines: { store: inputs.enrolledMachineStore },
|
|
110
|
+
strandNetworkState: { store: inputs.strandNetworkStateStore },
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* How the start settled this node's owner key: `EdgeNodeStatus.owner`. `'enrolled'`: a key is
|
|
116
|
+
* an owner and wired for signing. `'member'`: the party lists no key this app holds (a party it
|
|
117
|
+
* joined, or one it founded whose owner slot was lost). `'failed'` and `'timed-out'`: see
|
|
118
|
+
* {@link runOwnerGenesis}.
|
|
119
|
+
*/
|
|
120
|
+
export type OwnerGenesisResult = 'enrolled' | 'member' | 'failed' | 'timed-out';
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Default bound on {@link runOwnerGenesis} and {@link wireJoinedOwnerKey}: what a start can wait
|
|
124
|
+
* for its owner step, which runs control reads and one control write, before it goes on without
|
|
125
|
+
* it. Not derived from the link, since a start that waited out a stalled write (about 84 s, see
|
|
126
|
+
* `docs/cadre-consistency.md`) would be a start the user gave up on.
|
|
127
|
+
*/
|
|
128
|
+
// eslint-disable-next-line no-restricted-syntax -- cuts off by design: a start goes on without its owner step rather than wait out a stalled control write; see docs/cadre-consistency.md → "Deadlines Over Optimystic's Reads and Commits"
|
|
129
|
+
export const DEFAULT_OWNER_GENESIS_TIMEOUT_MS = 60_000;
|
|
130
|
+
|
|
131
|
+
/** Default `EdgeNodeOptions.ownerKeyLabel`: the founding owner key's name on its `OwnerKey` row. */
|
|
132
|
+
export const DEFAULT_OWNER_KEY_LABEL = 'phone';
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Refuses an `ownerKeyLabel` the founding `OwnerKey` row would refuse, so a start naming one
|
|
136
|
+
* fails outright: fail-soft genesis would only log it, on that start and every later one.
|
|
137
|
+
*/
|
|
138
|
+
export function requireOwnerKeyLabel(label: string | undefined): void {
|
|
139
|
+
const length = label?.trim().length ?? 0;
|
|
140
|
+
if (length > OWNER_KEY_MAX_LABEL) {
|
|
141
|
+
throw new Error(`ownerKeyLabel must be at most ${OWNER_KEY_MAX_LABEL} characters (this one has ${length})`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** What {@link runOwnerGenesis} takes. */
|
|
146
|
+
export interface OwnerGenesisOptions {
|
|
147
|
+
/** The key store whose owner slot holds this app's owner key: the one the identity is in. */
|
|
148
|
+
keyStore: KeyStore;
|
|
149
|
+
/** The founding row's label; default {@link DEFAULT_OWNER_KEY_LABEL}. */
|
|
150
|
+
label?: string;
|
|
151
|
+
/** Default {@link DEFAULT_OWNER_GENESIS_TIMEOUT_MS}. */
|
|
152
|
+
timeoutMs?: number;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** What {@link wireJoinedOwnerKey} takes. */
|
|
156
|
+
export interface JoinedOwnerKeyOptions {
|
|
157
|
+
/** The key store whose owner slot holds this app's owner key, if it holds one. */
|
|
158
|
+
keyStore: KeyStore;
|
|
159
|
+
/** Default {@link DEFAULT_OWNER_GENESIS_TIMEOUT_MS}. */
|
|
160
|
+
timeoutMs?: number;
|
|
161
|
+
/** An owner grant has just seated the slot's key: keep asking until the row arrives. */
|
|
162
|
+
granted?: boolean;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** How often {@link wireJoinedOwnerKey} asks again for an owner key an owner grant just seated. */
|
|
166
|
+
const GRANTED_OWNER_RECHECK_MS = 2_000;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The owner step of a party this app founded. On a party with no `OwnerKey` row yet, the owner
|
|
170
|
+
* slot's key (created there now unless the slot already holds one) becomes the founding owner
|
|
171
|
+
* under `label` and is wired for signing, which also lets the node author its own `CadrePeer` row.
|
|
172
|
+
* On a party that has owners, the slot's key is wired only when it is a live owner, and no key is
|
|
173
|
+
* created: a new one could not be seated without another owner's signature. A node must be an
|
|
174
|
+
* enrolled owner to publish strands, and `CadreNode.requestJoin` works only on one. A joined
|
|
175
|
+
* party's start runs {@link wireJoinedOwnerKey} instead.
|
|
176
|
+
*
|
|
177
|
+
* Fail-soft and bounded: a failure or a genesis that has not settled within `timeoutMs` is logged
|
|
178
|
+
* and returned, and the start stands — the app can still join discovered strands and sync. A
|
|
179
|
+
* genesis that times out keeps running; its later failure is logged.
|
|
180
|
+
*
|
|
181
|
+
* NOTE: the bound is here, in cadre-core, so every edge node gets it; a Node host runs genesis
|
|
182
|
+
* through the CLI's own flow instead. Since v1.10 a control write against an unresponsive member
|
|
183
|
+
* can hold the write lock for about 84 s, so an unbounded wait here would be a real stall. A bound
|
|
184
|
+
* on that write itself, in `ControlDatabase`, would make this one redundant; drop `timeoutMs` then.
|
|
185
|
+
*/
|
|
186
|
+
export function runOwnerGenesis(node: CadreNode, options: OwnerGenesisOptions): Promise<OwnerGenesisResult> {
|
|
187
|
+
const label = options.label ?? DEFAULT_OWNER_KEY_LABEL;
|
|
188
|
+
return withinBound('owner self-genesis', enrollFoundedOwner(node, options.keyStore, label), options.timeoutMs ?? DEFAULT_OWNER_GENESIS_TIMEOUT_MS);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* The owner step of a party this app joined: wire a key only when the party lists it as a live
|
|
193
|
+
* owner, never insert an `OwnerKey` row, so a joined app can neither seat itself as a founder nor
|
|
194
|
+
* anchor a key that is no owner's. Only the owner slot's key is tried: the key an owner grant
|
|
195
|
+
* seated, one imported here, or one an owner of the party added. The identity key is never
|
|
196
|
+
* tried, since a machine's network identity is never an owner key. Fail-soft and bounded as
|
|
197
|
+
* {@link runOwnerGenesis} is.
|
|
198
|
+
*
|
|
199
|
+
* `granted`: an owner grant has just made this app an owner, so a key not listed yet means the
|
|
200
|
+
* row has not reached this node, not that it is no owner. It is asked again every 2 s until it
|
|
201
|
+
* is wired or the bound passes; a start that timed out wires it on the next start.
|
|
202
|
+
*/
|
|
203
|
+
export function wireJoinedOwnerKey(node: CadreNode, options: JoinedOwnerKeyOptions): Promise<OwnerGenesisResult> {
|
|
204
|
+
let expired = false;
|
|
205
|
+
const wiring = async (): Promise<OwnerGenesisResult> => {
|
|
206
|
+
const held = await loadOwnerSigner(options.keyStore);
|
|
207
|
+
while (!(await wireJoinedOwner(node, held))) {
|
|
208
|
+
if (!options.granted) return 'member';
|
|
209
|
+
await new Promise((resolve) => setTimeout(resolve, GRANTED_OWNER_RECHECK_MS));
|
|
210
|
+
// Past the bound the start has gone on as `'timed-out'`, and may have stopped the node.
|
|
211
|
+
if (expired) return 'member';
|
|
212
|
+
}
|
|
213
|
+
return 'enrolled';
|
|
214
|
+
};
|
|
215
|
+
return withinBound('wiring the joined owner key', wiring(), options.timeoutMs ?? DEFAULT_OWNER_GENESIS_TIMEOUT_MS)
|
|
216
|
+
.finally(() => {
|
|
217
|
+
expired = true;
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** `settling`, or `'failed'` when it rejects (logged), or `'timed-out'` when it has not settled within `timeoutMs` (logged). */
|
|
222
|
+
async function withinBound(
|
|
223
|
+
label: string,
|
|
224
|
+
settling: Promise<OwnerGenesisResult>,
|
|
225
|
+
timeoutMs: number,
|
|
226
|
+
): Promise<OwnerGenesisResult> {
|
|
227
|
+
const settled = settling.catch((err: unknown): OwnerGenesisResult => {
|
|
228
|
+
console.warn(`[cadre-core/edge-node] ${label} failed:`, err);
|
|
229
|
+
return 'failed';
|
|
230
|
+
});
|
|
231
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
232
|
+
const deadline = new Promise<OwnerGenesisResult>((resolve) => {
|
|
233
|
+
timer = setTimeout(() => resolve('timed-out'), timeoutMs);
|
|
234
|
+
});
|
|
235
|
+
try {
|
|
236
|
+
const result = await Promise.race([settled, deadline]);
|
|
237
|
+
if (result === 'timed-out') {
|
|
238
|
+
console.warn(`[cadre-core/edge-node] ${label} has not settled after ${timeoutMs} ms; starting without it`);
|
|
239
|
+
}
|
|
240
|
+
return result;
|
|
241
|
+
} finally {
|
|
242
|
+
clearTimeout(timer);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function enrollFoundedOwner(node: CadreNode, keyStore: KeyStore, label: string): Promise<OwnerGenesisResult> {
|
|
247
|
+
const controlDb = node.getControlDatabase();
|
|
248
|
+
if (!controlDb) {
|
|
249
|
+
throw new Error('control database unavailable after start; cannot run owner genesis');
|
|
250
|
+
}
|
|
251
|
+
const held = await loadOwnerSigner(keyStore);
|
|
252
|
+
if (await controlDb.hasOwnerKey()) {
|
|
253
|
+
return held ? wiredIfOwner(node, held) : 'member';
|
|
254
|
+
}
|
|
255
|
+
// Loaded rather than created when the slot already holds a key: one imported before founding,
|
|
256
|
+
// or one an iOS reinstall kept in the Keychain while the app's data went.
|
|
257
|
+
const founding = held ?? (await createOwnerKey(keyStore));
|
|
258
|
+
if (!(await controlDb.ensureOwnerKey(founding.publicKey, label))) {
|
|
259
|
+
// A row arrived between the two reads: the party has an owner now, so judge the key as above.
|
|
260
|
+
return wiredIfOwner(node, founding);
|
|
261
|
+
}
|
|
262
|
+
await node.useOwnerSigner(founding);
|
|
263
|
+
return 'enrolled';
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async function wiredIfOwner(node: CadreNode, signer: OwnerSigner): Promise<OwnerGenesisResult> {
|
|
267
|
+
return (await node.wireOwnKeyIfOwner(signer)) ? 'enrolled' : 'member';
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Wire `held`, the owner slot's key, when the party lists it. A node takes one owner signer, so
|
|
272
|
+
* a signer already wired is asked about instead (is it still a live owner?) and never replaced.
|
|
273
|
+
*/
|
|
274
|
+
async function wireJoinedOwner(node: CadreNode, held: OwnerSigner | undefined): Promise<boolean> {
|
|
275
|
+
if (node.getOwnerSigner() !== null) return await node.wireOwnKeyIfOwner();
|
|
276
|
+
return held !== undefined && (await node.wireOwnKeyIfOwner(held));
|
|
277
|
+
}
|