@serfab/cadre-core 1.13.0 → 1.14.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.
Files changed (120) hide show
  1. package/dist/cadre-invite-protocol.d.ts +348 -0
  2. package/dist/cadre-invite-protocol.js +605 -0
  3. package/dist/cadre-invite-protocol.js.map +1 -0
  4. package/dist/cadre-node.d.ts +523 -180
  5. package/dist/cadre-node.js +921 -293
  6. package/dist/cadre-node.js.map +1 -1
  7. package/dist/claim-proof.d.ts +45 -0
  8. package/dist/claim-proof.js +132 -0
  9. package/dist/claim-proof.js.map +1 -0
  10. package/dist/control-authorization.d.ts +80 -19
  11. package/dist/control-authorization.js +55 -13
  12. package/dist/control-authorization.js.map +1 -1
  13. package/dist/control-cohort.js +1 -1
  14. package/dist/control-cohort.js.map +1 -1
  15. package/dist/control-database.d.ts +514 -97
  16. package/dist/control-database.js +1033 -195
  17. package/dist/control-database.js.map +1 -1
  18. package/dist/control-protocol-guard.d.ts +142 -0
  19. package/dist/control-protocol-guard.js +293 -0
  20. package/dist/control-protocol-guard.js.map +1 -0
  21. package/dist/control-schema.d.ts +1 -1
  22. package/dist/control-schema.js +801 -1067
  23. package/dist/control-schema.js.map +1 -1
  24. package/dist/control-stream.d.ts +4 -0
  25. package/dist/control-stream.js +22 -7
  26. package/dist/control-stream.js.map +1 -1
  27. package/dist/delegate-admission.d.ts +1 -1
  28. package/dist/delegate-admission.js +1 -1
  29. package/dist/device-token.d.ts +11 -9
  30. package/dist/device-token.js +14 -12
  31. package/dist/device-token.js.map +1 -1
  32. package/dist/ed25519-key.d.ts +15 -0
  33. package/dist/ed25519-key.js +28 -1
  34. package/dist/ed25519-key.js.map +1 -1
  35. package/dist/index.d.ts +14 -9
  36. package/dist/index.js +27 -9
  37. package/dist/index.js.map +1 -1
  38. package/dist/link-budget.d.ts +16 -29
  39. package/dist/link-budget.js +16 -31
  40. package/dist/link-budget.js.map +1 -1
  41. package/dist/membership-connection-gater.d.ts +190 -151
  42. package/dist/membership-connection-gater.js +235 -173
  43. package/dist/membership-connection-gater.js.map +1 -1
  44. package/dist/node-claim-payload.d.ts +19 -0
  45. package/dist/node-claim-payload.js +120 -0
  46. package/dist/node-claim-payload.js.map +1 -0
  47. package/dist/owner-anchor-sync.d.ts +22 -0
  48. package/dist/owner-anchor-sync.js +143 -0
  49. package/dist/owner-anchor-sync.js.map +1 -0
  50. package/dist/peer-authorization.d.ts +113 -18
  51. package/dist/peer-authorization.js +225 -29
  52. package/dist/peer-authorization.js.map +1 -1
  53. package/dist/peer-dial.d.ts +19 -0
  54. package/dist/peer-dial.js +22 -1
  55. package/dist/peer-dial.js.map +1 -1
  56. package/dist/peer-join-backfill.d.ts +26 -2
  57. package/dist/peer-join-backfill.js +54 -12
  58. package/dist/peer-join-backfill.js.map +1 -1
  59. package/dist/peer-record.d.ts +13 -9
  60. package/dist/peer-record.js +16 -12
  61. package/dist/peer-record.js.map +1 -1
  62. package/dist/pending-join-runner.d.ts +66 -58
  63. package/dist/pending-join-runner.js +136 -145
  64. package/dist/pending-join-runner.js.map +1 -1
  65. package/dist/push-fanout.d.ts +8 -2
  66. package/dist/push-fanout.js +8 -2
  67. package/dist/push-fanout.js.map +1 -1
  68. package/dist/seed-bootstrap.d.ts +147 -122
  69. package/dist/seed-bootstrap.js +284 -269
  70. package/dist/seed-bootstrap.js.map +1 -1
  71. package/dist/seed-trust-policy.d.ts +98 -37
  72. package/dist/seed-trust-policy.js +188 -2
  73. package/dist/seed-trust-policy.js.map +1 -1
  74. package/dist/strand-addr-protocol.d.ts +24 -26
  75. package/dist/strand-addr-protocol.js +19 -26
  76. package/dist/strand-addr-protocol.js.map +1 -1
  77. package/dist/strand-formation-protocol.js +1 -1
  78. package/dist/strand-formation-protocol.js.map +1 -1
  79. package/dist/strand-revocation-enforcer.d.ts +11 -11
  80. package/dist/strand-revocation-enforcer.js +11 -11
  81. package/dist/strand-wake-protocol.d.ts +13 -16
  82. package/dist/strand-wake-protocol.js +13 -20
  83. package/dist/strand-wake-protocol.js.map +1 -1
  84. package/dist/trusted-owner-store.d.ts +35 -5
  85. package/dist/trusted-owner-store.js +50 -8
  86. package/dist/trusted-owner-store.js.map +1 -1
  87. package/dist/types.d.ts +302 -87
  88. package/dist/types.js.map +1 -1
  89. package/package.json +8 -7
  90. package/src/cadre-invite-protocol.ts +864 -0
  91. package/src/cadre-node.ts +9381 -8686
  92. package/src/claim-proof.ts +147 -0
  93. package/src/control-authorization.ts +203 -117
  94. package/src/control-cohort.ts +1 -1
  95. package/src/control-database.ts +1216 -218
  96. package/src/control-protocol-guard.ts +348 -0
  97. package/src/control-schema.ts +812 -1078
  98. package/src/control-stream.ts +26 -7
  99. package/src/delegate-admission.ts +1 -1
  100. package/src/device-token.ts +14 -10
  101. package/src/ed25519-key.ts +28 -1
  102. package/src/index.ts +84 -10
  103. package/src/link-budget.ts +16 -32
  104. package/src/membership-connection-gater.ts +637 -570
  105. package/src/node-claim-payload.ts +125 -0
  106. package/src/owner-anchor-sync.ts +171 -0
  107. package/src/peer-authorization.ts +258 -29
  108. package/src/peer-dial.ts +22 -1
  109. package/src/peer-join-backfill.ts +56 -11
  110. package/src/peer-record.ts +16 -11
  111. package/src/pending-join-runner.ts +729 -735
  112. package/src/push-fanout.ts +8 -2
  113. package/src/seed-bootstrap.ts +344 -321
  114. package/src/seed-trust-policy.ts +353 -140
  115. package/src/strand-addr-protocol.ts +26 -35
  116. package/src/strand-formation-protocol.ts +1 -1
  117. package/src/strand-revocation-enforcer.ts +11 -11
  118. package/src/strand-wake-protocol.ts +13 -23
  119. package/src/trusted-owner-store.ts +242 -171
  120. package/src/types.ts +2471 -2242
@@ -1,15 +1,22 @@
1
1
  import debug from 'debug';
2
2
  import { digest, verify } from '@optimystic/quereus-plugin-crypto';
3
- import { controlAuthorizationFields } from './control-authorization.js';
3
+ import { controlAuthorizationFields, unboundAuthorizationFields, cadreInviteRowFields } from './control-authorization.js';
4
+ import { ed25519PublicKeyB64FromPeerId } from './ed25519-key.js';
4
5
  const log = debug('sereus:cadre:peer-authorization');
5
6
  /**
6
- * base64url SHA-256 digest over the shared domain-tagged field vector (see
7
+ * base64url SHA-256 digest over the shared domain-tagged, party-bound field vector (see
7
8
  * control-authorization.ts). The base64url twin of
8
9
  * control-database.ts:buildAuthorizationMessage (which returns the same digest as raw
9
10
  * bytes): sign either encoding with input encoding to match and the signed bytes agree.
11
+ * `partyId` is the party the verifier is configured for: a stored proof minted for
12
+ * another party fails here exactly as it fails the schema's `party_id()`-bound check.
10
13
  */
11
- function taggedDigest(domain, action, rowFields) {
12
- return digest(controlAuthorizationFields(domain, action, rowFields), 'sha256', 'base64url');
14
+ function taggedDigest(domain, action, partyId, rowFields) {
15
+ return digest(controlAuthorizationFields(domain, action, partyId, rowFields), 'sha256', 'base64url');
16
+ }
17
+ /** {@link taggedDigest} for the two party-free digests (`UnboundControlDomain`). */
18
+ function unboundTaggedDigest(domain, action, rowFields) {
19
+ return digest(unboundAuthorizationFields(domain, action, rowFields), 'sha256', 'base64url');
13
20
  }
14
21
  /**
15
22
  * Canonical digest an owner signs to vouch a peer's ENROLLMENT — the offline
@@ -23,12 +30,12 @@ function taggedDigest(domain, action, rowFields) {
23
30
  * here and both move together.
24
31
  */
25
32
  export function peerAuthorizationDigest(peerId) {
26
- return taggedDigest('Cadre.Enrollment', 'vouch', [peerId]);
33
+ return unboundTaggedDigest('Cadre.Enrollment', 'vouch', [peerId]);
27
34
  }
28
35
  /**
29
36
  * Canonical digest an owner signs to authorize a `DeviceToken` INSERT — the WHOLE row,
30
37
  * ending in its single-use `StampId` nonce. SQL mirror:
31
- * `digest('CadreControl.DeviceToken', 'add', new.PeerId, new.Platform, new.Token,
38
+ * `digest('CadreControl.DeviceToken', 'add', party_id(), new.PeerId, new.Platform, new.Token,
32
39
  * coalesce(cast(new.UpdatedAt as text), ''), coalesce(new.Sig, ''), new.StampId)` in
33
40
  * `DeviceToken.AuthorizedInsert`.
34
41
  *
@@ -44,8 +51,8 @@ export function peerAuthorizationDigest(peerId) {
44
51
  * Distinct from {@link deviceTokenRemoveDigest} so a captured insert approval can never
45
52
  * be replayed to delete the token, and vice versa.
46
53
  */
47
- export function deviceTokenAddDigest(row) {
48
- return taggedDigest('CadreControl.DeviceToken', 'add', [
54
+ export function deviceTokenAddDigest(partyId, row) {
55
+ return taggedDigest('CadreControl.DeviceToken', 'add', partyId, [
49
56
  row.peerId,
50
57
  row.platform,
51
58
  row.token,
@@ -57,7 +64,7 @@ export function deviceTokenAddDigest(row) {
57
64
  /**
58
65
  * Canonical digest an owner signs to authorize a `DeviceToken` DELETE, bound to the
59
66
  * STORED row's (PeerId, StampId). SQL mirror:
60
- * `digest('CadreControl.DeviceToken', 'remove', old.PeerId, old.StampId)` in
67
+ * `digest('CadreControl.DeviceToken', 'remove', party_id(), old.PeerId, old.StampId)` in
61
68
  * `DeviceToken.AuthorizedDelete`.
62
69
  *
63
70
  * A narrower vector than {@link deviceTokenAddDigest} on purpose (the same split
@@ -65,8 +72,8 @@ export function deviceTokenAddDigest(row) {
65
72
  * cannot be re-cut into an insert approval, and it is dead the moment the stamp it
66
73
  * names is tombstoned.
67
74
  */
68
- export function deviceTokenRemoveDigest(peerId, stampId) {
69
- return taggedDigest('CadreControl.DeviceToken', 'remove', [peerId, stampId]);
75
+ export function deviceTokenRemoveDigest(partyId, peerId, stampId) {
76
+ return taggedDigest('CadreControl.DeviceToken', 'remove', partyId, [peerId, stampId]);
70
77
  }
71
78
  /**
72
79
  * Canonical digest an owner signs to VOUCH a `CadrePeer` membership row (insert
@@ -79,10 +86,10 @@ export function deviceTokenRemoveDigest(peerId, stampId) {
79
86
  * a delete. The domain tag keeps the stored, replicated `VouchSig` useless against
80
87
  * every OTHER table's rules.
81
88
  *
82
- * SQL mirror: `digest('CadreControl.CadrePeer', 'vouch', new.PeerId, new.StampId)`.
89
+ * SQL mirror: `digest('CadreControl.CadrePeer', 'vouch', party_id(), new.PeerId, new.StampId)`.
83
90
  */
84
- export function cadrePeerVoucherDigest(peerId, stampId) {
85
- return taggedDigest('CadreControl.CadrePeer', 'vouch', [peerId, stampId]);
91
+ export function cadrePeerVoucherDigest(partyId, peerId, stampId) {
92
+ return taggedDigest('CadreControl.CadrePeer', 'vouch', partyId, [peerId, stampId]);
86
93
  }
87
94
  /**
88
95
  * Canonical digest an owner signs to REMOVE a `CadrePeer` row. Deliberately a
@@ -93,10 +100,10 @@ export function cadrePeerVoucherDigest(peerId, stampId) {
93
100
  * delete lands, because a re-added row carries a FRESH `StampId` (the removed row's
94
101
  * stamp is retired into `CadreControl.Revocation` and never reused).
95
102
  *
96
- * SQL mirror: `digest('CadreControl.CadrePeer', 'remove', old.PeerId, old.StampId)`.
103
+ * SQL mirror: `digest('CadreControl.CadrePeer', 'remove', party_id(), old.PeerId, old.StampId)`.
97
104
  */
98
- export function cadrePeerRemoveDigest(peerId, stampId) {
99
- return taggedDigest('CadreControl.CadrePeer', 'remove', [peerId, stampId]);
105
+ export function cadrePeerRemoveDigest(partyId, peerId, stampId) {
106
+ return taggedDigest('CadreControl.CadrePeer', 'remove', partyId, [peerId, stampId]);
100
107
  }
101
108
  /**
102
109
  * Canonical digest an owner signs to APPEND a `CadreControl.Revocation` tombstone —
@@ -104,7 +111,7 @@ export function cadrePeerRemoveDigest(peerId, stampId) {
104
111
  * (the removed row's primary key: OwnerKey.Key / ValidationKey.Key /
105
112
  * CadrePeer.PeerId / DeviceToken.PeerId / Strand.Id) as which row was retired.
106
113
  * SQL mirror:
107
- * `digest('CadreControl.Revocation', 'remove', new.TableName, new.RowKey, new.StampId)`
114
+ * `digest('CadreControl.Revocation', 'remove', party_id(), new.TableName, new.RowKey, new.StampId)`
108
115
  * in `Revocation.Authorized`.
109
116
  *
110
117
  * Its own `'CadreControl.Revocation'` domain tag makes it disjoint from the
@@ -113,8 +120,47 @@ export function cadrePeerRemoveDigest(peerId, stampId) {
113
120
  * this tombstone accompanies — a removal signature is not a retirement
114
121
  * signature and cannot be replayed as one.
115
122
  */
116
- export function revocationDigest(tableName, rowKey, stampId) {
117
- return taggedDigest('CadreControl.Revocation', 'remove', [tableName, rowKey, stampId]);
123
+ export function revocationDigest(partyId, tableName, rowKey, stampId) {
124
+ return taggedDigest('CadreControl.Revocation', 'remove', partyId, [tableName, rowKey, stampId]);
125
+ }
126
+ /**
127
+ * Does a `Revocation` row's stored signer pair (`SignerKey` / `SignerSig`) verify over the
128
+ * tombstone's own triple, for `partyId`? The read-side mirror of `Revocation.Authorized`,
129
+ * for a node that received the row by replication and must decide whether to honour it.
130
+ * Whether `row.signerKey` is an owner this node trusts is the caller's question, as for
131
+ * {@link verifyCadrePeerVoucher}. Never throws: a malformed row verifies as `false`.
132
+ */
133
+ export function verifyRevocationSigner(partyId, row) {
134
+ try {
135
+ return verifyB64(revocationDigest(partyId, row.tableName, row.rowKey, row.stampId), row.signerSig, row.signerKey);
136
+ }
137
+ catch (error) {
138
+ log('verifyRevocationSigner failed: %o', error);
139
+ return false;
140
+ }
141
+ }
142
+ /**
143
+ * Canonical digest an owner signs to seat a further `OwnerKey` row — the signed-add branch
144
+ * of `OwnerKey.Authorized`, which stores the pair as `VouchOwner` / `VouchSig`. SQL mirror:
145
+ * `digest('CadreControl.OwnerKey', 'add', party_id(), new.Key, new.StampId)`. The removal
146
+ * digest needs no builder here: it is the generic guarded `'remove'` digest over (Key, StampId).
147
+ */
148
+ export function ownerKeyAddDigest(partyId, key, stampId) {
149
+ return taggedDigest('CadreControl.OwnerKey', 'add', partyId, [key, stampId]);
150
+ }
151
+ /**
152
+ * Does an owner-signed `OwnerKey` row's stored voucher verify — `vouchSig` by `vouchOwner`
153
+ * over {@link ownerKeyAddDigest} for (`partyId`, `key`, `stampId`)? Whether `vouchOwner` is
154
+ * anchored is the caller's question, as for {@link verifyCadrePeerVoucher}. Never throws.
155
+ */
156
+ export function verifyOwnerKeyVoucher(partyId, key, stampId, vouchOwner, vouchSig) {
157
+ try {
158
+ return verifyB64(ownerKeyAddDigest(partyId, key, stampId), vouchSig, vouchOwner);
159
+ }
160
+ catch (error) {
161
+ log('verifyOwnerKeyVoucher failed: %o', error);
162
+ return false;
163
+ }
118
164
  }
119
165
  /**
120
166
  * Verify that `signature` is a valid owner ed25519 signature over `peerId`'s
@@ -141,12 +187,13 @@ export function verifyPeerAuthorization(peerId, ownerPublicKey, signature) {
141
187
  }
142
188
  /**
143
189
  * Verify that `signature` is a valid owner ed25519 signature over the
144
- * `CadrePeer` voucher digest for (`peerId`, `stampId`) — the read-side mirror
145
- * of the voucher {@link ControlDatabase.insertCadrePeer} signs and persists into
190
+ * `CadrePeer` voucher digest for (`partyId`, `peerId`, `stampId`) — the read-side
191
+ * mirror of the voucher {@link ControlDatabase.insertCadrePeer} signs and persists into
146
192
  * `VouchOwner`/`VouchSig` (see {@link cadrePeerVoucherDigest}).
147
193
  *
148
194
  * A `true` result means the holder of `ownerPublicKey` vouched THIS membership
149
- * row (the peer id bound to the row's single-use `StampId` nonce). It says
195
+ * row (the peer id bound to the row's single-use `StampId` nonce) in THIS party: a
196
+ * voucher copied from another party's table that shares the owner key fails. It says
150
197
  * nothing about whether that owner key is itself trustworthy — the caller must
151
198
  * separately check the key against the node-local trusted-owner anchor
152
199
  * (`TrustedOwnerStore`), never the replicated `OwnerKey` table.
@@ -155,9 +202,9 @@ export function verifyPeerAuthorization(peerId, ownerPublicKey, signature) {
155
202
  * {@link verifyPeerAuthorization}): malformed input or any crypto failure
156
203
  * resolves to `false`, logged at debug.
157
204
  */
158
- export function verifyCadrePeerVoucher(peerId, stampId, ownerPublicKey, signature) {
205
+ export function verifyCadrePeerVoucher(partyId, peerId, stampId, ownerPublicKey, signature) {
159
206
  try {
160
- return verify(cadrePeerVoucherDigest(peerId, stampId), signature, ownerPublicKey, 'ed25519', 'base64url', 'base64url', 'base64url');
207
+ return verify(cadrePeerVoucherDigest(partyId, peerId, stampId), signature, ownerPublicKey, 'ed25519', 'base64url', 'base64url', 'base64url');
161
208
  }
162
209
  catch (error) {
163
210
  log('verifyCadrePeerVoucher failed: %o', error);
@@ -168,12 +215,12 @@ export function verifyCadrePeerVoucher(peerId, stampId, ownerPublicKey, signatur
168
215
  * Canonical digest a JOINING peer signs to consent to ONE `FormationUsage`
169
216
  * redemption — the read-side mirror of `formationConsentMessage` in
170
217
  * control-database.ts, base64url-encoded instead of raw bytes (see
171
- * {@link taggedDigest}). That doc comment carries the field-vector rationale
172
- * (notably why `strandId` is not signed); the two vectors must not drift, which
173
- * peer-authorization.spec.ts pins by signing one form and verifying the other.
218
+ * {@link unboundTaggedDigest}). That doc comment carries the field-vector rationale
219
+ * (notably why neither `strandId` nor the party id is signed); the two vectors must not
220
+ * drift, which peer-authorization.spec.ts pins by signing one form and verifying the other.
174
221
  */
175
222
  export function formationConsentDigest(token, usageStampId, peerKey, disclosure) {
176
- return taggedDigest('CadreControl.FormationUsage', 'consent', [token, usageStampId, peerKey, disclosure]);
223
+ return unboundTaggedDigest('CadreControl.FormationUsage', 'consent', [token, usageStampId, peerKey, disclosure]);
177
224
  }
178
225
  /**
179
226
  * Verify that `row.peerSig` is a valid ed25519 signature over the joining peer's
@@ -194,4 +241,153 @@ export function verifyFormationConsent(row) {
194
241
  return false;
195
242
  }
196
243
  }
244
+ /**
245
+ * Canonical digest an owner signs to seat a `CadreInvite` row — the read-side mirror
246
+ * of `cadreInviteAddMessage` in control-database.ts, base64url instead of raw bytes
247
+ * (see {@link taggedDigest}). The whole row is bound, nullable columns as `''` and
248
+ * `ExpiresAt` in its stored canonical form, through the one field builder both sides
249
+ * share (`cadreInviteRowFields`). SQL mirror: `CadreInvite.AuthorizedInsert`.
250
+ */
251
+ export function cadreInviteAddDigest(partyId, row) {
252
+ return taggedDigest('CadreControl.CadreInvite', 'add', partyId, cadreInviteRowFields(row));
253
+ }
254
+ /**
255
+ * Canonical digest the HOLDER of a `CadreInvite`'s private key signs to redeem it once:
256
+ * proof of possession, verified against the invitation key itself
257
+ * (`CadreInviteUsage.InvitePossessed`). Binds the redemption's single-use nonce and the
258
+ * device being admitted, so one redemption cannot be re-presented for another device
259
+ * or another use. Mirror of `cadreInviteRedeemMessage` in control-database.ts.
260
+ */
261
+ export function cadreInviteRedeemDigest(partyId, inviteKey, usageStampId, peerKey) {
262
+ return taggedDigest('CadreControl.CadreInviteUsage', 'redeem', partyId, [inviteKey, usageStampId, peerKey]);
263
+ }
264
+ /**
265
+ * Canonical digest the DEVICE signs to consent to being admitted by one redemption
266
+ * (`CadreInviteUsage.PeerConsented`), over the same fields as
267
+ * {@link cadreInviteRedeemDigest} under a distinct action tag, so the holder's and
268
+ * the device's signatures are never interchangeable. Mirror of
269
+ * `cadreInviteConsentMessage` in control-database.ts.
270
+ */
271
+ export function cadreInviteConsentDigest(partyId, inviteKey, usageStampId, peerKey) {
272
+ return taggedDigest('CadreControl.CadreInviteUsage', 'consent', partyId, [inviteKey, usageStampId, peerKey]);
273
+ }
274
+ /** ed25519 verify over a base64url digest, with the siblings' never-throws contract left to the caller. */
275
+ function verifyB64(digestB64, signature, publicKey) {
276
+ return verify(digestB64, signature, publicKey, 'ed25519', 'base64url', 'base64url', 'base64url');
277
+ }
278
+ /**
279
+ * Does a `CadreInvite` row carry a valid `'add'` signature by its own `issuerKey`, for
280
+ * `partyId`? The redeeming device runs this over the row a member replies with, after
281
+ * checking the issuer against its pinned owner keys and the reply's party against the
282
+ * invitation's, and a member over a bundle's copy it does not hold yet
283
+ * (`ControlDatabase.isCadreInviteLive`); whether that issuer IS an owner is the caller's question.
284
+ * Never throws: a malformed row verifies as `false`.
285
+ */
286
+ export function verifyCadreInviteRow(partyId, invite) {
287
+ try {
288
+ return verifyB64(cadreInviteAddDigest(partyId, invite), invite.issuerSig, invite.issuerKey);
289
+ }
290
+ catch (error) {
291
+ log('verifyCadreInviteRow failed: %o', error);
292
+ return false;
293
+ }
294
+ }
295
+ /**
296
+ * The two signatures a redemption carries, checked before any database work: the holder's
297
+ * `'redeem'` signature with the invitation key, and the device's `'consent'` signature with
298
+ * its own key, both over (`inviteKey`, `usageStampId`, `peerKey`). The pre-check twin of
299
+ * `CadreInviteUsage.InvitePossessed` / `PeerConsented`, as `verifyFormationConsent` is for
300
+ * formation: the schema stays the authority, this turns a bad signature into a clean
301
+ * refusal instead of a constraint failure at commit. Never throws.
302
+ */
303
+ export function verifyCadreInviteRedemption(partyId, fields, inviteSig, peerSig) {
304
+ try {
305
+ return verifyB64(cadreInviteRedeemDigest(partyId, fields.inviteKey, fields.usageStampId, fields.peerKey), inviteSig, fields.inviteKey)
306
+ && verifyB64(cadreInviteConsentDigest(partyId, fields.inviteKey, fields.usageStampId, fields.peerKey), peerSig, fields.peerKey);
307
+ }
308
+ catch (error) {
309
+ log('verifyCadreInviteRedemption failed: %o', error);
310
+ return false;
311
+ }
312
+ }
313
+ /**
314
+ * The links of an invitation admission that do not depend on WHICH table's row was
315
+ * admitted — shared by {@link verifyInvitationAdmission} (`CadrePeer`) and
316
+ * {@link verifyInvitationOwnerAdmission} (`OwnerKey`), which differ only in the row
317
+ * columns the usage must name and, for an owner row, in `grantsOwner`. The chain is
318
+ *
319
+ * row --vouchUsage--> usage --inviteKey--> invitation --issuerKey--> anchored owner
320
+ *
321
+ * and every link here is verified: the issuer is anchored (`isAnchored`) and is the row's
322
+ * `vouchOwner`; the usage names this invitation and is the row's `vouchUsage`; a targeted
323
+ * invitation names the redeeming device; the stored `peerKey` really is the key behind the
324
+ * usage's `peerId` (the schema cannot unwrap a multihash, so a writer could assert any
325
+ * pair); the invitation's stored `'add'` signature verifies over the row rebuilt from its
326
+ * columns; the holder's `'redeem'` signature verifies with the invitation key and the
327
+ * device's `'consent'` signature with its own key. All three signatures are judged for
328
+ * `partyId`. Throws on malformed input; the two callers turn that into `false`.
329
+ */
330
+ function verifyAdmissionChain(partyId, vouchOwner, vouchUsage, usage, invite, isAnchored) {
331
+ return isAnchored(vouchOwner)
332
+ && invite.issuerKey === vouchOwner
333
+ && usage.usageStampId === vouchUsage
334
+ && usage.inviteKey === invite.key
335
+ && (invite.peerId === null || invite.peerId === usage.peerId)
336
+ && ed25519PublicKeyB64FromPeerId(usage.peerId) === usage.peerKey
337
+ && verifyB64(cadreInviteAddDigest(partyId, invite), invite.issuerSig, invite.issuerKey)
338
+ && verifyB64(cadreInviteRedeemDigest(partyId, invite.key, usage.usageStampId, usage.peerKey), usage.inviteSig, invite.key)
339
+ && verifyB64(cadreInviteConsentDigest(partyId, invite.key, usage.usageStampId, usage.peerKey), usage.peerSig, usage.peerKey);
340
+ }
341
+ /**
342
+ * Is an invitation-admitted `CadrePeer` row (`vouchSig` null, `vouchUsage` set) a
343
+ * member this node should trust? The read-side mirror of the consent branch of
344
+ * `CadrePeer.AuthorizedInsert`, re-checked against THIS node's anchor because the
345
+ * replicated `OwnerKey` table can be polluted. The usage must name this peer id and this
346
+ * exact row incarnation (`peerStampId`); every other link is {@link verifyAdmissionChain}.
347
+ *
348
+ * Expiry, use count and withdrawal are deliberately NOT re-checked: they were conditions
349
+ * at redemption (`CadreInviteUsage.Authorized`), and membership persists until an owner
350
+ * removes the row — exactly as an owner-vouched row's membership does. Keeping a device
351
+ * out is removing its row AND withdrawing the invitation.
352
+ *
353
+ * Returns a boolean and never throws (the siblings' contract): malformed input or any
354
+ * crypto failure resolves to `false`, logged at debug.
355
+ */
356
+ export function verifyInvitationAdmission(partyId, row, usage, invite, isAnchored) {
357
+ try {
358
+ return row.stampId !== null
359
+ && row.vouchOwner !== null
360
+ && row.vouchUsage !== null
361
+ && usage.peerStampId === row.stampId
362
+ && usage.peerId === row.peerId
363
+ && verifyAdmissionChain(partyId, row.vouchOwner, row.vouchUsage, usage, invite, isAnchored);
364
+ }
365
+ catch (error) {
366
+ log('verifyInvitationAdmission failed: %o', error);
367
+ return false;
368
+ }
369
+ }
370
+ /**
371
+ * Is an invitation-admitted `OwnerKey` row (`vouchSig` null, `vouchUsage` set) an owner
372
+ * this node should derive into its anchor? The read-side mirror of the consent branch of
373
+ * `OwnerKey.Authorized` (`owner-anchor-sync.ts` → `deriveOwnerAnchor`): the invitation
374
+ * must grant ownership, and the usage must name this key (`peerKey`) and this exact row
375
+ * incarnation (`ownerStampId`); every other link is {@link verifyAdmissionChain}. Same
376
+ * never-throws contract and the same deliberate omissions as
377
+ * {@link verifyInvitationAdmission}.
378
+ */
379
+ export function verifyInvitationOwnerAdmission(partyId, row, usage, invite, isAnchored) {
380
+ try {
381
+ return row.vouchOwner !== null
382
+ && row.vouchUsage !== null
383
+ && invite.grantsOwner
384
+ && usage.peerKey === row.key
385
+ && usage.ownerStampId === row.stampId
386
+ && verifyAdmissionChain(partyId, row.vouchOwner, row.vouchUsage, usage, invite, isAnchored);
387
+ }
388
+ catch (error) {
389
+ log('verifyInvitationOwnerAdmission failed: %o', error);
390
+ return false;
391
+ }
392
+ }
197
393
  //# sourceMappingURL=peer-authorization.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"peer-authorization.js","sourceRoot":"","sources":["../src/peer-authorization.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"peer-authorization.js","sourceRoot":"","sources":["../src/peer-authorization.ts"],"names":[],"mappings":"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"}
@@ -124,6 +124,19 @@ export interface AddrDialer {
124
124
  export declare class SelfRelayOnlyError extends Error {
125
125
  constructor(label: string);
126
126
  }
127
+ /**
128
+ * No connection to the peer formed, so nothing was sent to it. `cause` is what the dial threw:
129
+ * {@link dialPeerAddrs}'s error naming every address and why it failed, or a
130
+ * {@link SelfRelayOnlyError}.
131
+ *
132
+ * Kept apart from failures after a connection formed so a caller can tell "this device could not
133
+ * reach the peer" from "the peer was reached and then something else failed" without reading
134
+ * libp2p's error text. `SeedBootstrapService.deliverSeed` throws it, and so `CadreNode.claimNode`.
135
+ */
136
+ export declare class PeerUnreachableError extends Error {
137
+ readonly peerId: string;
138
+ constructor(peerId: string, cause: unknown);
139
+ }
127
140
  /**
128
141
  * Dial one peer from `addrs`, one address per `dial()` call, and return the
129
142
  * first connection that forms. Direct addresses are tried before relayed ones
@@ -184,3 +197,9 @@ export declare function tryAddrsInTurn<T>(addrs: readonly Multiaddr[], budget: P
184
197
  * bounded amount of time, so the caller's order is the better guide.
185
198
  */
186
199
  export declare function directBeforeRelayed(addrs: readonly Multiaddr[]): Multiaddr[];
200
+ /**
201
+ * Yield one macrotask: libp2p's dial queue drops an aborted job only on the next turn, so a
202
+ * dialer that moves on to another address after an abort waits this long first. Shared with
203
+ * the cadre invitation dialer (`cadre-invite-protocol.ts`).
204
+ */
205
+ export declare function nextMacrotask(): Promise<void>;
package/dist/peer-dial.js CHANGED
@@ -118,6 +118,22 @@ export class SelfRelayOnlyError extends Error {
118
118
  this.name = 'SelfRelayOnlyError';
119
119
  }
120
120
  }
121
+ /**
122
+ * No connection to the peer formed, so nothing was sent to it. `cause` is what the dial threw:
123
+ * {@link dialPeerAddrs}'s error naming every address and why it failed, or a
124
+ * {@link SelfRelayOnlyError}.
125
+ *
126
+ * Kept apart from failures after a connection formed so a caller can tell "this device could not
127
+ * reach the peer" from "the peer was reached and then something else failed" without reading
128
+ * libp2p's error text. `SeedBootstrapService.deliverSeed` throws it, and so `CadreNode.claimNode`.
129
+ */
130
+ export class PeerUnreachableError extends Error {
131
+ constructor(peerId, cause) {
132
+ super(`Could not reach ${peerId}: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
133
+ this.peerId = peerId;
134
+ this.name = 'PeerUnreachableError';
135
+ }
136
+ }
121
137
  /**
122
138
  * Dial one peer from `addrs`, one address per `dial()` call, and return the
123
139
  * first connection that forms. Direct addresses are tried before relayed ones
@@ -263,7 +279,12 @@ function allAttemptsFailed(label, failures) {
263
279
  const detail = failures.map((f) => `${f.addr.toString()} — ${f.error.message}`).join('; ');
264
280
  return new Error(`${label} failed for all ${failures.length} candidate addresses: ${detail}`, { cause: failures[0].error });
265
281
  }
266
- function nextMacrotask() {
282
+ /**
283
+ * Yield one macrotask: libp2p's dial queue drops an aborted job only on the next turn, so a
284
+ * dialer that moves on to another address after an abort waits this long first. Shared with
285
+ * the cadre invitation dialer (`cadre-invite-protocol.ts`).
286
+ */
287
+ export function nextMacrotask() {
267
288
  return new Promise((resolve) => setTimeout(resolve, 0));
268
289
  }
269
290
  //# sourceMappingURL=peer-dial.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"peer-dial.js","sourceRoot":"","sources":["../src/peer-dial.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"peer-dial.js","sourceRoot":"","sources":["../src/peer-dial.ts"],"names":[],"mappings":"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"}
@@ -118,9 +118,15 @@ export declare class PeerJoinBackfill {
118
118
  private readonly createPushClient;
119
119
  private started;
120
120
  private stopped;
121
- /** Peers fully caught up this runtime (never retried until the runtime is rebuilt). */
121
+ /**
122
+ * Peers fully caught up this runtime (never rescheduled until the runtime is rebuilt; only
123
+ * {@link forceCatchUpPeer} pushes to one again).
124
+ */
122
125
  private readonly done;
123
- /** Peers with a catch-up currently running (suppresses concurrent duplicates). */
126
+ /**
127
+ * Peers with a catch-up currently running, each with that run (suppresses concurrent
128
+ * duplicates; {@link forceCatchUpPeer} waits on the run).
129
+ */
124
130
  private readonly inFlight;
125
131
  /**
126
132
  * Peers whose (re-)schedule arrived while their OWN run was in flight, replayed once
@@ -179,6 +185,24 @@ export declare class PeerJoinBackfill {
179
185
  * caught up, or this backfill is stopped.
180
186
  */
181
187
  catchUpPeer(peerId: PeerId): Promise<PeerJoinBackfillResult>;
188
+ /**
189
+ * Run one peer's catch-up now and wait for it, whatever this instance remembers about the
190
+ * peer: wait out a run already in flight for it, then push the whole store again even when
191
+ * the peer is memoized as caught up. Never rejects; an all-zero result only when stopped.
192
+ *
193
+ * For a caller that must know the peer holds this store before it proceeds, where
194
+ * {@link catchUpPeer}'s empty answer would hide a run that was denied (a `peer:identify`
195
+ * run can pass the gate a moment before the caller authorized the peer) or a memo older
196
+ * than the commits since.
197
+ */
198
+ forceCatchUpPeer(peerId: PeerId): Promise<PeerJoinBackfillResult>;
199
+ /**
200
+ * Register one run in {@link inFlight} before its first await, so no second run for the
201
+ * peer can start beside it, and replay a schedule that arrived while it ran.
202
+ */
203
+ private trackRun;
204
+ /** One run and what follows from its outcome: the memo, or a backoff re-arm. Never rejects. */
205
+ private settleRun;
182
206
  /** Whether this network's libp2p node still holds a connection to that peer. */
183
207
  private isConnected;
184
208
  /**
@@ -195,10 +195,16 @@ export class PeerJoinBackfill {
195
195
  this.deps = deps;
196
196
  this.started = false;
197
197
  this.stopped = false;
198
- /** Peers fully caught up this runtime (never retried until the runtime is rebuilt). */
198
+ /**
199
+ * Peers fully caught up this runtime (never rescheduled until the runtime is rebuilt; only
200
+ * {@link forceCatchUpPeer} pushes to one again).
201
+ */
199
202
  this.done = new Set();
200
- /** Peers with a catch-up currently running (suppresses concurrent duplicates). */
201
- this.inFlight = new Set();
203
+ /**
204
+ * Peers with a catch-up currently running, each with that run (suppresses concurrent
205
+ * duplicates; {@link forceCatchUpPeer} waits on the run).
206
+ */
207
+ this.inFlight = new Map();
202
208
  /**
203
209
  * Peers whose (re-)schedule arrived while their OWN run was in flight, replayed once
204
210
  * that run finishes. Load-bearing for the gated path: the gate's authorization check is
@@ -342,7 +348,51 @@ export class PeerJoinBackfill {
342
348
  if (this.stopped || this.done.has(key) || this.inFlight.has(key)) {
343
349
  return emptyResult();
344
350
  }
345
- this.inFlight.add(key);
351
+ return await this.trackRun(peerId);
352
+ }
353
+ /**
354
+ * Run one peer's catch-up now and wait for it, whatever this instance remembers about the
355
+ * peer: wait out a run already in flight for it, then push the whole store again even when
356
+ * the peer is memoized as caught up. Never rejects; an all-zero result only when stopped.
357
+ *
358
+ * For a caller that must know the peer holds this store before it proceeds, where
359
+ * {@link catchUpPeer}'s empty answer would hide a run that was denied (a `peer:identify`
360
+ * run can pass the gate a moment before the caller authorized the peer) or a memo older
361
+ * than the commits since.
362
+ */
363
+ async forceCatchUpPeer(peerId) {
364
+ const key = peerId.toString();
365
+ for (let pending = this.inFlight.get(key); pending; pending = this.inFlight.get(key)) {
366
+ await pending;
367
+ }
368
+ if (this.stopped) {
369
+ return emptyResult();
370
+ }
371
+ return await this.trackRun(peerId);
372
+ }
373
+ /**
374
+ * Register one run in {@link inFlight} before its first await, so no second run for the
375
+ * peer can start beside it, and replay a schedule that arrived while it ran.
376
+ */
377
+ async trackRun(peerId) {
378
+ const key = peerId.toString();
379
+ const run = this.settleRun(peerId);
380
+ this.inFlight.set(key, run);
381
+ try {
382
+ return await run;
383
+ }
384
+ finally {
385
+ this.inFlight.delete(key);
386
+ // Replay a schedule that arrived mid-run. `schedulePeer` re-checks `done`, so a run
387
+ // that finished clean re-arms nothing; a denied or failed one gets its retry.
388
+ if (this.rearmAfterFlight.delete(key)) {
389
+ this.schedulePeer(peerId);
390
+ }
391
+ }
392
+ }
393
+ /** One run and what follows from its outcome: the memo, or a backoff re-arm. Never rejects. */
394
+ async settleRun(peerId) {
395
+ const key = peerId.toString();
346
396
  try {
347
397
  const { result, clean, pushFailed } = await this.runCatchUp(peerId);
348
398
  // NOTE: a run that hit `maxBlocks` is still "clean" and still memoizes the peer, so
@@ -375,14 +425,6 @@ export class PeerJoinBackfill {
375
425
  log('[%s] catch-up peer=%s failed: %o', this.deps.label, key, error);
376
426
  return emptyResult();
377
427
  }
378
- finally {
379
- this.inFlight.delete(key);
380
- // Replay a schedule that arrived mid-run. `schedulePeer` re-checks `done`, so a run
381
- // that finished clean re-arms nothing; a denied or failed one gets its retry.
382
- if (this.rearmAfterFlight.delete(key)) {
383
- this.schedulePeer(peerId);
384
- }
385
- }
386
428
  }
387
429
  /** Whether this network's libp2p node still holds a connection to that peer. */
388
430
  isConnected(peerId) {