@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.
- package/dist/src/cluster/client.d.ts +10 -0
- package/dist/src/cluster/client.d.ts.map +1 -1
- package/dist/src/cluster/client.js +30 -1
- package/dist/src/cluster/client.js.map +1 -1
- package/dist/src/cluster/cluster-policy.d.ts +13 -2
- package/dist/src/cluster/cluster-policy.d.ts.map +1 -1
- package/dist/src/cluster/cluster-policy.js +51 -4
- package/dist/src/cluster/cluster-policy.js.map +1 -1
- package/dist/src/cluster/cluster-repo.d.ts +42 -17
- package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
- package/dist/src/cluster/cluster-repo.js +229 -122
- package/dist/src/cluster/cluster-repo.js.map +1 -1
- package/dist/src/cluster/cluster-size-coupling.d.ts +28 -0
- package/dist/src/cluster/cluster-size-coupling.d.ts.map +1 -0
- package/dist/src/cluster/cluster-size-coupling.js +35 -0
- package/dist/src/cluster/cluster-size-coupling.js.map +1 -0
- package/dist/src/cluster/quorum-restore.d.ts +6 -0
- package/dist/src/cluster/quorum-restore.d.ts.map +1 -1
- package/dist/src/cluster/quorum-restore.js +1 -1
- package/dist/src/cluster/quorum-restore.js.map +1 -1
- package/dist/src/cluster/reconcile-block.d.ts.map +1 -1
- package/dist/src/cluster/reconcile-block.js +15 -3
- package/dist/src/cluster/reconcile-block.js.map +1 -1
- package/dist/src/cluster/service.d.ts +32 -1
- package/dist/src/cluster/service.d.ts.map +1 -1
- package/dist/src/cluster/service.js +43 -2
- package/dist/src/cluster/service.js.map +1 -1
- package/dist/src/cohort-topic/stream-util.d.ts +22 -6
- package/dist/src/cohort-topic/stream-util.d.ts.map +1 -1
- package/dist/src/cohort-topic/stream-util.js +56 -10
- package/dist/src/cohort-topic/stream-util.js.map +1 -1
- package/dist/src/dispute/dispute-service.d.ts.map +1 -1
- package/dist/src/dispute/dispute-service.js +9 -3
- package/dist/src/dispute/dispute-service.js.map +1 -1
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +5 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/libp2p-key-network.d.ts +134 -7
- package/dist/src/libp2p-key-network.d.ts.map +1 -1
- package/dist/src/libp2p-key-network.js +174 -37
- package/dist/src/libp2p-key-network.js.map +1 -1
- package/dist/src/libp2p-node-base.d.ts +3 -2
- package/dist/src/libp2p-node-base.d.ts.map +1 -1
- package/dist/src/libp2p-node-base.js +859 -778
- package/dist/src/libp2p-node-base.js.map +1 -1
- package/dist/src/libp2p-node-rn.d.ts +2 -2
- package/dist/src/libp2p-node-rn.d.ts.map +1 -1
- package/dist/src/libp2p-node-rn.js.map +1 -1
- package/dist/src/libp2p-node.d.ts +2 -2
- package/dist/src/libp2p-node.d.ts.map +1 -1
- package/dist/src/libp2p-node.js.map +1 -1
- package/dist/src/logger.d.ts +17 -1
- package/dist/src/logger.d.ts.map +1 -1
- package/dist/src/logger.js +19 -2
- package/dist/src/logger.js.map +1 -1
- package/dist/src/network/network-manager-service.d.ts +2 -0
- package/dist/src/network/network-manager-service.d.ts.map +1 -1
- package/dist/src/network/network-manager-service.js +4 -0
- package/dist/src/network/network-manager-service.js.map +1 -1
- package/dist/src/optimystic-node.d.ts +35 -0
- package/dist/src/optimystic-node.d.ts.map +1 -0
- package/dist/src/optimystic-node.js +2 -0
- package/dist/src/optimystic-node.js.map +1 -0
- package/dist/src/owned-block-seed.d.ts +6 -3
- package/dist/src/owned-block-seed.d.ts.map +1 -1
- package/dist/src/owned-block-seed.js +16 -3
- package/dist/src/owned-block-seed.js.map +1 -1
- package/dist/src/peer-address-book.d.ts +72 -0
- package/dist/src/peer-address-book.d.ts.map +1 -0
- package/dist/src/peer-address-book.js +123 -0
- package/dist/src/peer-address-book.js.map +1 -0
- package/dist/src/repo/client.d.ts.map +1 -1
- package/dist/src/repo/client.js +11 -2
- package/dist/src/repo/client.js.map +1 -1
- package/dist/src/repo/cluster-coordinator.d.ts +30 -0
- package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
- package/dist/src/repo/cluster-coordinator.js +95 -3
- package/dist/src/repo/cluster-coordinator.js.map +1 -1
- package/dist/src/repo/coordinator-repo.d.ts +78 -14
- package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
- package/dist/src/repo/coordinator-repo.js +266 -81
- package/dist/src/repo/coordinator-repo.js.map +1 -1
- package/dist/src/rn.d.ts +5 -0
- package/dist/src/rn.d.ts.map +1 -1
- package/dist/src/rn.js +5 -0
- package/dist/src/rn.js.map +1 -1
- package/dist/src/storage/block-storage.d.ts.map +1 -1
- package/dist/src/storage/block-storage.js +57 -5
- package/dist/src/storage/block-storage.js.map +1 -1
- package/dist/src/storage/cached-raw-storage.d.ts +83 -0
- package/dist/src/storage/cached-raw-storage.d.ts.map +1 -0
- package/dist/src/storage/cached-raw-storage.js +152 -0
- package/dist/src/storage/cached-raw-storage.js.map +1 -0
- package/dist/src/storage/cached-store-driver.d.ts +186 -0
- package/dist/src/storage/cached-store-driver.d.ts.map +1 -0
- package/dist/src/storage/cached-store-driver.js +775 -0
- package/dist/src/storage/cached-store-driver.js.map +1 -0
- package/dist/src/storage/i-block-storage.d.ts +20 -1
- package/dist/src/storage/i-block-storage.d.ts.map +1 -1
- package/dist/src/storage/i-raw-storage.d.ts +12 -5
- package/dist/src/storage/i-raw-storage.d.ts.map +1 -1
- package/dist/src/storage/shared-cache-pool.d.ts +234 -0
- package/dist/src/storage/shared-cache-pool.d.ts.map +1 -0
- package/dist/src/storage/shared-cache-pool.js +354 -0
- package/dist/src/storage/shared-cache-pool.js.map +1 -0
- package/dist/src/storage/storage-repo.d.ts +56 -3
- package/dist/src/storage/storage-repo.d.ts.map +1 -1
- package/dist/src/storage/storage-repo.js +124 -18
- package/dist/src/storage/storage-repo.js.map +1 -1
- package/dist/src/testing/raw-storage-conformance.d.ts +2 -1
- package/dist/src/testing/raw-storage-conformance.d.ts.map +1 -1
- package/dist/src/testing/raw-storage-conformance.js +52 -2
- package/dist/src/testing/raw-storage-conformance.js.map +1 -1
- package/package.json +3 -3
- package/readme.md +668 -653
- package/src/cluster/block-transfer.ts +424 -424
- package/src/cluster/client.ts +119 -88
- package/src/cluster/cluster-error.ts +64 -64
- package/src/cluster/cluster-policy.ts +203 -152
- package/src/cluster/cluster-repo.ts +245 -125
- package/src/cluster/cluster-size-coupling.ts +45 -0
- package/src/cluster/commit-cert.ts +139 -139
- package/src/cluster/i-transaction-state-store.ts +43 -43
- package/src/cluster/memory-transaction-state-store.ts +56 -56
- package/src/cluster/peer-key-binding.ts +37 -37
- package/src/cluster/persistent-transaction-state-store.ts +92 -92
- package/src/cluster/quorum-restore.ts +223 -223
- package/src/cluster/reconcile-block.ts +203 -191
- package/src/cluster/service.ts +293 -241
- package/src/cluster/supermajority-coupling.ts +37 -37
- package/src/cohort-topic/bootstrap-evidence-builder.ts +122 -122
- package/src/cohort-topic/bootstrap-evidence-verifiers.ts +132 -132
- package/src/cohort-topic/bootstrap-parent-reference.ts +159 -159
- package/src/cohort-topic/change-bridge.ts +109 -109
- package/src/cohort-topic/cohort-gossip-driver.ts +231 -231
- package/src/cohort-topic/cohort-gossip-transport.ts +84 -84
- package/src/cohort-topic/fret-trust-anchor.ts +153 -153
- package/src/cohort-topic/host.ts +2901 -2901
- package/src/cohort-topic/index.ts +13 -13
- package/src/cohort-topic/membership-publish-sink.ts +20 -20
- package/src/cohort-topic/membership-source.ts +68 -68
- package/src/cohort-topic/peer-codec.ts +31 -31
- package/src/cohort-topic/peer-sig.ts +86 -86
- package/src/cohort-topic/protocols.ts +71 -71
- package/src/cohort-topic/reactivity-membership-gate.ts +77 -77
- package/src/cohort-topic/size-estimator.ts +16 -16
- package/src/cohort-topic/stream-util.ts +135 -87
- package/src/cohort-topic/threshold-crypto.ts +239 -239
- package/src/cohort-topic/topic-router.ts +77 -77
- package/src/dispute/arbitrator-selection.ts +138 -138
- package/src/dispute/cascade.ts +524 -524
- package/src/dispute/dispute-service.ts +11 -5
- package/src/dispute/invalidation.ts +625 -625
- package/src/inbound-authorization.ts +190 -190
- package/src/index.ts +52 -47
- package/src/libp2p-key-network.ts +1120 -958
- package/src/libp2p-node-base.ts +1675 -1591
- package/src/libp2p-node-rn.ts +30 -30
- package/src/libp2p-node.ts +36 -36
- package/src/logger.ts +19 -2
- package/src/matchmaking/aggregate-counts.ts +104 -104
- package/src/matchmaking/index.ts +20 -20
- package/src/matchmaking/module.ts +363 -363
- package/src/matchmaking/protocols.ts +51 -51
- package/src/matchmaking/provider-manager.ts +95 -95
- package/src/matchmaking/query-handler.ts +88 -88
- package/src/matchmaking/query-transport.ts +492 -492
- package/src/matchmaking/seeker-manager.ts +64 -64
- package/src/matchmaking/seeker-walk-client.ts +293 -293
- package/src/matchmaking/traffic-validation.ts +195 -195
- package/src/network/network-manager-service.ts +5 -0
- package/src/optimystic-node.ts +36 -0
- package/src/owned-block-seed.ts +53 -40
- package/src/peer-address-book.ts +149 -0
- package/src/protocol-limits.ts +33 -33
- package/src/reactivity/forwarder-host.ts +438 -438
- package/src/reactivity/index.ts +19 -19
- package/src/reactivity/notify-transport.ts +144 -144
- package/src/reactivity/origination-manager.ts +192 -192
- package/src/reactivity/protocols.ts +61 -61
- package/src/reactivity/push-state-gossip.ts +291 -291
- package/src/reactivity/recover-transport.ts +408 -408
- package/src/reactivity/rotation-rereg-scheduler.ts +256 -256
- package/src/reactivity/subscriber-registry.ts +96 -96
- package/src/reactivity/subscription-manager.ts +450 -450
- package/src/reactivity/topic-bytes.ts +37 -37
- package/src/repo/client.ts +12 -2
- package/src/repo/cluster-coordinator.ts +99 -3
- package/src/repo/coordinator-repo.ts +305 -82
- package/src/repo/types.ts +7 -7
- package/src/rn.ts +39 -34
- package/src/rpc-deadline.ts +45 -45
- package/src/storage/arachnode-partition.ts +74 -74
- package/src/storage/block-storage.ts +59 -6
- package/src/storage/cached-raw-storage.ts +180 -0
- package/src/storage/cached-store-driver.ts +859 -0
- package/src/storage/i-block-storage.ts +20 -1
- package/src/storage/i-kv-store.ts +8 -8
- package/src/storage/i-raw-storage.ts +12 -5
- package/src/storage/kv-raw-storage.ts +135 -135
- package/src/storage/memory-kv-store.ts +28 -28
- package/src/storage/memory-storage.ts +25 -25
- package/src/storage/memory-store-driver.ts +157 -157
- package/src/storage/raw-store-codec.ts +42 -42
- package/src/storage/raw-store-driver.ts +80 -80
- package/src/storage/ring-selector.ts +317 -317
- package/src/storage/ring-shift-coordinator.ts +271 -271
- package/src/storage/shared-cache-pool.ts +452 -0
- package/src/storage/storage-repo.ts +1014 -903
- package/src/testing/cohort-topic-mesh-harness.ts +663 -663
- package/src/testing/index.ts +8 -8
- package/src/testing/matchmaking-mesh-harness.ts +475 -475
- package/src/testing/raw-storage-conformance.ts +453 -397
- package/src/testing/reactivity-mesh-harness.ts +922 -922
- package/dist/src/storage/restoration-coordinator-v2.d.ts +0 -67
- package/dist/src/storage/restoration-coordinator-v2.d.ts.map +0 -1
- package/dist/src/storage/restoration-coordinator-v2.js +0 -172
- 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
|
+
}
|