@optimystic/db-core 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/README.md +336 -336
- package/dist/src/btree/btree.d.ts +2 -1
- package/dist/src/btree/btree.d.ts.map +1 -1
- package/dist/src/btree/btree.js +1 -1
- package/dist/src/btree/btree.js.map +1 -1
- package/dist/src/chain/chain.d.ts +1 -1
- package/dist/src/chain/chain.d.ts.map +1 -1
- package/dist/src/chain/chain.js +1 -1
- package/dist/src/chain/chain.js.map +1 -1
- package/dist/src/cluster/structs.d.ts +39 -1
- package/dist/src/cluster/structs.d.ts.map +1 -1
- package/dist/src/cluster/structs.js +24 -0
- package/dist/src/cluster/structs.js.map +1 -1
- package/dist/src/collection/collection.d.ts +20 -1
- package/dist/src/collection/collection.d.ts.map +1 -1
- package/dist/src/collection/collection.js +31 -4
- package/dist/src/collection/collection.js.map +1 -1
- package/dist/src/collections/diary/diary.d.ts.map +1 -1
- package/dist/src/collections/diary/diary.js +2 -1
- package/dist/src/collections/diary/diary.js.map +1 -1
- package/dist/src/collections/diary/struct.js +1 -1
- package/dist/src/collections/diary/struct.js.map +1 -1
- package/dist/src/collections/tree/collection-trunk.js +1 -1
- package/dist/src/collections/tree/collection-trunk.js.map +1 -1
- package/dist/src/collections/tree/struct.d.ts +1 -1
- package/dist/src/collections/tree/struct.d.ts.map +1 -1
- package/dist/src/collections/tree/struct.js +2 -1
- package/dist/src/collections/tree/struct.js.map +1 -1
- package/dist/src/collections/tree/tree.d.ts +5 -0
- package/dist/src/collections/tree/tree.d.ts.map +1 -1
- package/dist/src/collections/tree/tree.js +7 -0
- package/dist/src/collections/tree/tree.js.map +1 -1
- package/dist/src/log/log.d.ts +1 -1
- package/dist/src/log/log.d.ts.map +1 -1
- package/dist/src/log/log.js +2 -2
- package/dist/src/log/log.js.map +1 -1
- package/dist/src/network/i-peer-network.d.ts +16 -0
- package/dist/src/network/i-peer-network.d.ts.map +1 -1
- package/dist/src/network/struct.d.ts +39 -2
- package/dist/src/network/struct.d.ts.map +1 -1
- package/dist/src/network/struct.js +18 -0
- package/dist/src/network/struct.js.map +1 -1
- package/dist/src/testing/test-transactor.d.ts +95 -8
- package/dist/src/testing/test-transactor.d.ts.map +1 -1
- package/dist/src/testing/test-transactor.js +133 -12
- package/dist/src/testing/test-transactor.js.map +1 -1
- package/dist/src/transaction/coordinator.d.ts.map +1 -1
- package/dist/src/transaction/coordinator.js +2 -1
- package/dist/src/transaction/coordinator.js.map +1 -1
- package/dist/src/transaction/transaction.d.ts +1 -1
- package/dist/src/transaction/transaction.js +1 -1
- package/dist/src/transactor/network-transactor.d.ts.map +1 -1
- package/dist/src/transactor/network-transactor.js +57 -18
- package/dist/src/transactor/network-transactor.js.map +1 -1
- package/dist/src/transactor/transactor-source.d.ts.map +1 -1
- package/dist/src/transactor/transactor-source.js +25 -2
- package/dist/src/transactor/transactor-source.js.map +1 -1
- package/dist/src/transform/cache-source.js +1 -1
- package/dist/src/transform/cache-source.js.map +1 -1
- package/dist/src/transform/helpers.d.ts +6 -1
- package/dist/src/transform/helpers.d.ts.map +1 -1
- package/dist/src/transform/helpers.js +7 -6
- package/dist/src/transform/helpers.js.map +1 -1
- package/dist/src/transform/tracker.d.ts.map +1 -1
- package/dist/src/transform/tracker.js +2 -1
- package/dist/src/transform/tracker.js.map +1 -1
- package/package.json +1 -1
- package/src/btree/btree.ts +2 -1
- package/src/chain/chain.ts +2 -1
- package/src/cluster/membership.ts +85 -85
- package/src/cluster/structs.ts +43 -4
- package/src/cohort-topic/addressing.ts +120 -120
- package/src/cohort-topic/antidos/bootstrap-evidence-envelope.ts +253 -253
- package/src/cohort-topic/antidos/bootstrap-evidence.ts +106 -106
- package/src/cohort-topic/antidos/index.ts +5 -5
- package/src/cohort-topic/antidos/rate-limiter.ts +210 -210
- package/src/cohort-topic/antidos/replay-guard.ts +146 -146
- package/src/cohort-topic/antidos/topic-budget.ts +160 -160
- package/src/cohort-topic/antiflood/index.ts +2 -2
- package/src/cohort-topic/antiflood/invariants.ts +108 -108
- package/src/cohort-topic/antiflood/jitter.ts +117 -117
- package/src/cohort-topic/coldstart.ts +237 -237
- package/src/cohort-topic/dmax.ts +88 -88
- package/src/cohort-topic/gossip/bus.ts +254 -254
- package/src/cohort-topic/gossip/index.ts +3 -3
- package/src/cohort-topic/gossip/records.ts +45 -45
- package/src/cohort-topic/gossip/view.ts +91 -91
- package/src/cohort-topic/index.ts +20 -20
- package/src/cohort-topic/load/barometer.ts +134 -134
- package/src/cohort-topic/load/index.ts +1 -1
- package/src/cohort-topic/member-engine.ts +430 -430
- package/src/cohort-topic/membership/index.ts +3 -3
- package/src/cohort-topic/membership/publisher.ts +163 -163
- package/src/cohort-topic/membership/source.ts +41 -41
- package/src/cohort-topic/membership/verifier.ts +461 -461
- package/src/cohort-topic/ports.ts +157 -157
- package/src/cohort-topic/promotion.ts +405 -405
- package/src/cohort-topic/registration/bytes.ts +37 -37
- package/src/cohort-topic/registration/handoff.ts +154 -154
- package/src/cohort-topic/registration/index.ts +6 -6
- package/src/cohort-topic/registration/renewal.ts +495 -495
- package/src/cohort-topic/registration/sharding.ts +61 -61
- package/src/cohort-topic/registration/store.ts +81 -81
- package/src/cohort-topic/registration/types.ts +91 -91
- package/src/cohort-topic/ring-hash.ts +50 -50
- package/src/cohort-topic/service.ts +416 -416
- package/src/cohort-topic/sig/index.ts +2 -2
- package/src/cohort-topic/sig/payloads.ts +59 -59
- package/src/cohort-topic/sig/threshold.ts +64 -64
- package/src/cohort-topic/tiers.ts +74 -74
- package/src/cohort-topic/traffic.ts +233 -233
- package/src/cohort-topic/walk.ts +326 -326
- package/src/cohort-topic/willingness.ts +237 -237
- package/src/cohort-topic/wire/codec.ts +216 -216
- package/src/cohort-topic/wire/index.ts +18 -18
- package/src/cohort-topic/wire/payloads.ts +126 -126
- package/src/cohort-topic/wire/primitives.ts +188 -188
- package/src/cohort-topic/wire/types.ts +475 -475
- package/src/cohort-topic/wire/validate.ts +512 -512
- package/src/collection/collection-type-registry.ts +37 -37
- package/src/collection/collection.ts +32 -4
- package/src/collections/diary/diary.ts +68 -67
- package/src/collections/diary/struct.ts +1 -1
- package/src/collections/tree/collection-trunk.ts +1 -1
- package/src/collections/tree/readme.md +4 -0
- package/src/collections/tree/struct.ts +3 -1
- package/src/collections/tree/tree.ts +320 -312
- package/src/log/log.ts +2 -2
- package/src/matchmaking/capability-filter.ts +45 -45
- package/src/matchmaking/config.ts +98 -98
- package/src/matchmaking/index.ts +21 -21
- package/src/matchmaking/multi-cohort-seeker.ts +234 -234
- package/src/matchmaking/provider.ts +123 -123
- package/src/matchmaking/query-eval.ts +105 -105
- package/src/matchmaking/seeker-walk.ts +127 -127
- package/src/matchmaking/seeker.ts +86 -86
- package/src/matchmaking/topic-anchor.ts +90 -90
- package/src/matchmaking/voting-quorum.ts +394 -394
- package/src/matchmaking/wire.ts +603 -603
- package/src/network/i-peer-network.ts +17 -0
- package/src/network/stale-failure.ts +43 -43
- package/src/network/struct.ts +41 -2
- package/src/network/types.ts +37 -37
- package/src/reactivity/backfill.ts +220 -220
- package/src/reactivity/backpressure.ts +191 -191
- package/src/reactivity/checkpoint.ts +308 -308
- package/src/reactivity/config.ts +172 -172
- package/src/reactivity/dedupe.ts +132 -132
- package/src/reactivity/forwarder.ts +87 -87
- package/src/reactivity/index.ts +34 -34
- package/src/reactivity/notification.ts +123 -123
- package/src/reactivity/policy.ts +79 -79
- package/src/reactivity/push-state.ts +310 -310
- package/src/reactivity/recover.ts +153 -153
- package/src/reactivity/replay-buffer.ts +141 -141
- package/src/reactivity/resume.ts +549 -549
- package/src/reactivity/rotation.ts +415 -415
- package/src/reactivity/subscriber.ts +132 -132
- package/src/reactivity/subscription.ts +66 -66
- package/src/reactivity/topic-anchor.ts +71 -71
- package/src/reactivity/verify.ts +73 -73
- package/src/reactivity/wire-validate.ts +13 -13
- package/src/reactivity/wire.ts +224 -224
- package/src/testing/async-wait.ts +65 -65
- package/src/testing/index.ts +2 -2
- package/src/testing/test-transactor.ts +638 -489
- package/src/transaction/coordinator.ts +2 -1
- package/src/transaction/errors.ts +91 -91
- package/src/transaction/operations-hash.ts +196 -196
- package/src/transaction/read-dependency-collector.ts +78 -78
- package/src/transaction/transaction.ts +1 -1
- package/src/transactor/change-notifier.ts +80 -80
- package/src/transactor/index.ts +5 -5
- package/src/transactor/network-transactor.ts +58 -19
- package/src/transactor/transactor-source.ts +25 -2
- package/src/transform/atomic-proxy.ts +92 -92
- package/src/transform/cache-source.ts +1 -1
- package/src/transform/helpers.ts +159 -158
- package/src/transform/tracker.ts +2 -1
- package/src/utility/backoff.ts +95 -95
- package/src/utility/batch-coordinator.ts +191 -191
- package/dist/src/transaction/context.d.ts +0 -60
- package/dist/src/transaction/context.d.ts.map +0 -1
- package/dist/src/transaction/context.js +0 -91
- package/dist/src/transaction/context.js.map +0 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from "./payloads.js";
|
|
2
|
-
export * from "./threshold.js";
|
|
1
|
+
export * from "./payloads.js";
|
|
2
|
+
export * from "./threshold.js";
|
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cohort-topic substrate — canonical threshold-signature payloads.
|
|
3
|
-
*
|
|
4
|
-
* A threshold signature is only verifiable if signer and verifier agree on the exact bytes signed.
|
|
5
|
-
* These builders produce that deterministic byte image for each threshold-signed message
|
|
6
|
-
* (`MembershipCertV1`, `PromotionNoticeV1`, `DemotionNoticeV1`, `ChildLinkV1`), covering exactly the
|
|
7
|
-
* semantic fields per `docs/cohort-topic.md` §Wire formats — never the `thresholdSig`/`signers` envelope.
|
|
8
|
-
*
|
|
9
|
-
* Determinism comes from encoding an explicitly-ordered JSON array (array order is stable, unlike
|
|
10
|
-
* object key order) as UTF-8. The publisher signs this image; the verifier recomputes and checks it.
|
|
11
|
-
*
|
|
12
|
-
* The notice images carry `cohortCoord` (the served coord the deciding cohort sits at) so the coord the
|
|
13
|
-
* receiver routes by is bound into the signed bytes — rewriting it to hijack a sibling cohort breaks
|
|
14
|
-
* verification. It is inserted **just before the trailing `cohortEpoch`**, deliberately keeping `cohortEpoch`
|
|
15
|
-
* the LAST element: the `/sign` endorser (`handleSignRequest`) reads a notice's embedded epoch positionally
|
|
16
|
-
* as `image[image.length - 1]`, so `cohortEpoch` must stay last. This is a hard, coordinated change to the
|
|
17
|
-
* canonical signed bytes, acceptable pre-release (no deployed nodes speak this protocol, so there is no
|
|
18
|
-
* version to negotiate).
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import type { ChildLinkV1, DemotionNoticeV1, MembershipCertV1, PromotionNoticeV1 } from "../wire/types.js";
|
|
22
|
-
|
|
23
|
-
const utf8 = new TextEncoder();
|
|
24
|
-
|
|
25
|
-
/** Fields of a `MembershipCertV1` covered by its threshold signature (doc: cohortCoord, cohortEpoch, members, stabilizedAt). */
|
|
26
|
-
export type MembershipCertSignable = Pick<MembershipCertV1, "cohortCoord" | "cohortEpoch" | "members" | "stabilizedAt">;
|
|
27
|
-
|
|
28
|
-
/** Canonical signed byte image of a membership certificate. */
|
|
29
|
-
export function membershipCertSigningPayload(cert: MembershipCertSignable): Uint8Array {
|
|
30
|
-
return utf8.encode(JSON.stringify(["MembershipCertV1", cert.cohortCoord, cert.cohortEpoch, cert.members, cert.stabilizedAt]));
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** Fields of a `PromotionNoticeV1` covered by its threshold signature. */
|
|
34
|
-
export type PromotionSignable = Pick<PromotionNoticeV1, "topicId" | "fromTier" | "toTier" | "effectiveAt" | "cohortEpoch" | "cohortCoord">;
|
|
35
|
-
|
|
36
|
-
/** Canonical signed byte image of a promotion notice. `cohortEpoch` stays last (the `/sign` endorser reads it positionally). */
|
|
37
|
-
export function promotionNoticeSigningPayload(n: PromotionSignable): Uint8Array {
|
|
38
|
-
return utf8.encode(JSON.stringify(["PromotionNoticeV1", n.topicId, n.fromTier, n.toTier, n.effectiveAt, n.cohortCoord, n.cohortEpoch]));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** Fields of a `DemotionNoticeV1` covered by its threshold signature. */
|
|
42
|
-
export type DemotionSignable = Pick<DemotionNoticeV1, "topicId" | "tier" | "parentCohortCoord" | "effectiveAt" | "cohortEpoch" | "cohortCoord">;
|
|
43
|
-
|
|
44
|
-
/** Canonical signed byte image of a demotion notice. `cohortEpoch` stays last (the `/sign` endorser reads it positionally). */
|
|
45
|
-
export function demotionNoticeSigningPayload(n: DemotionSignable): Uint8Array {
|
|
46
|
-
return utf8.encode(JSON.stringify(["DemotionNoticeV1", n.topicId, n.tier, n.parentCohortCoord, n.effectiveAt, n.cohortCoord, n.cohortEpoch]));
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** Fields of a `ChildLinkV1` covered by the child cohort's threshold signature. */
|
|
50
|
-
export type ChildLinkSignable = Pick<ChildLinkV1, "topicId" | "childCohortCoord" | "childParticipantCoord" | "childTier" | "tier" | "effectiveAt" | "cohortEpoch">;
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Canonical signed byte image of a child-link frame. `cohortEpoch` stays **last** so the `/sign` endorser
|
|
54
|
-
* (`handleSignRequest`) reads the embedded epoch positionally as `image[image.length - 1]`, exactly as it
|
|
55
|
-
* does for a promotion / demotion notice.
|
|
56
|
-
*/
|
|
57
|
-
export function childLinkSigningPayload(n: ChildLinkSignable): Uint8Array {
|
|
58
|
-
return utf8.encode(JSON.stringify(["ChildLinkV1", n.topicId, n.childCohortCoord, n.childParticipantCoord, n.childTier, n.tier, n.effectiveAt, n.cohortEpoch]));
|
|
59
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Cohort-topic substrate — canonical threshold-signature payloads.
|
|
3
|
+
*
|
|
4
|
+
* A threshold signature is only verifiable if signer and verifier agree on the exact bytes signed.
|
|
5
|
+
* These builders produce that deterministic byte image for each threshold-signed message
|
|
6
|
+
* (`MembershipCertV1`, `PromotionNoticeV1`, `DemotionNoticeV1`, `ChildLinkV1`), covering exactly the
|
|
7
|
+
* semantic fields per `docs/cohort-topic.md` §Wire formats — never the `thresholdSig`/`signers` envelope.
|
|
8
|
+
*
|
|
9
|
+
* Determinism comes from encoding an explicitly-ordered JSON array (array order is stable, unlike
|
|
10
|
+
* object key order) as UTF-8. The publisher signs this image; the verifier recomputes and checks it.
|
|
11
|
+
*
|
|
12
|
+
* The notice images carry `cohortCoord` (the served coord the deciding cohort sits at) so the coord the
|
|
13
|
+
* receiver routes by is bound into the signed bytes — rewriting it to hijack a sibling cohort breaks
|
|
14
|
+
* verification. It is inserted **just before the trailing `cohortEpoch`**, deliberately keeping `cohortEpoch`
|
|
15
|
+
* the LAST element: the `/sign` endorser (`handleSignRequest`) reads a notice's embedded epoch positionally
|
|
16
|
+
* as `image[image.length - 1]`, so `cohortEpoch` must stay last. This is a hard, coordinated change to the
|
|
17
|
+
* canonical signed bytes, acceptable pre-release (no deployed nodes speak this protocol, so there is no
|
|
18
|
+
* version to negotiate).
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { ChildLinkV1, DemotionNoticeV1, MembershipCertV1, PromotionNoticeV1 } from "../wire/types.js";
|
|
22
|
+
|
|
23
|
+
const utf8 = new TextEncoder();
|
|
24
|
+
|
|
25
|
+
/** Fields of a `MembershipCertV1` covered by its threshold signature (doc: cohortCoord, cohortEpoch, members, stabilizedAt). */
|
|
26
|
+
export type MembershipCertSignable = Pick<MembershipCertV1, "cohortCoord" | "cohortEpoch" | "members" | "stabilizedAt">;
|
|
27
|
+
|
|
28
|
+
/** Canonical signed byte image of a membership certificate. */
|
|
29
|
+
export function membershipCertSigningPayload(cert: MembershipCertSignable): Uint8Array {
|
|
30
|
+
return utf8.encode(JSON.stringify(["MembershipCertV1", cert.cohortCoord, cert.cohortEpoch, cert.members, cert.stabilizedAt]));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Fields of a `PromotionNoticeV1` covered by its threshold signature. */
|
|
34
|
+
export type PromotionSignable = Pick<PromotionNoticeV1, "topicId" | "fromTier" | "toTier" | "effectiveAt" | "cohortEpoch" | "cohortCoord">;
|
|
35
|
+
|
|
36
|
+
/** Canonical signed byte image of a promotion notice. `cohortEpoch` stays last (the `/sign` endorser reads it positionally). */
|
|
37
|
+
export function promotionNoticeSigningPayload(n: PromotionSignable): Uint8Array {
|
|
38
|
+
return utf8.encode(JSON.stringify(["PromotionNoticeV1", n.topicId, n.fromTier, n.toTier, n.effectiveAt, n.cohortCoord, n.cohortEpoch]));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Fields of a `DemotionNoticeV1` covered by its threshold signature. */
|
|
42
|
+
export type DemotionSignable = Pick<DemotionNoticeV1, "topicId" | "tier" | "parentCohortCoord" | "effectiveAt" | "cohortEpoch" | "cohortCoord">;
|
|
43
|
+
|
|
44
|
+
/** Canonical signed byte image of a demotion notice. `cohortEpoch` stays last (the `/sign` endorser reads it positionally). */
|
|
45
|
+
export function demotionNoticeSigningPayload(n: DemotionSignable): Uint8Array {
|
|
46
|
+
return utf8.encode(JSON.stringify(["DemotionNoticeV1", n.topicId, n.tier, n.parentCohortCoord, n.effectiveAt, n.cohortCoord, n.cohortEpoch]));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Fields of a `ChildLinkV1` covered by the child cohort's threshold signature. */
|
|
50
|
+
export type ChildLinkSignable = Pick<ChildLinkV1, "topicId" | "childCohortCoord" | "childParticipantCoord" | "childTier" | "tier" | "effectiveAt" | "cohortEpoch">;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Canonical signed byte image of a child-link frame. `cohortEpoch` stays **last** so the `/sign` endorser
|
|
54
|
+
* (`handleSignRequest`) reads the embedded epoch positionally as `image[image.length - 1]`, exactly as it
|
|
55
|
+
* does for a promotion / demotion notice.
|
|
56
|
+
*/
|
|
57
|
+
export function childLinkSigningPayload(n: ChildLinkSignable): Uint8Array {
|
|
58
|
+
return utf8.encode(JSON.stringify(["ChildLinkV1", n.topicId, n.childCohortCoord, n.childParticipantCoord, n.childTier, n.tier, n.effectiveAt, n.cohortEpoch]));
|
|
59
|
+
}
|
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cohort-topic substrate — `k − x` threshold signing and verification.
|
|
3
|
-
*
|
|
4
|
-
* Per `docs/cohort-topic.md` §FRET integration (L659) and §Membership snapshots: the cohort
|
|
5
|
-
* threshold-signs `PromotionNoticeV1` / `DemotionNoticeV1` / `MembershipCertV1` with `minSigs`
|
|
6
|
-
* (= `k − x`, default 14) signers. This module is db-core logic over the {@link ICohortThresholdCrypto}
|
|
7
|
-
* port; db-p2p binds the port to FRET's `minSigs` cohort-signature assembly — db-core never imports
|
|
8
|
-
* FRET, and the underlying scheme is reused unchanged.
|
|
9
|
-
*
|
|
10
|
-
* {@link CohortSigner.verifyThreshold} layers the membership check db-core owns on top of the raw
|
|
11
|
-
* crypto: the `signers` must be a distinct `≥ minSigs` subset of the certificate's `members`, *and*
|
|
12
|
-
* the signature must verify against the payload. Either failure → not verified.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import type { ICohortThresholdCrypto } from "../ports.js";
|
|
16
|
-
import { bytesToB64url } from "../wire/codec.js";
|
|
17
|
-
import type { MembershipCertV1 } from "../wire/types.js";
|
|
18
|
-
|
|
19
|
-
/** Default cohort-signature threshold, `k − x` (see §Configuration). */
|
|
20
|
-
export const DEFAULT_MIN_SIGS = 14;
|
|
21
|
-
|
|
22
|
-
/** Threshold signer/verifier over the injected cohort crypto. Peer ids are raw bytes. */
|
|
23
|
-
export interface CohortSigner {
|
|
24
|
-
/** Assemble a cohort threshold signature over `payload` (collects `minSigs` signers). */
|
|
25
|
-
thresholdSign(payload: Uint8Array): Promise<{ thresholdSig: Uint8Array; signers: Uint8Array[] }>;
|
|
26
|
-
/**
|
|
27
|
-
* Verify a threshold-signed message: `signers` are a distinct `≥ minSigs` subset of
|
|
28
|
-
* `cert.members` and `sig` is a valid cohort signature over `payload`.
|
|
29
|
-
*/
|
|
30
|
-
verifyThreshold(payload: Uint8Array, sig: Uint8Array, signers: readonly Uint8Array[], cert: MembershipCertV1, minSigs: number): boolean;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
class CryptoCohortSigner implements CohortSigner {
|
|
34
|
-
constructor(private readonly crypto: ICohortThresholdCrypto, private readonly minSigs: number) {}
|
|
35
|
-
|
|
36
|
-
thresholdSign(payload: Uint8Array): Promise<{ thresholdSig: Uint8Array; signers: Uint8Array[] }> {
|
|
37
|
-
return this.crypto.assemble(payload, this.minSigs);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
verifyThreshold(payload: Uint8Array, sig: Uint8Array, signers: readonly Uint8Array[], cert: MembershipCertV1, minSigs: number): boolean {
|
|
41
|
-
if (signers.length < minSigs) {
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
// Members are base64url on the wire; compare signers in that same canonical form.
|
|
45
|
-
const memberSet = new Set(cert.members);
|
|
46
|
-
const seen = new Set<string>();
|
|
47
|
-
for (const signer of signers) {
|
|
48
|
-
const key = bytesToB64url(signer);
|
|
49
|
-
if (seen.has(key)) {
|
|
50
|
-
return false; // a duplicated signer cannot pad the count toward minSigs
|
|
51
|
-
}
|
|
52
|
-
seen.add(key);
|
|
53
|
-
if (!memberSet.has(key)) {
|
|
54
|
-
return false; // signer is not a member of the attested cohort
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return this.crypto.verify(payload, sig, signers);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** Build a {@link CohortSigner} over the FRET-backed (in db-p2p) threshold crypto. */
|
|
62
|
-
export function createCohortSigner(crypto: ICohortThresholdCrypto, minSigs: number = DEFAULT_MIN_SIGS): CohortSigner {
|
|
63
|
-
return new CryptoCohortSigner(crypto, minSigs);
|
|
64
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Cohort-topic substrate — `k − x` threshold signing and verification.
|
|
3
|
+
*
|
|
4
|
+
* Per `docs/cohort-topic.md` §FRET integration (L659) and §Membership snapshots: the cohort
|
|
5
|
+
* threshold-signs `PromotionNoticeV1` / `DemotionNoticeV1` / `MembershipCertV1` with `minSigs`
|
|
6
|
+
* (= `k − x`, default 14) signers. This module is db-core logic over the {@link ICohortThresholdCrypto}
|
|
7
|
+
* port; db-p2p binds the port to FRET's `minSigs` cohort-signature assembly — db-core never imports
|
|
8
|
+
* FRET, and the underlying scheme is reused unchanged.
|
|
9
|
+
*
|
|
10
|
+
* {@link CohortSigner.verifyThreshold} layers the membership check db-core owns on top of the raw
|
|
11
|
+
* crypto: the `signers` must be a distinct `≥ minSigs` subset of the certificate's `members`, *and*
|
|
12
|
+
* the signature must verify against the payload. Either failure → not verified.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { ICohortThresholdCrypto } from "../ports.js";
|
|
16
|
+
import { bytesToB64url } from "../wire/codec.js";
|
|
17
|
+
import type { MembershipCertV1 } from "../wire/types.js";
|
|
18
|
+
|
|
19
|
+
/** Default cohort-signature threshold, `k − x` (see §Configuration). */
|
|
20
|
+
export const DEFAULT_MIN_SIGS = 14;
|
|
21
|
+
|
|
22
|
+
/** Threshold signer/verifier over the injected cohort crypto. Peer ids are raw bytes. */
|
|
23
|
+
export interface CohortSigner {
|
|
24
|
+
/** Assemble a cohort threshold signature over `payload` (collects `minSigs` signers). */
|
|
25
|
+
thresholdSign(payload: Uint8Array): Promise<{ thresholdSig: Uint8Array; signers: Uint8Array[] }>;
|
|
26
|
+
/**
|
|
27
|
+
* Verify a threshold-signed message: `signers` are a distinct `≥ minSigs` subset of
|
|
28
|
+
* `cert.members` and `sig` is a valid cohort signature over `payload`.
|
|
29
|
+
*/
|
|
30
|
+
verifyThreshold(payload: Uint8Array, sig: Uint8Array, signers: readonly Uint8Array[], cert: MembershipCertV1, minSigs: number): boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
class CryptoCohortSigner implements CohortSigner {
|
|
34
|
+
constructor(private readonly crypto: ICohortThresholdCrypto, private readonly minSigs: number) {}
|
|
35
|
+
|
|
36
|
+
thresholdSign(payload: Uint8Array): Promise<{ thresholdSig: Uint8Array; signers: Uint8Array[] }> {
|
|
37
|
+
return this.crypto.assemble(payload, this.minSigs);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
verifyThreshold(payload: Uint8Array, sig: Uint8Array, signers: readonly Uint8Array[], cert: MembershipCertV1, minSigs: number): boolean {
|
|
41
|
+
if (signers.length < minSigs) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
// Members are base64url on the wire; compare signers in that same canonical form.
|
|
45
|
+
const memberSet = new Set(cert.members);
|
|
46
|
+
const seen = new Set<string>();
|
|
47
|
+
for (const signer of signers) {
|
|
48
|
+
const key = bytesToB64url(signer);
|
|
49
|
+
if (seen.has(key)) {
|
|
50
|
+
return false; // a duplicated signer cannot pad the count toward minSigs
|
|
51
|
+
}
|
|
52
|
+
seen.add(key);
|
|
53
|
+
if (!memberSet.has(key)) {
|
|
54
|
+
return false; // signer is not a member of the attested cohort
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return this.crypto.verify(payload, sig, signers);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Build a {@link CohortSigner} over the FRET-backed (in db-p2p) threshold crypto. */
|
|
62
|
+
export function createCohortSigner(crypto: ICohortThresholdCrypto, minSigs: number = DEFAULT_MIN_SIGS): CohortSigner {
|
|
63
|
+
return new CryptoCohortSigner(crypto, minSigs);
|
|
64
|
+
}
|
|
@@ -1,74 +1,74 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cohort-topic substrate — tier ladder (T0–T3) and node profiles (Edge / Core).
|
|
3
|
-
*
|
|
4
|
-
* Transcribed from `docs/cohort-topic.md` §Tier ladder. This module encodes only the **static**
|
|
5
|
-
* tier classes, the Edge/Core profiles, and the per-node override surface. The decline-policy
|
|
6
|
-
* *behavior* (T0 never declined while serving, T3 declined freely) and the Edge runtime overrides
|
|
7
|
-
* (`ttl = 60s`, `ping_interval = 20s`, sticky-cached backups) are willingness/TTL concerns owned by
|
|
8
|
-
* later tickets — only the profile *flags* (which tiers a node is willing to forward) live here.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* System-wide capacity tier, in priority order (T0 essential … T3 luxury).
|
|
13
|
-
*
|
|
14
|
-
* Modeled as a frozen const-object + value-union type rather than a TS `enum`: db-core's test
|
|
15
|
-
* runner uses Node's native type-stripping, which rejects `enum` (non-erasable syntax). The object
|
|
16
|
-
* carries the named members (`Tier.T0`), the type alias carries the value union (`0 | 1 | 2 | 3`).
|
|
17
|
-
*
|
|
18
|
-
* - **T0 — essential**: transaction commit, block production, threshold-sig contribution.
|
|
19
|
-
* - **T1 — correctness-supporting**: chain serving, replay-window storage, partition heal.
|
|
20
|
-
* - **T2 — functional**: matchmaking/voting directories, capability discovery, capacity gossip.
|
|
21
|
-
* - **T3 — luxury**: reactivity push forwarding, anticipatory warm-up, optional delta payloads.
|
|
22
|
-
*/
|
|
23
|
-
export const Tier = { T0: 0, T1: 1, T2: 2, T3: 3 } as const;
|
|
24
|
-
export type Tier = (typeof Tier)[keyof typeof Tier];
|
|
25
|
-
|
|
26
|
-
/** All four tiers, ascending — useful for iteration and override validation. */
|
|
27
|
-
export const ALL_TIERS: readonly Tier[] = [Tier.T0, Tier.T1, Tier.T2, Tier.T3];
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* A node's static tier classification. `willingTiers` is the set of tiers this node will take on
|
|
31
|
-
* *forwarder* roles for; cohort assembly under FRET is tier-blind, so this is the per-node gate the
|
|
32
|
-
* willingness check consults. Edge: `{T0, T1}`; Core: `{T0, T1, T2, T3}` (operator-narrowable).
|
|
33
|
-
*/
|
|
34
|
-
export interface NodeProfile {
|
|
35
|
-
readonly kind: "edge" | "core";
|
|
36
|
-
readonly willingTiers: ReadonlySet<Tier>;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** Per-node operator override surface. Currently the willing-tier set; widened by later tickets. */
|
|
40
|
-
export interface NodeProfileOverrides {
|
|
41
|
-
/**
|
|
42
|
-
* Restrict the willing-tier set. Tiers outside the profile's base set are ignored (Edge can never
|
|
43
|
-
* be widened to T2/T3 via override — that is a hardware/policy class, not a knob).
|
|
44
|
-
*/
|
|
45
|
-
willingTiers?: Iterable<Tier>;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const EDGE_BASE_TIERS: readonly Tier[] = [Tier.T0, Tier.T1];
|
|
49
|
-
const CORE_BASE_TIERS: readonly Tier[] = ALL_TIERS;
|
|
50
|
-
|
|
51
|
-
/** Intersect a requested override set with the profile's base tiers (override can only narrow). */
|
|
52
|
-
function resolveWillingTiers(base: readonly Tier[], overrides?: NodeProfileOverrides): ReadonlySet<Tier> {
|
|
53
|
-
if (!overrides?.willingTiers) {
|
|
54
|
-
return new Set(base);
|
|
55
|
-
}
|
|
56
|
-
const requested = new Set(overrides.willingTiers);
|
|
57
|
-
return new Set(base.filter((t) => requested.has(t)));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Edge profile (mobile / browser / IoT): forwards T0 + T1 only. T2/T3 willingness is permanently
|
|
62
|
-
* off and an operator override cannot turn it on. An override may further narrow (e.g. to `{T0}`).
|
|
63
|
-
*/
|
|
64
|
-
export function edgeProfile(overrides?: NodeProfileOverrides): NodeProfile {
|
|
65
|
-
return { kind: "edge", willingTiers: resolveWillingTiers(EDGE_BASE_TIERS, overrides) };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Core profile (servers / fixed infrastructure): forwards T0–T3 by default. An operator override may
|
|
70
|
-
* restrict it to a subset (e.g. `{T0, T1, T2}` to shed reactivity-push duty).
|
|
71
|
-
*/
|
|
72
|
-
export function coreProfile(overrides?: NodeProfileOverrides): NodeProfile {
|
|
73
|
-
return { kind: "core", willingTiers: resolveWillingTiers(CORE_BASE_TIERS, overrides) };
|
|
74
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Cohort-topic substrate — tier ladder (T0–T3) and node profiles (Edge / Core).
|
|
3
|
+
*
|
|
4
|
+
* Transcribed from `docs/cohort-topic.md` §Tier ladder. This module encodes only the **static**
|
|
5
|
+
* tier classes, the Edge/Core profiles, and the per-node override surface. The decline-policy
|
|
6
|
+
* *behavior* (T0 never declined while serving, T3 declined freely) and the Edge runtime overrides
|
|
7
|
+
* (`ttl = 60s`, `ping_interval = 20s`, sticky-cached backups) are willingness/TTL concerns owned by
|
|
8
|
+
* later tickets — only the profile *flags* (which tiers a node is willing to forward) live here.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* System-wide capacity tier, in priority order (T0 essential … T3 luxury).
|
|
13
|
+
*
|
|
14
|
+
* Modeled as a frozen const-object + value-union type rather than a TS `enum`: db-core's test
|
|
15
|
+
* runner uses Node's native type-stripping, which rejects `enum` (non-erasable syntax). The object
|
|
16
|
+
* carries the named members (`Tier.T0`), the type alias carries the value union (`0 | 1 | 2 | 3`).
|
|
17
|
+
*
|
|
18
|
+
* - **T0 — essential**: transaction commit, block production, threshold-sig contribution.
|
|
19
|
+
* - **T1 — correctness-supporting**: chain serving, replay-window storage, partition heal.
|
|
20
|
+
* - **T2 — functional**: matchmaking/voting directories, capability discovery, capacity gossip.
|
|
21
|
+
* - **T3 — luxury**: reactivity push forwarding, anticipatory warm-up, optional delta payloads.
|
|
22
|
+
*/
|
|
23
|
+
export const Tier = { T0: 0, T1: 1, T2: 2, T3: 3 } as const;
|
|
24
|
+
export type Tier = (typeof Tier)[keyof typeof Tier];
|
|
25
|
+
|
|
26
|
+
/** All four tiers, ascending — useful for iteration and override validation. */
|
|
27
|
+
export const ALL_TIERS: readonly Tier[] = [Tier.T0, Tier.T1, Tier.T2, Tier.T3];
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A node's static tier classification. `willingTiers` is the set of tiers this node will take on
|
|
31
|
+
* *forwarder* roles for; cohort assembly under FRET is tier-blind, so this is the per-node gate the
|
|
32
|
+
* willingness check consults. Edge: `{T0, T1}`; Core: `{T0, T1, T2, T3}` (operator-narrowable).
|
|
33
|
+
*/
|
|
34
|
+
export interface NodeProfile {
|
|
35
|
+
readonly kind: "edge" | "core";
|
|
36
|
+
readonly willingTiers: ReadonlySet<Tier>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Per-node operator override surface. Currently the willing-tier set; widened by later tickets. */
|
|
40
|
+
export interface NodeProfileOverrides {
|
|
41
|
+
/**
|
|
42
|
+
* Restrict the willing-tier set. Tiers outside the profile's base set are ignored (Edge can never
|
|
43
|
+
* be widened to T2/T3 via override — that is a hardware/policy class, not a knob).
|
|
44
|
+
*/
|
|
45
|
+
willingTiers?: Iterable<Tier>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const EDGE_BASE_TIERS: readonly Tier[] = [Tier.T0, Tier.T1];
|
|
49
|
+
const CORE_BASE_TIERS: readonly Tier[] = ALL_TIERS;
|
|
50
|
+
|
|
51
|
+
/** Intersect a requested override set with the profile's base tiers (override can only narrow). */
|
|
52
|
+
function resolveWillingTiers(base: readonly Tier[], overrides?: NodeProfileOverrides): ReadonlySet<Tier> {
|
|
53
|
+
if (!overrides?.willingTiers) {
|
|
54
|
+
return new Set(base);
|
|
55
|
+
}
|
|
56
|
+
const requested = new Set(overrides.willingTiers);
|
|
57
|
+
return new Set(base.filter((t) => requested.has(t)));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Edge profile (mobile / browser / IoT): forwards T0 + T1 only. T2/T3 willingness is permanently
|
|
62
|
+
* off and an operator override cannot turn it on. An override may further narrow (e.g. to `{T0}`).
|
|
63
|
+
*/
|
|
64
|
+
export function edgeProfile(overrides?: NodeProfileOverrides): NodeProfile {
|
|
65
|
+
return { kind: "edge", willingTiers: resolveWillingTiers(EDGE_BASE_TIERS, overrides) };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Core profile (servers / fixed infrastructure): forwards T0–T3 by default. An operator override may
|
|
70
|
+
* restrict it to a subset (e.g. `{T0, T1, T2}` to shed reactivity-push duty).
|
|
71
|
+
*/
|
|
72
|
+
export function coreProfile(overrides?: NodeProfileOverrides): NodeProfile {
|
|
73
|
+
return { kind: "core", willingTiers: resolveWillingTiers(CORE_BASE_TIERS, overrides) };
|
|
74
|
+
}
|