@optimystic/db-p2p 0.22.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 (177) 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-repo.d.ts +39 -14
  6. package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
  7. package/dist/src/cluster/cluster-repo.js +226 -119
  8. package/dist/src/cluster/cluster-repo.js.map +1 -1
  9. package/dist/src/cluster/service.d.ts +32 -1
  10. package/dist/src/cluster/service.d.ts.map +1 -1
  11. package/dist/src/cluster/service.js +43 -2
  12. package/dist/src/cluster/service.js.map +1 -1
  13. package/dist/src/cohort-topic/stream-util.d.ts +22 -6
  14. package/dist/src/cohort-topic/stream-util.d.ts.map +1 -1
  15. package/dist/src/cohort-topic/stream-util.js +56 -10
  16. package/dist/src/cohort-topic/stream-util.js.map +1 -1
  17. package/dist/src/dispute/dispute-service.d.ts.map +1 -1
  18. package/dist/src/dispute/dispute-service.js +9 -3
  19. package/dist/src/dispute/dispute-service.js.map +1 -1
  20. package/dist/src/index.d.ts +3 -0
  21. package/dist/src/index.d.ts.map +1 -1
  22. package/dist/src/index.js +3 -0
  23. package/dist/src/index.js.map +1 -1
  24. package/dist/src/libp2p-key-network.d.ts +88 -2
  25. package/dist/src/libp2p-key-network.d.ts.map +1 -1
  26. package/dist/src/libp2p-key-network.js +134 -28
  27. package/dist/src/libp2p-key-network.js.map +1 -1
  28. package/dist/src/libp2p-node-base.d.ts.map +1 -1
  29. package/dist/src/libp2p-node-base.js +25 -1
  30. package/dist/src/libp2p-node-base.js.map +1 -1
  31. package/dist/src/logger.d.ts +17 -1
  32. package/dist/src/logger.d.ts.map +1 -1
  33. package/dist/src/logger.js +19 -2
  34. package/dist/src/logger.js.map +1 -1
  35. package/dist/src/owned-block-seed.d.ts +6 -3
  36. package/dist/src/owned-block-seed.d.ts.map +1 -1
  37. package/dist/src/owned-block-seed.js +16 -3
  38. package/dist/src/owned-block-seed.js.map +1 -1
  39. package/dist/src/peer-address-book.d.ts +72 -0
  40. package/dist/src/peer-address-book.d.ts.map +1 -0
  41. package/dist/src/peer-address-book.js +123 -0
  42. package/dist/src/peer-address-book.js.map +1 -0
  43. package/dist/src/repo/client.d.ts.map +1 -1
  44. package/dist/src/repo/client.js +11 -2
  45. package/dist/src/repo/client.js.map +1 -1
  46. package/dist/src/repo/cluster-coordinator.d.ts +30 -0
  47. package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
  48. package/dist/src/repo/cluster-coordinator.js +95 -3
  49. package/dist/src/repo/cluster-coordinator.js.map +1 -1
  50. package/dist/src/repo/coordinator-repo.d.ts +62 -9
  51. package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
  52. package/dist/src/repo/coordinator-repo.js +242 -73
  53. package/dist/src/repo/coordinator-repo.js.map +1 -1
  54. package/dist/src/rn.d.ts +3 -0
  55. package/dist/src/rn.d.ts.map +1 -1
  56. package/dist/src/rn.js +3 -0
  57. package/dist/src/rn.js.map +1 -1
  58. package/dist/src/storage/cached-raw-storage.d.ts +83 -0
  59. package/dist/src/storage/cached-raw-storage.d.ts.map +1 -0
  60. package/dist/src/storage/cached-raw-storage.js +152 -0
  61. package/dist/src/storage/cached-raw-storage.js.map +1 -0
  62. package/dist/src/storage/cached-store-driver.d.ts +186 -0
  63. package/dist/src/storage/cached-store-driver.d.ts.map +1 -0
  64. package/dist/src/storage/cached-store-driver.js +775 -0
  65. package/dist/src/storage/cached-store-driver.js.map +1 -0
  66. package/dist/src/storage/i-raw-storage.d.ts +12 -5
  67. package/dist/src/storage/i-raw-storage.d.ts.map +1 -1
  68. package/dist/src/storage/shared-cache-pool.d.ts +234 -0
  69. package/dist/src/storage/shared-cache-pool.d.ts.map +1 -0
  70. package/dist/src/storage/shared-cache-pool.js +354 -0
  71. package/dist/src/storage/shared-cache-pool.js.map +1 -0
  72. package/dist/src/testing/raw-storage-conformance.d.ts +2 -1
  73. package/dist/src/testing/raw-storage-conformance.d.ts.map +1 -1
  74. package/dist/src/testing/raw-storage-conformance.js +35 -2
  75. package/dist/src/testing/raw-storage-conformance.js.map +1 -1
  76. package/package.json +3 -3
  77. package/readme.md +668 -668
  78. package/src/cluster/block-transfer.ts +424 -424
  79. package/src/cluster/client.ts +119 -88
  80. package/src/cluster/cluster-error.ts +64 -64
  81. package/src/cluster/cluster-policy.ts +203 -203
  82. package/src/cluster/cluster-repo.ts +242 -122
  83. package/src/cluster/cluster-size-coupling.ts +45 -45
  84. package/src/cluster/commit-cert.ts +139 -139
  85. package/src/cluster/i-transaction-state-store.ts +43 -43
  86. package/src/cluster/memory-transaction-state-store.ts +56 -56
  87. package/src/cluster/peer-key-binding.ts +37 -37
  88. package/src/cluster/persistent-transaction-state-store.ts +92 -92
  89. package/src/cluster/quorum-restore.ts +223 -223
  90. package/src/cluster/reconcile-block.ts +203 -203
  91. package/src/cluster/service.ts +293 -241
  92. package/src/cluster/supermajority-coupling.ts +37 -37
  93. package/src/cohort-topic/bootstrap-evidence-builder.ts +122 -122
  94. package/src/cohort-topic/bootstrap-evidence-verifiers.ts +132 -132
  95. package/src/cohort-topic/bootstrap-parent-reference.ts +159 -159
  96. package/src/cohort-topic/change-bridge.ts +109 -109
  97. package/src/cohort-topic/cohort-gossip-driver.ts +231 -231
  98. package/src/cohort-topic/cohort-gossip-transport.ts +84 -84
  99. package/src/cohort-topic/fret-trust-anchor.ts +153 -153
  100. package/src/cohort-topic/host.ts +2901 -2901
  101. package/src/cohort-topic/index.ts +13 -13
  102. package/src/cohort-topic/membership-publish-sink.ts +20 -20
  103. package/src/cohort-topic/membership-source.ts +68 -68
  104. package/src/cohort-topic/peer-codec.ts +31 -31
  105. package/src/cohort-topic/peer-sig.ts +86 -86
  106. package/src/cohort-topic/protocols.ts +71 -71
  107. package/src/cohort-topic/reactivity-membership-gate.ts +77 -77
  108. package/src/cohort-topic/size-estimator.ts +16 -16
  109. package/src/cohort-topic/stream-util.ts +135 -87
  110. package/src/cohort-topic/threshold-crypto.ts +239 -239
  111. package/src/cohort-topic/topic-router.ts +77 -77
  112. package/src/dispute/arbitrator-selection.ts +138 -138
  113. package/src/dispute/cascade.ts +524 -524
  114. package/src/dispute/dispute-service.ts +11 -5
  115. package/src/dispute/invalidation.ts +625 -625
  116. package/src/inbound-authorization.ts +190 -190
  117. package/src/index.ts +52 -49
  118. package/src/libp2p-key-network.ts +1120 -990
  119. package/src/libp2p-node-base.ts +1675 -1651
  120. package/src/libp2p-node-rn.ts +30 -30
  121. package/src/libp2p-node.ts +36 -36
  122. package/src/logger.ts +19 -2
  123. package/src/matchmaking/aggregate-counts.ts +104 -104
  124. package/src/matchmaking/index.ts +20 -20
  125. package/src/matchmaking/module.ts +363 -363
  126. package/src/matchmaking/protocols.ts +51 -51
  127. package/src/matchmaking/provider-manager.ts +95 -95
  128. package/src/matchmaking/query-handler.ts +88 -88
  129. package/src/matchmaking/query-transport.ts +492 -492
  130. package/src/matchmaking/seeker-manager.ts +64 -64
  131. package/src/matchmaking/seeker-walk-client.ts +293 -293
  132. package/src/matchmaking/traffic-validation.ts +195 -195
  133. package/src/optimystic-node.ts +36 -36
  134. package/src/owned-block-seed.ts +53 -40
  135. package/src/peer-address-book.ts +149 -0
  136. package/src/protocol-limits.ts +33 -33
  137. package/src/reactivity/forwarder-host.ts +438 -438
  138. package/src/reactivity/index.ts +19 -19
  139. package/src/reactivity/notify-transport.ts +144 -144
  140. package/src/reactivity/origination-manager.ts +192 -192
  141. package/src/reactivity/protocols.ts +61 -61
  142. package/src/reactivity/push-state-gossip.ts +291 -291
  143. package/src/reactivity/recover-transport.ts +408 -408
  144. package/src/reactivity/rotation-rereg-scheduler.ts +256 -256
  145. package/src/reactivity/subscriber-registry.ts +96 -96
  146. package/src/reactivity/subscription-manager.ts +450 -450
  147. package/src/reactivity/topic-bytes.ts +37 -37
  148. package/src/repo/client.ts +12 -2
  149. package/src/repo/cluster-coordinator.ts +99 -3
  150. package/src/repo/coordinator-repo.ts +281 -74
  151. package/src/repo/types.ts +7 -7
  152. package/src/rn.ts +39 -36
  153. package/src/rpc-deadline.ts +45 -45
  154. package/src/storage/arachnode-partition.ts +74 -74
  155. package/src/storage/cached-raw-storage.ts +180 -0
  156. package/src/storage/cached-store-driver.ts +859 -0
  157. package/src/storage/i-kv-store.ts +8 -8
  158. package/src/storage/i-raw-storage.ts +12 -5
  159. package/src/storage/kv-raw-storage.ts +135 -135
  160. package/src/storage/memory-kv-store.ts +28 -28
  161. package/src/storage/memory-storage.ts +25 -25
  162. package/src/storage/memory-store-driver.ts +157 -157
  163. package/src/storage/raw-store-codec.ts +42 -42
  164. package/src/storage/raw-store-driver.ts +80 -80
  165. package/src/storage/ring-selector.ts +317 -317
  166. package/src/storage/ring-shift-coordinator.ts +271 -271
  167. package/src/storage/shared-cache-pool.ts +452 -0
  168. package/src/storage/storage-repo.ts +1014 -1014
  169. package/src/testing/cohort-topic-mesh-harness.ts +663 -663
  170. package/src/testing/index.ts +8 -8
  171. package/src/testing/matchmaking-mesh-harness.ts +475 -475
  172. package/src/testing/raw-storage-conformance.ts +453 -417
  173. package/src/testing/reactivity-mesh-harness.ts +922 -922
  174. package/dist/src/storage/restoration-coordinator-v2.d.ts +0 -67
  175. package/dist/src/storage/restoration-coordinator-v2.d.ts.map +0 -1
  176. package/dist/src/storage/restoration-coordinator-v2.js +0 -172
  177. 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
+ }