@optimystic/db-p2p 0.21.0 → 0.24.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 (219) hide show
  1. package/dist/src/cluster/client.d.ts +10 -0
  2. package/dist/src/cluster/client.d.ts.map +1 -1
  3. package/dist/src/cluster/client.js +30 -1
  4. package/dist/src/cluster/client.js.map +1 -1
  5. package/dist/src/cluster/cluster-policy.d.ts +13 -2
  6. package/dist/src/cluster/cluster-policy.d.ts.map +1 -1
  7. package/dist/src/cluster/cluster-policy.js +51 -4
  8. package/dist/src/cluster/cluster-policy.js.map +1 -1
  9. package/dist/src/cluster/cluster-repo.d.ts +42 -17
  10. package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
  11. package/dist/src/cluster/cluster-repo.js +229 -122
  12. package/dist/src/cluster/cluster-repo.js.map +1 -1
  13. package/dist/src/cluster/cluster-size-coupling.d.ts +28 -0
  14. package/dist/src/cluster/cluster-size-coupling.d.ts.map +1 -0
  15. package/dist/src/cluster/cluster-size-coupling.js +35 -0
  16. package/dist/src/cluster/cluster-size-coupling.js.map +1 -0
  17. package/dist/src/cluster/quorum-restore.d.ts +6 -0
  18. package/dist/src/cluster/quorum-restore.d.ts.map +1 -1
  19. package/dist/src/cluster/quorum-restore.js +1 -1
  20. package/dist/src/cluster/quorum-restore.js.map +1 -1
  21. package/dist/src/cluster/reconcile-block.d.ts.map +1 -1
  22. package/dist/src/cluster/reconcile-block.js +15 -3
  23. package/dist/src/cluster/reconcile-block.js.map +1 -1
  24. package/dist/src/cluster/service.d.ts +32 -1
  25. package/dist/src/cluster/service.d.ts.map +1 -1
  26. package/dist/src/cluster/service.js +43 -2
  27. package/dist/src/cluster/service.js.map +1 -1
  28. package/dist/src/cohort-topic/stream-util.d.ts +22 -6
  29. package/dist/src/cohort-topic/stream-util.d.ts.map +1 -1
  30. package/dist/src/cohort-topic/stream-util.js +56 -10
  31. package/dist/src/cohort-topic/stream-util.js.map +1 -1
  32. package/dist/src/dispute/dispute-service.d.ts.map +1 -1
  33. package/dist/src/dispute/dispute-service.js +9 -3
  34. package/dist/src/dispute/dispute-service.js.map +1 -1
  35. package/dist/src/index.d.ts +5 -0
  36. package/dist/src/index.d.ts.map +1 -1
  37. package/dist/src/index.js +5 -0
  38. package/dist/src/index.js.map +1 -1
  39. package/dist/src/libp2p-key-network.d.ts +134 -7
  40. package/dist/src/libp2p-key-network.d.ts.map +1 -1
  41. package/dist/src/libp2p-key-network.js +174 -37
  42. package/dist/src/libp2p-key-network.js.map +1 -1
  43. package/dist/src/libp2p-node-base.d.ts +3 -2
  44. package/dist/src/libp2p-node-base.d.ts.map +1 -1
  45. package/dist/src/libp2p-node-base.js +859 -778
  46. package/dist/src/libp2p-node-base.js.map +1 -1
  47. package/dist/src/libp2p-node-rn.d.ts +2 -2
  48. package/dist/src/libp2p-node-rn.d.ts.map +1 -1
  49. package/dist/src/libp2p-node-rn.js.map +1 -1
  50. package/dist/src/libp2p-node.d.ts +2 -2
  51. package/dist/src/libp2p-node.d.ts.map +1 -1
  52. package/dist/src/libp2p-node.js.map +1 -1
  53. package/dist/src/logger.d.ts +17 -1
  54. package/dist/src/logger.d.ts.map +1 -1
  55. package/dist/src/logger.js +19 -2
  56. package/dist/src/logger.js.map +1 -1
  57. package/dist/src/network/network-manager-service.d.ts +2 -0
  58. package/dist/src/network/network-manager-service.d.ts.map +1 -1
  59. package/dist/src/network/network-manager-service.js +4 -0
  60. package/dist/src/network/network-manager-service.js.map +1 -1
  61. package/dist/src/optimystic-node.d.ts +35 -0
  62. package/dist/src/optimystic-node.d.ts.map +1 -0
  63. package/dist/src/optimystic-node.js +2 -0
  64. package/dist/src/optimystic-node.js.map +1 -0
  65. package/dist/src/owned-block-seed.d.ts +6 -3
  66. package/dist/src/owned-block-seed.d.ts.map +1 -1
  67. package/dist/src/owned-block-seed.js +16 -3
  68. package/dist/src/owned-block-seed.js.map +1 -1
  69. package/dist/src/peer-address-book.d.ts +72 -0
  70. package/dist/src/peer-address-book.d.ts.map +1 -0
  71. package/dist/src/peer-address-book.js +123 -0
  72. package/dist/src/peer-address-book.js.map +1 -0
  73. package/dist/src/repo/client.d.ts.map +1 -1
  74. package/dist/src/repo/client.js +11 -2
  75. package/dist/src/repo/client.js.map +1 -1
  76. package/dist/src/repo/cluster-coordinator.d.ts +30 -0
  77. package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
  78. package/dist/src/repo/cluster-coordinator.js +95 -3
  79. package/dist/src/repo/cluster-coordinator.js.map +1 -1
  80. package/dist/src/repo/coordinator-repo.d.ts +78 -14
  81. package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
  82. package/dist/src/repo/coordinator-repo.js +266 -81
  83. package/dist/src/repo/coordinator-repo.js.map +1 -1
  84. package/dist/src/rn.d.ts +5 -0
  85. package/dist/src/rn.d.ts.map +1 -1
  86. package/dist/src/rn.js +5 -0
  87. package/dist/src/rn.js.map +1 -1
  88. package/dist/src/storage/block-storage.d.ts.map +1 -1
  89. package/dist/src/storage/block-storage.js +57 -5
  90. package/dist/src/storage/block-storage.js.map +1 -1
  91. package/dist/src/storage/cached-raw-storage.d.ts +83 -0
  92. package/dist/src/storage/cached-raw-storage.d.ts.map +1 -0
  93. package/dist/src/storage/cached-raw-storage.js +152 -0
  94. package/dist/src/storage/cached-raw-storage.js.map +1 -0
  95. package/dist/src/storage/cached-store-driver.d.ts +186 -0
  96. package/dist/src/storage/cached-store-driver.d.ts.map +1 -0
  97. package/dist/src/storage/cached-store-driver.js +775 -0
  98. package/dist/src/storage/cached-store-driver.js.map +1 -0
  99. package/dist/src/storage/i-block-storage.d.ts +20 -1
  100. package/dist/src/storage/i-block-storage.d.ts.map +1 -1
  101. package/dist/src/storage/i-raw-storage.d.ts +12 -5
  102. package/dist/src/storage/i-raw-storage.d.ts.map +1 -1
  103. package/dist/src/storage/shared-cache-pool.d.ts +234 -0
  104. package/dist/src/storage/shared-cache-pool.d.ts.map +1 -0
  105. package/dist/src/storage/shared-cache-pool.js +354 -0
  106. package/dist/src/storage/shared-cache-pool.js.map +1 -0
  107. package/dist/src/storage/storage-repo.d.ts +56 -3
  108. package/dist/src/storage/storage-repo.d.ts.map +1 -1
  109. package/dist/src/storage/storage-repo.js +124 -18
  110. package/dist/src/storage/storage-repo.js.map +1 -1
  111. package/dist/src/testing/raw-storage-conformance.d.ts +2 -1
  112. package/dist/src/testing/raw-storage-conformance.d.ts.map +1 -1
  113. package/dist/src/testing/raw-storage-conformance.js +52 -2
  114. package/dist/src/testing/raw-storage-conformance.js.map +1 -1
  115. package/package.json +3 -3
  116. package/readme.md +668 -653
  117. package/src/cluster/block-transfer.ts +424 -424
  118. package/src/cluster/client.ts +119 -88
  119. package/src/cluster/cluster-error.ts +64 -64
  120. package/src/cluster/cluster-policy.ts +203 -152
  121. package/src/cluster/cluster-repo.ts +245 -125
  122. package/src/cluster/cluster-size-coupling.ts +45 -0
  123. package/src/cluster/commit-cert.ts +139 -139
  124. package/src/cluster/i-transaction-state-store.ts +43 -43
  125. package/src/cluster/memory-transaction-state-store.ts +56 -56
  126. package/src/cluster/peer-key-binding.ts +37 -37
  127. package/src/cluster/persistent-transaction-state-store.ts +92 -92
  128. package/src/cluster/quorum-restore.ts +223 -223
  129. package/src/cluster/reconcile-block.ts +203 -191
  130. package/src/cluster/service.ts +293 -241
  131. package/src/cluster/supermajority-coupling.ts +37 -37
  132. package/src/cohort-topic/bootstrap-evidence-builder.ts +122 -122
  133. package/src/cohort-topic/bootstrap-evidence-verifiers.ts +132 -132
  134. package/src/cohort-topic/bootstrap-parent-reference.ts +159 -159
  135. package/src/cohort-topic/change-bridge.ts +109 -109
  136. package/src/cohort-topic/cohort-gossip-driver.ts +231 -231
  137. package/src/cohort-topic/cohort-gossip-transport.ts +84 -84
  138. package/src/cohort-topic/fret-trust-anchor.ts +153 -153
  139. package/src/cohort-topic/host.ts +2901 -2901
  140. package/src/cohort-topic/index.ts +13 -13
  141. package/src/cohort-topic/membership-publish-sink.ts +20 -20
  142. package/src/cohort-topic/membership-source.ts +68 -68
  143. package/src/cohort-topic/peer-codec.ts +31 -31
  144. package/src/cohort-topic/peer-sig.ts +86 -86
  145. package/src/cohort-topic/protocols.ts +71 -71
  146. package/src/cohort-topic/reactivity-membership-gate.ts +77 -77
  147. package/src/cohort-topic/size-estimator.ts +16 -16
  148. package/src/cohort-topic/stream-util.ts +135 -87
  149. package/src/cohort-topic/threshold-crypto.ts +239 -239
  150. package/src/cohort-topic/topic-router.ts +77 -77
  151. package/src/dispute/arbitrator-selection.ts +138 -138
  152. package/src/dispute/cascade.ts +524 -524
  153. package/src/dispute/dispute-service.ts +11 -5
  154. package/src/dispute/invalidation.ts +625 -625
  155. package/src/inbound-authorization.ts +190 -190
  156. package/src/index.ts +52 -47
  157. package/src/libp2p-key-network.ts +1120 -958
  158. package/src/libp2p-node-base.ts +1675 -1591
  159. package/src/libp2p-node-rn.ts +30 -30
  160. package/src/libp2p-node.ts +36 -36
  161. package/src/logger.ts +19 -2
  162. package/src/matchmaking/aggregate-counts.ts +104 -104
  163. package/src/matchmaking/index.ts +20 -20
  164. package/src/matchmaking/module.ts +363 -363
  165. package/src/matchmaking/protocols.ts +51 -51
  166. package/src/matchmaking/provider-manager.ts +95 -95
  167. package/src/matchmaking/query-handler.ts +88 -88
  168. package/src/matchmaking/query-transport.ts +492 -492
  169. package/src/matchmaking/seeker-manager.ts +64 -64
  170. package/src/matchmaking/seeker-walk-client.ts +293 -293
  171. package/src/matchmaking/traffic-validation.ts +195 -195
  172. package/src/network/network-manager-service.ts +5 -0
  173. package/src/optimystic-node.ts +36 -0
  174. package/src/owned-block-seed.ts +53 -40
  175. package/src/peer-address-book.ts +149 -0
  176. package/src/protocol-limits.ts +33 -33
  177. package/src/reactivity/forwarder-host.ts +438 -438
  178. package/src/reactivity/index.ts +19 -19
  179. package/src/reactivity/notify-transport.ts +144 -144
  180. package/src/reactivity/origination-manager.ts +192 -192
  181. package/src/reactivity/protocols.ts +61 -61
  182. package/src/reactivity/push-state-gossip.ts +291 -291
  183. package/src/reactivity/recover-transport.ts +408 -408
  184. package/src/reactivity/rotation-rereg-scheduler.ts +256 -256
  185. package/src/reactivity/subscriber-registry.ts +96 -96
  186. package/src/reactivity/subscription-manager.ts +450 -450
  187. package/src/reactivity/topic-bytes.ts +37 -37
  188. package/src/repo/client.ts +12 -2
  189. package/src/repo/cluster-coordinator.ts +99 -3
  190. package/src/repo/coordinator-repo.ts +305 -82
  191. package/src/repo/types.ts +7 -7
  192. package/src/rn.ts +39 -34
  193. package/src/rpc-deadline.ts +45 -45
  194. package/src/storage/arachnode-partition.ts +74 -74
  195. package/src/storage/block-storage.ts +59 -6
  196. package/src/storage/cached-raw-storage.ts +180 -0
  197. package/src/storage/cached-store-driver.ts +859 -0
  198. package/src/storage/i-block-storage.ts +20 -1
  199. package/src/storage/i-kv-store.ts +8 -8
  200. package/src/storage/i-raw-storage.ts +12 -5
  201. package/src/storage/kv-raw-storage.ts +135 -135
  202. package/src/storage/memory-kv-store.ts +28 -28
  203. package/src/storage/memory-storage.ts +25 -25
  204. package/src/storage/memory-store-driver.ts +157 -157
  205. package/src/storage/raw-store-codec.ts +42 -42
  206. package/src/storage/raw-store-driver.ts +80 -80
  207. package/src/storage/ring-selector.ts +317 -317
  208. package/src/storage/ring-shift-coordinator.ts +271 -271
  209. package/src/storage/shared-cache-pool.ts +452 -0
  210. package/src/storage/storage-repo.ts +1014 -903
  211. package/src/testing/cohort-topic-mesh-harness.ts +663 -663
  212. package/src/testing/index.ts +8 -8
  213. package/src/testing/matchmaking-mesh-harness.ts +475 -475
  214. package/src/testing/raw-storage-conformance.ts +453 -397
  215. package/src/testing/reactivity-mesh-harness.ts +922 -922
  216. package/dist/src/storage/restoration-coordinator-v2.d.ts +0 -67
  217. package/dist/src/storage/restoration-coordinator-v2.d.ts.map +0 -1
  218. package/dist/src/storage/restoration-coordinator-v2.js +0 -172
  219. package/dist/src/storage/restoration-coordinator-v2.js.map +0 -1
@@ -1,122 +1,122 @@
1
- /**
2
- * Cohort-topic substrate — participant-side bootstrap-evidence builder (db-p2p side of the `buildBootstrapEvidence` seam).
3
- *
4
- * Implements the db-core `CohortTopicServiceDeps.buildBootstrapEvidence` seam for the node's participant
5
- * role: on a cold-start `bootstrap: true` re-issue the service calls this with the register's own
6
- * canonical `(topicId, tier, participantCoord, timestamp)` tuple (base64url wire strings) and attaches the
7
- * returned bytes — **before** signing — into `RegisterV1.bootstrapEvidence`.
8
- *
9
- * - **Tier ≤ maxNoPowTier (T0/T1):** proof-of-work is not the expected evidence. With an `endorse`
10
- * capability (a key-ful node) we mint a *self-vouch reputation endorsement* over the bound image so a
11
- * configured cohort's referee verifier admits it; without one we return `undefined` (parent-reference
12
- * origination is the follow-on `cohort-topic-bootstrap-parent-reference`). A T0/T1 bootstrap with no
13
- * evidence is denied by a configured cohort until that lands — but the single-tier-0 milestone's
14
- * cohort-side tests construct evidence directly, so this does not block them.
15
- * - **Tier ≥ maxNoPowTier+1 (T2/T3):** mint a proof-of-work — loop nonces until
16
- * `meetsDifficulty(hash.H(powPreimage(reg, nonce)), bits)`. Bound work ≈ `2^bits` hashes (default 20 ≈
17
- * ~1 M, sub-second). The loop is capped ({@link DEFAULT_POW_MAX_ITERATIONS}); on cap-exceeded it returns
18
- * `undefined` so the register proceeds without evidence rather than ever hanging the register path.
19
- *
20
- * Returns the **raw** envelope JSON bytes (`utf8(JSON.stringify(env))`), NOT the already-base64url string
21
- * from `serializeBootstrapEvidenceEnvelope` — the service base64url-encodes them itself (returning the
22
- * serialized string's bytes would double-encode the field). We obtain the raw bytes by decoding the
23
- * canonical serializer output once, so the byte layout matches exactly what a verifier reconstructs.
24
- */
25
-
26
- import {
27
- serializeBootstrapEvidenceEnvelope,
28
- bootstrapBoundImage,
29
- powPreimage,
30
- meetsDifficulty,
31
- DEFAULT_POW_DIFFICULTY_BITS,
32
- DEFAULT_MAX_NO_POW_TIER,
33
- bytesToB64url,
34
- b64urlToBytes,
35
- type IRingHash,
36
- type BootstrapBoundFields,
37
- type BootstrapEvidenceEnvelopeV1,
38
- type ReputationEvidenceV1,
39
- } from "@optimystic/db-core";
40
- import { randomBytes } from "@libp2p/crypto";
41
-
42
- /**
43
- * Defensive cap on the PoW nonce search (~16.7 M hashes). Comfortably above the ~`2^bits` expected for
44
- * the default 20-bit difficulty (~1 M), so a real miner solves long before the cap; hitting it (a
45
- * mis-set, very-high `bits`) returns `undefined` rather than hanging the register path.
46
- */
47
- export const DEFAULT_POW_MAX_ITERATIONS = 1 << 24;
48
-
49
- /** The bound tuple the service hands the builder — the same shape a verifier binds via {@link bootstrapBoundImage}. */
50
- export type BootstrapEvidenceBuildParams = BootstrapBoundFields;
51
-
52
- /** Inputs to {@link createBootstrapEvidenceBuilder}. */
53
- export interface BootstrapEvidenceBuilderDeps {
54
- /** The node's ring hash (SHA-256) — the same `H` the PoW verifier checks against. */
55
- readonly hash: IRingHash;
56
- /** Difficulty bits to mint at. Default {@link DEFAULT_POW_DIFFICULTY_BITS}. `0` solves on the first nonce (test). */
57
- readonly bits?: number;
58
- /** Highest tier exempt from PoW (T0/T1 → 1). Default {@link DEFAULT_MAX_NO_POW_TIER}. */
59
- readonly maxNoPowTier?: number;
60
- /** Nonce-search cap. Default {@link DEFAULT_POW_MAX_ITERATIONS}. */
61
- readonly maxIterations?: number;
62
- /**
63
- * Optional self-vouch endorsement capability for a key-ful node: signs the bound image with the node's
64
- * peer key and returns the referee (= self) + signature. Supplied → T0/T1 mints a reputation
65
- * endorsement (the interim T0/T1 path until parent-reference origination lands); absent → T0/T1 carries
66
- * no evidence.
67
- */
68
- readonly endorse?: (boundImage: Uint8Array) => Promise<ReputationEvidenceV1>;
69
- }
70
-
71
- /**
72
- * Build the {@link import("@optimystic/db-core").CohortTopicServiceDeps.buildBootstrapEvidence} seam:
73
- * a `(params) => Promise<Uint8Array | undefined>` that mints the cold-start evidence for the node's own
74
- * register. PoW for T2/T3; a self-vouch reputation endorsement (when `endorse` is supplied) or nothing
75
- * for T0/T1. Never throws and never hangs (the nonce loop is capped).
76
- */
77
- export function createBootstrapEvidenceBuilder(
78
- deps: BootstrapEvidenceBuilderDeps,
79
- ): (params: BootstrapEvidenceBuildParams) => Promise<Uint8Array | undefined> {
80
- const bits = deps.bits ?? DEFAULT_POW_DIFFICULTY_BITS;
81
- const maxNoPowTier = deps.maxNoPowTier ?? DEFAULT_MAX_NO_POW_TIER;
82
- const maxIterations = deps.maxIterations ?? DEFAULT_POW_MAX_ITERATIONS;
83
-
84
- return async (params: BootstrapEvidenceBuildParams): Promise<Uint8Array | undefined> => {
85
- const bound: BootstrapBoundFields = {
86
- topicId: params.topicId,
87
- tier: params.tier,
88
- participantCoord: params.participantCoord,
89
- timestamp: params.timestamp,
90
- };
91
-
92
- if (params.tier <= maxNoPowTier) {
93
- // T0/T1: PoW is not the expected evidence. A key-ful node self-vouches; otherwise no evidence
94
- // (the parent-reference path is the follow-on ticket — documented deferral).
95
- if (deps.endorse === undefined) {
96
- return undefined;
97
- }
98
- const reputation = await deps.endorse(bootstrapBoundImage(bound));
99
- return rawEnvelopeBytes({ v: 1, reputation });
100
- }
101
-
102
- // T2/T3: mint a proof-of-work — search nonces until the digest meets the difficulty target.
103
- for (let i = 0; i < maxIterations; i++) {
104
- const nonce = randomBytes(16); // CSPRNG nonce — bound to the register tuple via the preimage
105
- if (meetsDifficulty(deps.hash.H(powPreimage(bound, nonce)), bits)) {
106
- return rawEnvelopeBytes({ v: 1, pow: { nonce: bytesToB64url(nonce) } });
107
- }
108
- }
109
- // Cap exceeded (mis-set difficulty): attach nothing rather than hang. A configured cohort denies it.
110
- return undefined;
111
- };
112
- }
113
-
114
- /**
115
- * The raw envelope JSON bytes the service expects (`utf8(JSON.stringify(env))`). Reuses the db-core
116
- * canonical serializer (fixed field order, deterministic JSON) and decodes its base64url output back to
117
- * the underlying bytes — so the layout is byte-identical to what a verifier reconstructs, with zero
118
- * duplicated canonicalization here.
119
- */
120
- function rawEnvelopeBytes(env: BootstrapEvidenceEnvelopeV1): Uint8Array {
121
- return b64urlToBytes(serializeBootstrapEvidenceEnvelope(env));
122
- }
1
+ /**
2
+ * Cohort-topic substrate — participant-side bootstrap-evidence builder (db-p2p side of the `buildBootstrapEvidence` seam).
3
+ *
4
+ * Implements the db-core `CohortTopicServiceDeps.buildBootstrapEvidence` seam for the node's participant
5
+ * role: on a cold-start `bootstrap: true` re-issue the service calls this with the register's own
6
+ * canonical `(topicId, tier, participantCoord, timestamp)` tuple (base64url wire strings) and attaches the
7
+ * returned bytes — **before** signing — into `RegisterV1.bootstrapEvidence`.
8
+ *
9
+ * - **Tier ≤ maxNoPowTier (T0/T1):** proof-of-work is not the expected evidence. With an `endorse`
10
+ * capability (a key-ful node) we mint a *self-vouch reputation endorsement* over the bound image so a
11
+ * configured cohort's referee verifier admits it; without one we return `undefined` (parent-reference
12
+ * origination is the follow-on `cohort-topic-bootstrap-parent-reference`). A T0/T1 bootstrap with no
13
+ * evidence is denied by a configured cohort until that lands — but the single-tier-0 milestone's
14
+ * cohort-side tests construct evidence directly, so this does not block them.
15
+ * - **Tier ≥ maxNoPowTier+1 (T2/T3):** mint a proof-of-work — loop nonces until
16
+ * `meetsDifficulty(hash.H(powPreimage(reg, nonce)), bits)`. Bound work ≈ `2^bits` hashes (default 20 ≈
17
+ * ~1 M, sub-second). The loop is capped ({@link DEFAULT_POW_MAX_ITERATIONS}); on cap-exceeded it returns
18
+ * `undefined` so the register proceeds without evidence rather than ever hanging the register path.
19
+ *
20
+ * Returns the **raw** envelope JSON bytes (`utf8(JSON.stringify(env))`), NOT the already-base64url string
21
+ * from `serializeBootstrapEvidenceEnvelope` — the service base64url-encodes them itself (returning the
22
+ * serialized string's bytes would double-encode the field). We obtain the raw bytes by decoding the
23
+ * canonical serializer output once, so the byte layout matches exactly what a verifier reconstructs.
24
+ */
25
+
26
+ import {
27
+ serializeBootstrapEvidenceEnvelope,
28
+ bootstrapBoundImage,
29
+ powPreimage,
30
+ meetsDifficulty,
31
+ DEFAULT_POW_DIFFICULTY_BITS,
32
+ DEFAULT_MAX_NO_POW_TIER,
33
+ bytesToB64url,
34
+ b64urlToBytes,
35
+ type IRingHash,
36
+ type BootstrapBoundFields,
37
+ type BootstrapEvidenceEnvelopeV1,
38
+ type ReputationEvidenceV1,
39
+ } from "@optimystic/db-core";
40
+ import { randomBytes } from "@libp2p/crypto";
41
+
42
+ /**
43
+ * Defensive cap on the PoW nonce search (~16.7 M hashes). Comfortably above the ~`2^bits` expected for
44
+ * the default 20-bit difficulty (~1 M), so a real miner solves long before the cap; hitting it (a
45
+ * mis-set, very-high `bits`) returns `undefined` rather than hanging the register path.
46
+ */
47
+ export const DEFAULT_POW_MAX_ITERATIONS = 1 << 24;
48
+
49
+ /** The bound tuple the service hands the builder — the same shape a verifier binds via {@link bootstrapBoundImage}. */
50
+ export type BootstrapEvidenceBuildParams = BootstrapBoundFields;
51
+
52
+ /** Inputs to {@link createBootstrapEvidenceBuilder}. */
53
+ export interface BootstrapEvidenceBuilderDeps {
54
+ /** The node's ring hash (SHA-256) — the same `H` the PoW verifier checks against. */
55
+ readonly hash: IRingHash;
56
+ /** Difficulty bits to mint at. Default {@link DEFAULT_POW_DIFFICULTY_BITS}. `0` solves on the first nonce (test). */
57
+ readonly bits?: number;
58
+ /** Highest tier exempt from PoW (T0/T1 → 1). Default {@link DEFAULT_MAX_NO_POW_TIER}. */
59
+ readonly maxNoPowTier?: number;
60
+ /** Nonce-search cap. Default {@link DEFAULT_POW_MAX_ITERATIONS}. */
61
+ readonly maxIterations?: number;
62
+ /**
63
+ * Optional self-vouch endorsement capability for a key-ful node: signs the bound image with the node's
64
+ * peer key and returns the referee (= self) + signature. Supplied → T0/T1 mints a reputation
65
+ * endorsement (the interim T0/T1 path until parent-reference origination lands); absent → T0/T1 carries
66
+ * no evidence.
67
+ */
68
+ readonly endorse?: (boundImage: Uint8Array) => Promise<ReputationEvidenceV1>;
69
+ }
70
+
71
+ /**
72
+ * Build the {@link import("@optimystic/db-core").CohortTopicServiceDeps.buildBootstrapEvidence} seam:
73
+ * a `(params) => Promise<Uint8Array | undefined>` that mints the cold-start evidence for the node's own
74
+ * register. PoW for T2/T3; a self-vouch reputation endorsement (when `endorse` is supplied) or nothing
75
+ * for T0/T1. Never throws and never hangs (the nonce loop is capped).
76
+ */
77
+ export function createBootstrapEvidenceBuilder(
78
+ deps: BootstrapEvidenceBuilderDeps,
79
+ ): (params: BootstrapEvidenceBuildParams) => Promise<Uint8Array | undefined> {
80
+ const bits = deps.bits ?? DEFAULT_POW_DIFFICULTY_BITS;
81
+ const maxNoPowTier = deps.maxNoPowTier ?? DEFAULT_MAX_NO_POW_TIER;
82
+ const maxIterations = deps.maxIterations ?? DEFAULT_POW_MAX_ITERATIONS;
83
+
84
+ return async (params: BootstrapEvidenceBuildParams): Promise<Uint8Array | undefined> => {
85
+ const bound: BootstrapBoundFields = {
86
+ topicId: params.topicId,
87
+ tier: params.tier,
88
+ participantCoord: params.participantCoord,
89
+ timestamp: params.timestamp,
90
+ };
91
+
92
+ if (params.tier <= maxNoPowTier) {
93
+ // T0/T1: PoW is not the expected evidence. A key-ful node self-vouches; otherwise no evidence
94
+ // (the parent-reference path is the follow-on ticket — documented deferral).
95
+ if (deps.endorse === undefined) {
96
+ return undefined;
97
+ }
98
+ const reputation = await deps.endorse(bootstrapBoundImage(bound));
99
+ return rawEnvelopeBytes({ v: 1, reputation });
100
+ }
101
+
102
+ // T2/T3: mint a proof-of-work — search nonces until the digest meets the difficulty target.
103
+ for (let i = 0; i < maxIterations; i++) {
104
+ const nonce = randomBytes(16); // CSPRNG nonce — bound to the register tuple via the preimage
105
+ if (meetsDifficulty(deps.hash.H(powPreimage(bound, nonce)), bits)) {
106
+ return rawEnvelopeBytes({ v: 1, pow: { nonce: bytesToB64url(nonce) } });
107
+ }
108
+ }
109
+ // Cap exceeded (mis-set difficulty): attach nothing rather than hang. A configured cohort denies it.
110
+ return undefined;
111
+ };
112
+ }
113
+
114
+ /**
115
+ * The raw envelope JSON bytes the service expects (`utf8(JSON.stringify(env))`). Reuses the db-core
116
+ * canonical serializer (fixed field order, deterministic JSON) and decodes its base64url output back to
117
+ * the underlying bytes — so the layout is byte-identical to what a verifier reconstructs, with zero
118
+ * duplicated canonicalization here.
119
+ */
120
+ function rawEnvelopeBytes(env: BootstrapEvidenceEnvelopeV1): Uint8Array {
121
+ return b64urlToBytes(serializeBootstrapEvidenceEnvelope(env));
122
+ }
@@ -1,132 +1,132 @@
1
- /**
2
- * Cohort-topic substrate — real bootstrap-evidence verifiers (db-p2p side of the anti-DoS gate).
3
- *
4
- * db-core owns the *policy* (which evidence kinds satisfy which tier) and the crypto-free envelope /
5
- * bound-image / PoW-preimage helpers (`antidos/bootstrap-evidence-envelope.ts`); it deliberately embeds
6
- * no specific PoW or reputation scheme. This module supplies the **self-contained cryptographic checks**
7
- * the host injects into `createBootstrapEvidence`, binding the node's `RingHash` (the same SHA-256 the
8
- * addressing uses) and the cohort-topic peer-key `verifyPeerSig` — no new crypto dependency.
9
- *
10
- * Both verifiers are **synchronous** (one hash, or one signature verify + two reputation-map reads) and
11
- * **total** — any parse / decode / verify failure on attacker-supplied input yields `false`, never a
12
- * throw — because `BootstrapEvidence.verify` runs inside `member-engine.ts`'s `runGuards` on every
13
- * register and must do no network I/O (that would itself be a DoS amplifier).
14
- */
15
-
16
- import {
17
- parseBootstrapEvidenceEnvelope,
18
- powPreimage,
19
- bootstrapBoundImage,
20
- meetsDifficulty,
21
- DEFAULT_POW_DIFFICULTY_BITS,
22
- b64urlToBytes,
23
- type IRingHash,
24
- type RegisterV1,
25
- } from "@optimystic/db-core";
26
- import { DEFAULT_THRESHOLDS } from "../reputation/types.js";
27
- import { verifyPeerSig } from "./peer-sig.js";
28
- import { bytesToPeerIdString } from "./peer-codec.js";
29
-
30
- /**
31
- * The slice of a peer-reputation service the bootstrap-evidence referee verifier consults — a subset of
32
- * {@link import("../reputation/types.js").IPeerReputation} that `PeerReputationService` satisfies
33
- * directly. "Sufficient reputation" is **stronger than mere non-ban**: the referee must be *both* not
34
- * banned *and* below the deprioritize threshold.
35
- */
36
- export interface BootstrapReputationView {
37
- /** True when `peerId` (a peer-id string) is banned / excluded from operations. */
38
- isBanned(peerId: string): boolean;
39
- /** Effective reputation score for `peerId` (0 = a clean, unseen peer). Lower is better. */
40
- getScore(peerId: string): number;
41
- }
42
-
43
- /**
44
- * The default "sufficient reputation" cutoff: a referee with `score < deprioritize` is reputable enough
45
- * to endorse a bootstrap. Mirrors the reputation service's default `deprioritize` threshold (20) so a
46
- * default-configured node and the gate agree on "sufficient". Strict `<`, so a referee *at* the
47
- * threshold is not sufficient.
48
- */
49
- export const DEFAULT_DEPRIORITIZE_THRESHOLD = DEFAULT_THRESHOLDS.deprioritize;
50
-
51
- /** Inputs to {@link createPoWVerifier}. */
52
- export interface PoWVerifierDeps {
53
- /** The node's ring hash (SHA-256) — the same `H` the addressing uses; hashes the PoW preimage. */
54
- readonly hash: IRingHash;
55
- /** Required leading-zero bits. Default {@link DEFAULT_POW_DIFFICULTY_BITS}. `0` admits any nonce (test). */
56
- readonly bits?: number;
57
- }
58
-
59
- /**
60
- * A real proof-of-work verifier for the T2/T3 evidence path. Self-contained — no subsystem, one hash.
61
- *
62
- * ```
63
- * env = parseBootstrapEvidenceEnvelope(reg); if (!env?.pow) return false
64
- * h = hash.H(powPreimage(reg, b64urlToBytes(env.pow.nonce)))
65
- * return meetsDifficulty(h, bits)
66
- * ```
67
- *
68
- * The preimage binds `(topicId, tier, participantCoord, timestamp)` (via {@link bootstrapBoundImage}),
69
- * so a PoW minted for one topic / peer / time cannot bootstrap another. Any absent `pow`, malformed
70
- * envelope, or non-base64url nonce → `false` (fails closed).
71
- */
72
- export function createPoWVerifier(deps: PoWVerifierDeps): (reg: RegisterV1) => boolean {
73
- const bits = deps.bits ?? DEFAULT_POW_DIFFICULTY_BITS;
74
- return (reg: RegisterV1): boolean => {
75
- try {
76
- const env = parseBootstrapEvidenceEnvelope(reg);
77
- if (env?.pow === undefined) {
78
- return false; // not offered / not a PoW envelope
79
- }
80
- const nonce = b64urlToBytes(env.pow.nonce);
81
- return meetsDifficulty(deps.hash.H(powPreimage(reg, nonce)), bits);
82
- } catch {
83
- return false; // any decode / hash failure on attacker input → fail closed
84
- }
85
- };
86
- }
87
-
88
- /** Inputs to {@link createReputationVerifier}. */
89
- export interface ReputationVerifierDeps {
90
- /** Local reputation view the referee is scored against (not banned + below the threshold). */
91
- readonly reputation: BootstrapReputationView;
92
- /** Strict "sufficient reputation" cutoff. Default {@link DEFAULT_DEPRIORITIZE_THRESHOLD}. */
93
- readonly deprioritizeThreshold?: number;
94
- }
95
-
96
- /**
97
- * A real reputation-endorsement verifier: a *referee* peer endorses the bootstrap by peer-key-signing
98
- * the {@link bootstrapBoundImage}, and the cohort checks the signature **and** that the referee is
99
- * sufficiently reputable in the **local** reputation view.
100
- *
101
- * ```
102
- * env = parseBootstrapEvidenceEnvelope(reg); if (!env?.reputation) return false
103
- * refereeBytes = b64urlToBytes(env.reputation.referee)
104
- * if (!verifyPeerSig(refereeBytes, bootstrapBoundImage(reg), b64urlToBytes(env.reputation.sig))) return false
105
- * refereeId = bytesToPeerIdString(refereeBytes)
106
- * return !reputation.isBanned(refereeId) && reputation.getScore(refereeId) < deprioritizeThreshold
107
- * ```
108
- *
109
- * The `referee` MAY equal the participant (a reputable participant self-vouches with its own peer key).
110
- * An unknown referee scores `0` (a clean, unseen peer) and so is sufficient; a referee exactly *at* the
111
- * threshold is not (strict `<`). Bad sig / banned / over-threshold / malformed → `false` (fails closed).
112
- */
113
- export function createReputationVerifier(deps: ReputationVerifierDeps): (reg: RegisterV1) => boolean {
114
- const threshold = deps.deprioritizeThreshold ?? DEFAULT_DEPRIORITIZE_THRESHOLD;
115
- return (reg: RegisterV1): boolean => {
116
- try {
117
- const env = parseBootstrapEvidenceEnvelope(reg);
118
- if (env?.reputation === undefined) {
119
- return false; // not offered / not a reputation envelope
120
- }
121
- const refereeBytes = b64urlToBytes(env.reputation.referee);
122
- const sig = b64urlToBytes(env.reputation.sig);
123
- if (!verifyPeerSig(refereeBytes, bootstrapBoundImage(reg), sig)) {
124
- return false; // signature does not bind this register's tuple to the referee key
125
- }
126
- const refereeId = bytesToPeerIdString(refereeBytes);
127
- return !deps.reputation.isBanned(refereeId) && deps.reputation.getScore(refereeId) < threshold;
128
- } catch {
129
- return false; // any decode / verify failure on attacker input → fail closed
130
- }
131
- };
132
- }
1
+ /**
2
+ * Cohort-topic substrate — real bootstrap-evidence verifiers (db-p2p side of the anti-DoS gate).
3
+ *
4
+ * db-core owns the *policy* (which evidence kinds satisfy which tier) and the crypto-free envelope /
5
+ * bound-image / PoW-preimage helpers (`antidos/bootstrap-evidence-envelope.ts`); it deliberately embeds
6
+ * no specific PoW or reputation scheme. This module supplies the **self-contained cryptographic checks**
7
+ * the host injects into `createBootstrapEvidence`, binding the node's `RingHash` (the same SHA-256 the
8
+ * addressing uses) and the cohort-topic peer-key `verifyPeerSig` — no new crypto dependency.
9
+ *
10
+ * Both verifiers are **synchronous** (one hash, or one signature verify + two reputation-map reads) and
11
+ * **total** — any parse / decode / verify failure on attacker-supplied input yields `false`, never a
12
+ * throw — because `BootstrapEvidence.verify` runs inside `member-engine.ts`'s `runGuards` on every
13
+ * register and must do no network I/O (that would itself be a DoS amplifier).
14
+ */
15
+
16
+ import {
17
+ parseBootstrapEvidenceEnvelope,
18
+ powPreimage,
19
+ bootstrapBoundImage,
20
+ meetsDifficulty,
21
+ DEFAULT_POW_DIFFICULTY_BITS,
22
+ b64urlToBytes,
23
+ type IRingHash,
24
+ type RegisterV1,
25
+ } from "@optimystic/db-core";
26
+ import { DEFAULT_THRESHOLDS } from "../reputation/types.js";
27
+ import { verifyPeerSig } from "./peer-sig.js";
28
+ import { bytesToPeerIdString } from "./peer-codec.js";
29
+
30
+ /**
31
+ * The slice of a peer-reputation service the bootstrap-evidence referee verifier consults — a subset of
32
+ * {@link import("../reputation/types.js").IPeerReputation} that `PeerReputationService` satisfies
33
+ * directly. "Sufficient reputation" is **stronger than mere non-ban**: the referee must be *both* not
34
+ * banned *and* below the deprioritize threshold.
35
+ */
36
+ export interface BootstrapReputationView {
37
+ /** True when `peerId` (a peer-id string) is banned / excluded from operations. */
38
+ isBanned(peerId: string): boolean;
39
+ /** Effective reputation score for `peerId` (0 = a clean, unseen peer). Lower is better. */
40
+ getScore(peerId: string): number;
41
+ }
42
+
43
+ /**
44
+ * The default "sufficient reputation" cutoff: a referee with `score < deprioritize` is reputable enough
45
+ * to endorse a bootstrap. Mirrors the reputation service's default `deprioritize` threshold (20) so a
46
+ * default-configured node and the gate agree on "sufficient". Strict `<`, so a referee *at* the
47
+ * threshold is not sufficient.
48
+ */
49
+ export const DEFAULT_DEPRIORITIZE_THRESHOLD = DEFAULT_THRESHOLDS.deprioritize;
50
+
51
+ /** Inputs to {@link createPoWVerifier}. */
52
+ export interface PoWVerifierDeps {
53
+ /** The node's ring hash (SHA-256) — the same `H` the addressing uses; hashes the PoW preimage. */
54
+ readonly hash: IRingHash;
55
+ /** Required leading-zero bits. Default {@link DEFAULT_POW_DIFFICULTY_BITS}. `0` admits any nonce (test). */
56
+ readonly bits?: number;
57
+ }
58
+
59
+ /**
60
+ * A real proof-of-work verifier for the T2/T3 evidence path. Self-contained — no subsystem, one hash.
61
+ *
62
+ * ```
63
+ * env = parseBootstrapEvidenceEnvelope(reg); if (!env?.pow) return false
64
+ * h = hash.H(powPreimage(reg, b64urlToBytes(env.pow.nonce)))
65
+ * return meetsDifficulty(h, bits)
66
+ * ```
67
+ *
68
+ * The preimage binds `(topicId, tier, participantCoord, timestamp)` (via {@link bootstrapBoundImage}),
69
+ * so a PoW minted for one topic / peer / time cannot bootstrap another. Any absent `pow`, malformed
70
+ * envelope, or non-base64url nonce → `false` (fails closed).
71
+ */
72
+ export function createPoWVerifier(deps: PoWVerifierDeps): (reg: RegisterV1) => boolean {
73
+ const bits = deps.bits ?? DEFAULT_POW_DIFFICULTY_BITS;
74
+ return (reg: RegisterV1): boolean => {
75
+ try {
76
+ const env = parseBootstrapEvidenceEnvelope(reg);
77
+ if (env?.pow === undefined) {
78
+ return false; // not offered / not a PoW envelope
79
+ }
80
+ const nonce = b64urlToBytes(env.pow.nonce);
81
+ return meetsDifficulty(deps.hash.H(powPreimage(reg, nonce)), bits);
82
+ } catch {
83
+ return false; // any decode / hash failure on attacker input → fail closed
84
+ }
85
+ };
86
+ }
87
+
88
+ /** Inputs to {@link createReputationVerifier}. */
89
+ export interface ReputationVerifierDeps {
90
+ /** Local reputation view the referee is scored against (not banned + below the threshold). */
91
+ readonly reputation: BootstrapReputationView;
92
+ /** Strict "sufficient reputation" cutoff. Default {@link DEFAULT_DEPRIORITIZE_THRESHOLD}. */
93
+ readonly deprioritizeThreshold?: number;
94
+ }
95
+
96
+ /**
97
+ * A real reputation-endorsement verifier: a *referee* peer endorses the bootstrap by peer-key-signing
98
+ * the {@link bootstrapBoundImage}, and the cohort checks the signature **and** that the referee is
99
+ * sufficiently reputable in the **local** reputation view.
100
+ *
101
+ * ```
102
+ * env = parseBootstrapEvidenceEnvelope(reg); if (!env?.reputation) return false
103
+ * refereeBytes = b64urlToBytes(env.reputation.referee)
104
+ * if (!verifyPeerSig(refereeBytes, bootstrapBoundImage(reg), b64urlToBytes(env.reputation.sig))) return false
105
+ * refereeId = bytesToPeerIdString(refereeBytes)
106
+ * return !reputation.isBanned(refereeId) && reputation.getScore(refereeId) < deprioritizeThreshold
107
+ * ```
108
+ *
109
+ * The `referee` MAY equal the participant (a reputable participant self-vouches with its own peer key).
110
+ * An unknown referee scores `0` (a clean, unseen peer) and so is sufficient; a referee exactly *at* the
111
+ * threshold is not (strict `<`). Bad sig / banned / over-threshold / malformed → `false` (fails closed).
112
+ */
113
+ export function createReputationVerifier(deps: ReputationVerifierDeps): (reg: RegisterV1) => boolean {
114
+ const threshold = deps.deprioritizeThreshold ?? DEFAULT_DEPRIORITIZE_THRESHOLD;
115
+ return (reg: RegisterV1): boolean => {
116
+ try {
117
+ const env = parseBootstrapEvidenceEnvelope(reg);
118
+ if (env?.reputation === undefined) {
119
+ return false; // not offered / not a reputation envelope
120
+ }
121
+ const refereeBytes = b64urlToBytes(env.reputation.referee);
122
+ const sig = b64urlToBytes(env.reputation.sig);
123
+ if (!verifyPeerSig(refereeBytes, bootstrapBoundImage(reg), sig)) {
124
+ return false; // signature does not bind this register's tuple to the referee key
125
+ }
126
+ const refereeId = bytesToPeerIdString(refereeBytes);
127
+ return !deps.reputation.isBanned(refereeId) && deps.reputation.getScore(refereeId) < threshold;
128
+ } catch {
129
+ return false; // any decode / verify failure on attacker input → fail closed
130
+ }
131
+ };
132
+ }