@serfab/cadre-core 1.13.0 → 1.15.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 +293 -274
- package/dist/cadre-invite-protocol.d.ts +383 -0
- package/dist/cadre-invite-protocol.js +774 -0
- package/dist/cadre-invite-protocol.js.map +1 -0
- package/dist/cadre-join-request.d.ts +42 -0
- package/dist/cadre-join-request.js +144 -0
- package/dist/cadre-join-request.js.map +1 -0
- package/dist/cadre-node.d.ts +849 -210
- package/dist/cadre-node.js +1574 -374
- package/dist/cadre-node.js.map +1 -1
- package/dist/claim-proof.d.ts +45 -0
- package/dist/claim-proof.js +132 -0
- package/dist/claim-proof.js.map +1 -0
- package/dist/control-authorization.d.ts +80 -19
- package/dist/control-authorization.js +55 -13
- package/dist/control-authorization.js.map +1 -1
- package/dist/control-cohort.js +1 -1
- package/dist/control-cohort.js.map +1 -1
- package/dist/control-database.d.ts +605 -108
- package/dist/control-database.js +1299 -280
- package/dist/control-database.js.map +1 -1
- package/dist/control-protocol-guard.d.ts +142 -0
- package/dist/control-protocol-guard.js +293 -0
- package/dist/control-protocol-guard.js.map +1 -0
- package/dist/control-schema.d.ts +1 -1
- package/dist/control-schema.js +817 -1067
- package/dist/control-schema.js.map +1 -1
- package/dist/control-stream.d.ts +4 -0
- package/dist/control-stream.js +22 -7
- package/dist/control-stream.js.map +1 -1
- package/dist/delegate-admission.d.ts +1 -1
- package/dist/delegate-admission.js +1 -1
- package/dist/device-token.d.ts +11 -9
- package/dist/device-token.js +14 -12
- package/dist/device-token.js.map +1 -1
- package/dist/ed25519-key.d.ts +22 -9
- package/dist/ed25519-key.js +47 -10
- package/dist/ed25519-key.js.map +1 -1
- package/dist/fetch-impl.d.ts +12 -0
- package/dist/fetch-impl.js +23 -0
- package/dist/fetch-impl.js.map +1 -0
- package/dist/file-durable-slot.js +2 -1
- package/dist/file-durable-slot.js.map +1 -1
- package/dist/file-safe-name.d.ts +25 -0
- package/dist/file-safe-name.js +29 -0
- package/dist/file-safe-name.js.map +1 -0
- package/dist/formation-approval.js +5 -11
- package/dist/formation-approval.js.map +1 -1
- package/dist/fs-atomic.d.ts +0 -19
- package/dist/fs-atomic.js +3 -24
- package/dist/fs-atomic.js.map +1 -1
- package/dist/index.d.ts +22 -10
- package/dist/index.js +54 -11
- package/dist/index.js.map +1 -1
- package/dist/invitation-bootstrap.d.ts +50 -12
- package/dist/invitation-bootstrap.js +76 -13
- package/dist/invitation-bootstrap.js.map +1 -1
- package/dist/joined-strand-store.d.ts +7 -0
- package/dist/joined-strand-store.js +9 -0
- package/dist/joined-strand-store.js.map +1 -1
- package/dist/key-store-file.js +2 -1
- package/dist/key-store-file.js.map +1 -1
- package/dist/link-budget.d.ts +25 -33
- package/dist/link-budget.js +25 -35
- package/dist/link-budget.js.map +1 -1
- package/dist/machine-removal.d.ts +78 -0
- package/dist/machine-removal.js +166 -0
- package/dist/machine-removal.js.map +1 -0
- package/dist/membership-connection-gater.d.ts +191 -151
- package/dist/membership-connection-gater.js +236 -173
- package/dist/membership-connection-gater.js.map +1 -1
- package/dist/node-claim-payload.d.ts +74 -0
- package/dist/node-claim-payload.js +224 -0
- package/dist/node-claim-payload.js.map +1 -0
- package/dist/node-default-transports.d.ts +27 -0
- package/dist/node-default-transports.js +35 -0
- package/dist/node-default-transports.js.map +1 -0
- package/dist/node-transports.d.ts +30 -0
- package/dist/node-transports.js +40 -0
- package/dist/node-transports.js.map +1 -0
- package/dist/owner-anchor-sync.d.ts +22 -0
- package/dist/owner-anchor-sync.js +143 -0
- package/dist/owner-anchor-sync.js.map +1 -0
- package/dist/peer-authorization.d.ts +113 -18
- package/dist/peer-authorization.js +225 -29
- package/dist/peer-authorization.js.map +1 -1
- package/dist/peer-dial.d.ts +143 -27
- package/dist/peer-dial.js +276 -31
- package/dist/peer-dial.js.map +1 -1
- package/dist/peer-join-backfill.d.ts +26 -2
- package/dist/peer-join-backfill.js +60 -12
- package/dist/peer-join-backfill.js.map +1 -1
- package/dist/peer-record.d.ts +17 -52
- package/dist/peer-record.js +20 -58
- package/dist/peer-record.js.map +1 -1
- package/dist/pending-join-runner.d.ts +66 -58
- package/dist/pending-join-runner.js +136 -145
- package/dist/pending-join-runner.js.map +1 -1
- package/dist/primary-lan-address.d.ts +8 -0
- package/dist/primary-lan-address.js +46 -0
- package/dist/primary-lan-address.js.map +1 -0
- package/dist/provider/client.d.ts +131 -0
- package/dist/provider/client.js +226 -0
- package/dist/provider/client.js.map +1 -0
- package/dist/provider/descriptor.d.ts +41 -0
- package/dist/provider/descriptor.js +103 -0
- package/dist/provider/descriptor.js.map +1 -0
- package/dist/provider/index.d.ts +9 -0
- package/dist/provider/index.js +10 -0
- package/dist/provider/index.js.map +1 -0
- package/dist/provider/oauth.d.ts +125 -0
- package/dist/provider/oauth.js +199 -0
- package/dist/provider/oauth.js.map +1 -0
- package/dist/provider/rent-node.d.ts +101 -0
- package/dist/provider/rent-node.js +220 -0
- package/dist/provider/rent-node.js.map +1 -0
- package/dist/push-fanout.d.ts +8 -2
- package/dist/push-fanout.js +8 -2
- package/dist/push-fanout.js.map +1 -1
- package/dist/relay-addrs.d.ts +9 -4
- package/dist/relay-addrs.js +9 -4
- package/dist/relay-addrs.js.map +1 -1
- package/dist/relay-reservation.d.ts +51 -16
- package/dist/relay-reservation.js +107 -24
- package/dist/relay-reservation.js.map +1 -1
- package/dist/seed-bootstrap.d.ts +165 -123
- package/dist/seed-bootstrap.js +313 -275
- package/dist/seed-bootstrap.js.map +1 -1
- package/dist/seed-trust-policy.d.ts +98 -37
- package/dist/seed-trust-policy.js +188 -2
- package/dist/seed-trust-policy.js.map +1 -1
- package/dist/strand-addr-protocol.d.ts +24 -26
- package/dist/strand-addr-protocol.js +19 -26
- package/dist/strand-addr-protocol.js.map +1 -1
- package/dist/strand-commit-durability.d.ts +54 -0
- package/dist/strand-commit-durability.js +74 -0
- package/dist/strand-commit-durability.js.map +1 -0
- package/dist/strand-database.d.ts +23 -8
- package/dist/strand-database.js +41 -13
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-first-sync-gate.d.ts +7 -1
- package/dist/strand-first-sync-gate.js +7 -1
- package/dist/strand-first-sync-gate.js.map +1 -1
- package/dist/strand-formation-protocol.js +5 -2
- package/dist/strand-formation-protocol.js.map +1 -1
- package/dist/strand-fret-addrs.d.ts +19 -1
- package/dist/strand-fret-addrs.js +27 -9
- package/dist/strand-fret-addrs.js.map +1 -1
- package/dist/strand-instance-manager.d.ts +81 -19
- package/dist/strand-instance-manager.js +216 -105
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-move.d.ts +124 -0
- package/dist/strand-move.js +438 -0
- package/dist/strand-move.js.map +1 -0
- package/dist/strand-network-config.js +7 -6
- package/dist/strand-network-config.js.map +1 -1
- package/dist/strand-peer-redial.d.ts +113 -0
- package/dist/strand-peer-redial.js +236 -0
- package/dist/strand-peer-redial.js.map +1 -0
- package/dist/strand-revocation-enforcer.d.ts +11 -11
- package/dist/strand-revocation-enforcer.js +11 -11
- package/dist/strand-wake-protocol.d.ts +13 -16
- package/dist/strand-wake-protocol.js +23 -27
- package/dist/strand-wake-protocol.js.map +1 -1
- package/dist/trusted-owner-store.d.ts +35 -5
- package/dist/trusted-owner-store.js +50 -8
- package/dist/trusted-owner-store.js.map +1 -1
- package/dist/types.d.ts +512 -89
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -1
- package/package.json +19 -10
- package/src/cadre-invite-protocol.ts +1052 -0
- package/src/cadre-join-request.ts +167 -0
- package/src/cadre-node.ts +1703 -386
- package/src/claim-proof.ts +147 -0
- package/src/control-authorization.ts +203 -117
- package/src/control-cohort.ts +1 -1
- package/src/control-database.ts +4663 -3453
- package/src/control-protocol-guard.ts +348 -0
- package/src/control-schema.ts +828 -1078
- package/src/control-stream.ts +26 -7
- package/src/delegate-admission.ts +1 -1
- package/src/device-token.ts +14 -10
- package/src/ed25519-key.ts +47 -10
- package/src/fetch-impl.ts +23 -0
- package/src/file-durable-slot.ts +64 -63
- package/src/file-safe-name.ts +32 -0
- package/src/formation-approval.ts +604 -610
- package/src/fs-atomic.ts +92 -117
- package/src/index.ts +789 -648
- package/src/invitation-bootstrap.ts +91 -13
- package/src/joined-strand-store.ts +508 -498
- package/src/key-store-file.ts +142 -141
- package/src/link-budget.ts +25 -36
- package/src/machine-removal.ts +216 -0
- package/src/membership-connection-gater.ts +259 -191
- package/src/node-claim-payload.ts +259 -0
- package/src/node-default-transports.ts +38 -0
- package/src/node-transports.ts +49 -0
- package/src/owner-anchor-sync.ts +171 -0
- package/src/peer-authorization.ts +258 -29
- package/src/peer-dial.ts +604 -315
- package/src/peer-join-backfill.ts +62 -11
- package/src/peer-record.ts +159 -197
- package/src/pending-join-runner.ts +729 -735
- package/src/primary-lan-address.ts +47 -0
- package/src/provider/client.ts +326 -0
- package/src/provider/descriptor.ts +129 -0
- package/src/provider/index.ts +59 -0
- package/src/provider/oauth.ts +285 -0
- package/src/provider/rent-node.ts +275 -0
- package/src/push-fanout.ts +8 -2
- package/src/relay-addrs.ts +9 -4
- package/src/relay-reservation.ts +1211 -1121
- package/src/seed-bootstrap.ts +1502 -1448
- package/src/seed-trust-policy.ts +353 -140
- package/src/strand-addr-protocol.ts +26 -35
- package/src/strand-commit-durability.ts +116 -0
- package/src/strand-database.ts +46 -14
- package/src/strand-first-sync-gate.ts +7 -1
- package/src/strand-formation-protocol.ts +1039 -1036
- package/src/strand-fret-addrs.ts +179 -145
- package/src/strand-instance-manager.ts +2026 -1883
- package/src/strand-move.ts +585 -0
- package/src/strand-network-config.ts +7 -6
- package/src/strand-peer-redial.ts +307 -0
- package/src/strand-revocation-enforcer.ts +11 -11
- package/src/strand-wake-protocol.ts +23 -30
- package/src/trusted-owner-store.ts +242 -171
- package/src/types.ts +2687 -2242
|
@@ -34,6 +34,12 @@ import { peerJoinPushBudget } from './link-budget.js';
|
|
|
34
34
|
// If Optimystic ever grows a cohort-join catch-up of its own, delete this module rather
|
|
35
35
|
// than running two.
|
|
36
36
|
//
|
|
37
|
+
// NOTE: strand nodes pass db-p2p no `kvStore`, so Optimystic's under-replication ledger (the
|
|
38
|
+
// copies a node owes peers) dies at every restart. This module's whole-store push on connect is
|
|
39
|
+
// what delivers a write a machine made alone after it restarts (`joiner-first-commit-over-slow-relay`,
|
|
40
|
+
// restart arm). If this module is narrowed (the cohort filter above) or deleted, give strand
|
|
41
|
+
// nodes a persistent kvStore first.
|
|
42
|
+
//
|
|
37
43
|
// Membership — the one place the two networks differ, expressed as the optional
|
|
38
44
|
// `authorizePeer` dep:
|
|
39
45
|
//
|
|
@@ -315,10 +321,16 @@ export class PeerJoinBackfill {
|
|
|
315
321
|
private readonly createPushClient: (peerId: PeerId) => PeerJoinBackfillPushClient;
|
|
316
322
|
private started = false;
|
|
317
323
|
private stopped = false;
|
|
318
|
-
/**
|
|
324
|
+
/**
|
|
325
|
+
* Peers fully caught up this runtime (never rescheduled until the runtime is rebuilt; only
|
|
326
|
+
* {@link forceCatchUpPeer} pushes to one again).
|
|
327
|
+
*/
|
|
319
328
|
private readonly done = new Set<string>();
|
|
320
|
-
/**
|
|
321
|
-
|
|
329
|
+
/**
|
|
330
|
+
* Peers with a catch-up currently running, each with that run (suppresses concurrent
|
|
331
|
+
* duplicates; {@link forceCatchUpPeer} waits on the run).
|
|
332
|
+
*/
|
|
333
|
+
private readonly inFlight = new Map<string, Promise<PeerJoinBackfillResult>>();
|
|
322
334
|
/**
|
|
323
335
|
* Peers whose (re-)schedule arrived while their OWN run was in flight, replayed once
|
|
324
336
|
* that run finishes. Load-bearing for the gated path: the gate's authorization check is
|
|
@@ -464,7 +476,53 @@ export class PeerJoinBackfill {
|
|
|
464
476
|
if (this.stopped || this.done.has(key) || this.inFlight.has(key)) {
|
|
465
477
|
return emptyResult();
|
|
466
478
|
}
|
|
467
|
-
this.
|
|
479
|
+
return await this.trackRun(peerId);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Run one peer's catch-up now and wait for it, whatever this instance remembers about the
|
|
484
|
+
* peer: wait out a run already in flight for it, then push the whole store again even when
|
|
485
|
+
* the peer is memoized as caught up. Never rejects; an all-zero result only when stopped.
|
|
486
|
+
*
|
|
487
|
+
* For a caller that must know the peer holds this store before it proceeds, where
|
|
488
|
+
* {@link catchUpPeer}'s empty answer would hide a run that was denied (a `peer:identify`
|
|
489
|
+
* run can pass the gate a moment before the caller authorized the peer) or a memo older
|
|
490
|
+
* than the commits since.
|
|
491
|
+
*/
|
|
492
|
+
async forceCatchUpPeer(peerId: PeerId): Promise<PeerJoinBackfillResult> {
|
|
493
|
+
const key = peerId.toString();
|
|
494
|
+
for (let pending = this.inFlight.get(key); pending; pending = this.inFlight.get(key)) {
|
|
495
|
+
await pending;
|
|
496
|
+
}
|
|
497
|
+
if (this.stopped) {
|
|
498
|
+
return emptyResult();
|
|
499
|
+
}
|
|
500
|
+
return await this.trackRun(peerId);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Register one run in {@link inFlight} before its first await, so no second run for the
|
|
505
|
+
* peer can start beside it, and replay a schedule that arrived while it ran.
|
|
506
|
+
*/
|
|
507
|
+
private async trackRun(peerId: PeerId): Promise<PeerJoinBackfillResult> {
|
|
508
|
+
const key = peerId.toString();
|
|
509
|
+
const run = this.settleRun(peerId);
|
|
510
|
+
this.inFlight.set(key, run);
|
|
511
|
+
try {
|
|
512
|
+
return await run;
|
|
513
|
+
} finally {
|
|
514
|
+
this.inFlight.delete(key);
|
|
515
|
+
// Replay a schedule that arrived mid-run. `schedulePeer` re-checks `done`, so a run
|
|
516
|
+
// that finished clean re-arms nothing; a denied or failed one gets its retry.
|
|
517
|
+
if (this.rearmAfterFlight.delete(key)) {
|
|
518
|
+
this.schedulePeer(peerId);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/** One run and what follows from its outcome: the memo, or a backoff re-arm. Never rejects. */
|
|
524
|
+
private async settleRun(peerId: PeerId): Promise<PeerJoinBackfillResult> {
|
|
525
|
+
const key = peerId.toString();
|
|
468
526
|
try {
|
|
469
527
|
const { result, clean, pushFailed } = await this.runCatchUp(peerId);
|
|
470
528
|
// NOTE: a run that hit `maxBlocks` is still "clean" and still memoizes the peer, so
|
|
@@ -496,13 +554,6 @@ export class PeerJoinBackfill {
|
|
|
496
554
|
// metadata reads too — a backfill fault must never surface through a libp2p event.
|
|
497
555
|
log('[%s] catch-up peer=%s failed: %o', this.deps.label, key, error);
|
|
498
556
|
return emptyResult();
|
|
499
|
-
} finally {
|
|
500
|
-
this.inFlight.delete(key);
|
|
501
|
-
// Replay a schedule that arrived mid-run. `schedulePeer` re-checks `done`, so a run
|
|
502
|
-
// that finished clean re-arms nothing; a denied or failed one gets its retry.
|
|
503
|
-
if (this.rearmAfterFlight.delete(key)) {
|
|
504
|
-
this.schedulePeer(peerId);
|
|
505
|
-
}
|
|
506
557
|
}
|
|
507
558
|
}
|
|
508
559
|
|
package/src/peer-record.ts
CHANGED
|
@@ -1,197 +1,159 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Peer-address record helpers: the single source of truth for the bytes a node
|
|
3
|
-
* signs when publishing its own `CadrePeer` row and re-verifies when resolving
|
|
4
|
-
* another peer's row.
|
|
5
|
-
*
|
|
6
|
-
* The signed payload is a single SHA-256 digest over the shared domain-tagged
|
|
7
|
-
* field vector (see control-authorization.ts) — the crypto plugin's injective
|
|
8
|
-
* multi-field encoding, so no field split is ambiguous. It is intentionally NOT
|
|
9
|
-
* JSON-canonicalized (deterministic across node/browser/RN, no key ordering)
|
|
10
|
-
* and — crucially — is reconstructable inside the `CadrePeer.AuthorizedUpdate`
|
|
11
|
-
* SQL constraint from the row's own columns
|
|
12
|
-
*
|
|
13
|
-
* digest('CadreControl.CadrePeer', 'publish',
|
|
14
|
-
* new.PeerId, new.Multiaddr, cast(new.UpdatedAt as text)
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
import
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* @param
|
|
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
|
-
export function
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
* Never use before signature verification: verification runs against the
|
|
161
|
-
* original on-record strings.
|
|
162
|
-
*/
|
|
163
|
-
export function withTrailingPeerId(addr: Multiaddr, peerId: string): Multiaddr | null {
|
|
164
|
-
const components = addr.getComponents();
|
|
165
|
-
if (components[components.length - 1]?.name !== 'p2p') {
|
|
166
|
-
return addr.encapsulate(`/p2p/${peerId}`);
|
|
167
|
-
}
|
|
168
|
-
return trailingPeerId(addr) === peerId ? addr : null;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Return `addrs` with signaling (`/p2p-circuit`) addrs first, otherwise stable.
|
|
173
|
-
* Used to present the WebRTC dial input ahead of direct addrs. Does NOT mutate
|
|
174
|
-
* the input and must not be used before signature verification (verification
|
|
175
|
-
* uses the original on-record order).
|
|
176
|
-
*/
|
|
177
|
-
export function orderSignalingFirst(addrs: string[]): string[] {
|
|
178
|
-
const signaling = addrs.filter(isSignalingAddr);
|
|
179
|
-
const rest = addrs.filter(a => !isSignalingAddr(a));
|
|
180
|
-
return [...signaling, ...rest];
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Default resolve trust gate: trust any peer that has a `CadrePeer` row.
|
|
185
|
-
*
|
|
186
|
-
* A row only exists because an owner signed its `AuthorizedInsert`, so row
|
|
187
|
-
* presence already means "owner-vouched member". This is the seam where a
|
|
188
|
-
* stricter policy (trust circle, pinned keys) plugs in — see
|
|
189
|
-
* {@link PeerResolveTrustPolicy}.
|
|
190
|
-
*/
|
|
191
|
-
export function currentMemberTrustPolicy(): PeerResolveTrustPolicy {
|
|
192
|
-
return {
|
|
193
|
-
evaluate() {
|
|
194
|
-
return true;
|
|
195
|
-
},
|
|
196
|
-
};
|
|
197
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Peer-address record helpers: the single source of truth for the bytes a node
|
|
3
|
+
* signs when publishing its own `CadrePeer` row and re-verifies when resolving
|
|
4
|
+
* another peer's row.
|
|
5
|
+
*
|
|
6
|
+
* The signed payload is a single SHA-256 digest over the shared domain-tagged
|
|
7
|
+
* field vector (see control-authorization.ts) — the crypto plugin's injective
|
|
8
|
+
* multi-field encoding, so no field split is ambiguous. It is intentionally NOT
|
|
9
|
+
* JSON-canonicalized (deterministic across node/browser/RN, no key ordering)
|
|
10
|
+
* and — crucially — is reconstructable inside the `CadrePeer.AuthorizedUpdate`
|
|
11
|
+
* SQL constraint from the row's own columns and the node-local `party_id()`:
|
|
12
|
+
*
|
|
13
|
+
* digest('CadreControl.CadrePeer', 'publish', party_id(),
|
|
14
|
+
* new.PeerId, new.Multiaddr, cast(new.UpdatedAt as text),
|
|
15
|
+
* coalesce(new.Profile, ''))
|
|
16
|
+
*
|
|
17
|
+
* Keep {@link peerRecordSignedPayload} and that constraint byte-for-byte in
|
|
18
|
+
* sync. The `'publish'` action tag marks this as a peer's SELF-signed record
|
|
19
|
+
* (signed with the peer's own key, not an owner key) and keeps it disjoint from
|
|
20
|
+
* every owner-signed digest; the party id keeps a record published into one
|
|
21
|
+
* party's table from verifying in another's. A node signs and verifies with its
|
|
22
|
+
* own party: these records come only from its own party's control database.
|
|
23
|
+
*
|
|
24
|
+
* `publicKey` is deliberately excluded from the payload — it is the key the
|
|
25
|
+
* signature is verified *with* (so a signature already commits to exactly one
|
|
26
|
+
* key), and its binding to `peerId` is checked separately at resolve time.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { digest, sign, verify } from '@optimystic/quereus-plugin-crypto';
|
|
30
|
+
import { controlAuthorizationFields } from './control-authorization.js';
|
|
31
|
+
import type { NodeProfile, PeerAddressRecord, PeerResolveTrustPolicy } from './types.js';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Default freshness ceiling for resolved peer-address records (15 minutes).
|
|
35
|
+
* A record older than this is treated as stale and filtered out, so a resolver
|
|
36
|
+
* never hands back a dead relay reservation. The publish path re-stamps at
|
|
37
|
+
* roughly half this interval (see {@link DEFAULT_PEER_RECORD_HEARTBEAT_MS}).
|
|
38
|
+
*/
|
|
39
|
+
export const DEFAULT_PEER_RECORD_MAX_AGE_MS = 15 * 60 * 1000;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* TTL heartbeat interval for re-publishing the self record — half the freshness
|
|
43
|
+
* ceiling, so a record is refreshed well before it can go stale.
|
|
44
|
+
*/
|
|
45
|
+
export const DEFAULT_PEER_RECORD_HEARTBEAT_MS = DEFAULT_PEER_RECORD_MAX_AGE_MS / 2;
|
|
46
|
+
|
|
47
|
+
/** The signaling/relay prefix a WebRTC (or any relayed) dial path consumes. */
|
|
48
|
+
const SIGNALING_PREFIX = '/p2p-circuit';
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Build the base64url SHA-256 digest that the self-signature covers. Mirrors the
|
|
52
|
+
* `CadrePeer.AuthorizedUpdate` constraint exactly; both sides take the default
|
|
53
|
+
* base64url output of a single `digest(...)`, which round-trips cleanly (unlike
|
|
54
|
+
* concatenating several base64url digests).
|
|
55
|
+
*
|
|
56
|
+
* @param partyId - the party whose `CadrePeer` table the record is published into
|
|
57
|
+
* @param peerId - base58btc libp2p peer id (the row key)
|
|
58
|
+
* @param multiaddr - the comma-joined `Multiaddr` string EXACTLY as stored
|
|
59
|
+
* @param updatedAt - epoch-ms freshness stamp
|
|
60
|
+
* @param profile - the publisher's node profile; null signs as `''`, as the constraint coalesces it
|
|
61
|
+
*/
|
|
62
|
+
export function peerRecordSignedPayload(
|
|
63
|
+
partyId: string, peerId: string, multiaddr: string, updatedAt: number, profile: NodeProfile | null
|
|
64
|
+
): string {
|
|
65
|
+
const fields = controlAuthorizationFields('CadreControl.CadrePeer', 'publish', partyId, [peerId, multiaddr, String(updatedAt), profile ?? '']);
|
|
66
|
+
return digest(fields, 'sha256', 'base64url') as string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Sign a peer-address record with the ed25519 private key behind its `peerId`.
|
|
71
|
+
* Returns a fully-populated {@link PeerAddressRecord} (the `addrs` order is
|
|
72
|
+
* preserved and is the order signed over).
|
|
73
|
+
*
|
|
74
|
+
* @param partyId - the party whose `CadrePeer` table the record is published into
|
|
75
|
+
* @param fields - the record fields to sign (signaling addr first by convention)
|
|
76
|
+
* @param privateKeyB64 - base64url ed25519 seed (see `ed25519KeyPairFromLibp2p`)
|
|
77
|
+
*/
|
|
78
|
+
export function signPeerRecord(
|
|
79
|
+
partyId: string,
|
|
80
|
+
fields: Omit<PeerAddressRecord, 'sig'>,
|
|
81
|
+
privateKeyB64: string
|
|
82
|
+
): PeerAddressRecord {
|
|
83
|
+
const payloadDigest = peerRecordSignedPayload(partyId, fields.peerId, fields.addrs.join(','), fields.updatedAt, fields.profile);
|
|
84
|
+
const sig = sign(
|
|
85
|
+
payloadDigest,
|
|
86
|
+
privateKeyB64,
|
|
87
|
+
'ed25519',
|
|
88
|
+
'base64url',
|
|
89
|
+
'base64url',
|
|
90
|
+
'base64url'
|
|
91
|
+
) as string;
|
|
92
|
+
return { peerId: fields.peerId, publicKey: fields.publicKey, addrs: [...fields.addrs], updatedAt: fields.updatedAt, profile: fields.profile, sig };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Verify a record's self-signature against its own `publicKey`, for `partyId`.
|
|
97
|
+
* Reconstructs the signed bytes from the record exactly as {@link signPeerRecord}
|
|
98
|
+
* produced them. Returns false on a missing key/sig or any verification failure.
|
|
99
|
+
*/
|
|
100
|
+
export function verifyPeerRecordSignature(partyId: string, record: PeerAddressRecord): boolean {
|
|
101
|
+
if (!record.publicKey || !record.sig) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
const payloadDigest = peerRecordSignedPayload(partyId, record.peerId, record.addrs.join(','), record.updatedAt, record.profile);
|
|
105
|
+
return verify(
|
|
106
|
+
payloadDigest,
|
|
107
|
+
record.sig,
|
|
108
|
+
record.publicKey,
|
|
109
|
+
'ed25519',
|
|
110
|
+
'base64url',
|
|
111
|
+
'base64url',
|
|
112
|
+
'base64url'
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Freshness predicate: true when `updatedAt` is a positive stamp within
|
|
118
|
+
* `maxAgeMs` of `now`. A non-positive `updatedAt` (never self-published) is
|
|
119
|
+
* never fresh.
|
|
120
|
+
*/
|
|
121
|
+
export function isPeerRecordFresh(updatedAt: number, maxAgeMs: number, now: number): boolean {
|
|
122
|
+
if (!(updatedAt > 0)) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
return now - updatedAt <= maxAgeMs;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** True if `addr` is a `/p2p-circuit` signaling/relay multiaddr. */
|
|
129
|
+
export function isSignalingAddr(addr: string): boolean {
|
|
130
|
+
return addr.includes(SIGNALING_PREFIX);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Return `addrs` with signaling (`/p2p-circuit`) addrs first, otherwise stable.
|
|
135
|
+
* Used to present the WebRTC dial input ahead of direct addrs. Does NOT mutate
|
|
136
|
+
* the input and must not be used before signature verification (verification
|
|
137
|
+
* uses the original on-record order).
|
|
138
|
+
*/
|
|
139
|
+
export function orderSignalingFirst(addrs: string[]): string[] {
|
|
140
|
+
const signaling = addrs.filter(isSignalingAddr);
|
|
141
|
+
const rest = addrs.filter(a => !isSignalingAddr(a));
|
|
142
|
+
return [...signaling, ...rest];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Default resolve trust gate: trust any peer that has a `CadrePeer` row.
|
|
147
|
+
*
|
|
148
|
+
* A row only exists because an owner signed its `AuthorizedInsert`, so row
|
|
149
|
+
* presence already means "owner-vouched member". This is the seam where a
|
|
150
|
+
* stricter policy (trust circle, pinned keys) plugs in — see
|
|
151
|
+
* {@link PeerResolveTrustPolicy}.
|
|
152
|
+
*/
|
|
153
|
+
export function currentMemberTrustPolicy(): PeerResolveTrustPolicy {
|
|
154
|
+
return {
|
|
155
|
+
evaluate() {
|
|
156
|
+
return true;
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|