signalk-p2p-plugin 0.3.0 → 0.4.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/CHANGELOG.md +20 -0
- package/README.md +41 -14
- package/deploy/README.md +20 -2
- package/deploy/policy.json +30 -0
- package/dist/advanced-config.d.ts +11 -0
- package/dist/advanced-config.d.ts.map +1 -0
- package/dist/advanced-config.js +40 -0
- package/dist/advanced-config.js.map +1 -0
- package/dist/api.d.ts +14 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +41 -3
- package/dist/api.js.map +1 -1
- package/dist/config.d.ts +5 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +12 -273
- package/dist/config.js.map +1 -1
- package/dist/data/envelope.d.ts.map +1 -1
- package/dist/data/envelope.js +2 -0
- package/dist/data/envelope.js.map +1 -1
- package/dist/data/types.d.ts +1 -1
- package/dist/data/types.d.ts.map +1 -1
- package/dist/groups/control.d.ts +8 -0
- package/dist/groups/control.d.ts.map +1 -1
- package/dist/groups/control.js +30 -0
- package/dist/groups/control.js.map +1 -1
- package/dist/groups/manager.d.ts +2 -0
- package/dist/groups/manager.d.ts.map +1 -1
- package/dist/groups/manager.js +28 -1
- package/dist/groups/manager.js.map +1 -1
- package/dist/groups/types.d.ts +12 -0
- package/dist/groups/types.d.ts.map +1 -1
- package/dist/groups/types.js +23 -0
- package/dist/groups/types.js.map +1 -1
- package/dist/live/relay.d.ts +3 -0
- package/dist/live/relay.d.ts.map +1 -1
- package/dist/live/relay.js +11 -0
- package/dist/live/relay.js.map +1 -1
- package/dist/peer/cli.js +16 -0
- package/dist/peer/cli.js.map +1 -1
- package/dist/peer/config.d.ts +3 -0
- package/dist/peer/config.d.ts.map +1 -1
- package/dist/peer/config.js +5 -0
- package/dist/peer/config.js.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +136 -46
- package/dist/plugin.js.map +1 -1
- package/dist/policy/operator.d.ts +13 -0
- package/dist/policy/operator.d.ts.map +1 -0
- package/dist/policy/operator.js +53 -0
- package/dist/policy/operator.js.map +1 -0
- package/dist/policy/policy.d.ts +49 -0
- package/dist/policy/policy.d.ts.map +1 -0
- package/dist/policy/policy.js +147 -0
- package/dist/policy/policy.js.map +1 -0
- package/docs/plans/README.md +2 -2
- package/docs/plans/architecture.md +2 -2
- package/docs/plans/networks.md +35 -7
- package/docs/plans/peer-node.md +7 -2
- package/docs/plans/shared-data.md +4 -3
- package/package.json +3 -2
- package/policy/default.json +30 -0
- package/policy/operator.pub +2 -0
- package/public/index.html +268 -68
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { verifyDetached } from "../identity.js";
|
|
2
|
+
import { canonicalJson } from "../groups/types.js";
|
|
3
|
+
import { PROTOCOL_VERSION } from "../data/envelope.js";
|
|
4
|
+
import { ALL_VESSELS_TOPIC } from "../live/topic.js";
|
|
5
|
+
export const DEFAULT_POLICY_VERSION = 1;
|
|
6
|
+
export const MAX_POLICY_RATE_HZ = 10;
|
|
7
|
+
export function defaultPolicy(version = DEFAULT_POLICY_VERSION, issuedAt = Math.floor(Date.now() / 1000)) {
|
|
8
|
+
return {
|
|
9
|
+
version,
|
|
10
|
+
topic: ALL_VESSELS_TOPIC,
|
|
11
|
+
protocol: PROTOCOL_VERSION,
|
|
12
|
+
issuedAt,
|
|
13
|
+
sharding: {
|
|
14
|
+
maxVesselsPerShard: 500,
|
|
15
|
+
maxVesselsPerInstance: 1000,
|
|
16
|
+
lookAheadKm: 50,
|
|
17
|
+
splitSettleMinutes: 10,
|
|
18
|
+
mergeSettleMinutes: 60,
|
|
19
|
+
mergeBelowRatio: 0.25,
|
|
20
|
+
boundaryHysteresisMeters: 1000,
|
|
21
|
+
},
|
|
22
|
+
election: {
|
|
23
|
+
epochMinutes: 10,
|
|
24
|
+
minUptimeMinutes: 10,
|
|
25
|
+
},
|
|
26
|
+
rates: {
|
|
27
|
+
maxSnapshotHzUnderway: 0.05,
|
|
28
|
+
maxSnapshotHzMoored: 0.01,
|
|
29
|
+
},
|
|
30
|
+
ais: {
|
|
31
|
+
maxTargets: 100,
|
|
32
|
+
maxRateHz: 0.02,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export function policyBytes(policy) {
|
|
37
|
+
return Buffer.from(canonicalJson(policy));
|
|
38
|
+
}
|
|
39
|
+
export function signPolicy(policy, identity) {
|
|
40
|
+
return {
|
|
41
|
+
policy,
|
|
42
|
+
signature: identity.sign(policyBytes(policy)).toString("base64"),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export function verifyPolicy(signed, operatorKeys, expectedTopic, now = Math.floor(Date.now() / 1000)) {
|
|
46
|
+
if (!validPolicy(signed.policy, expectedTopic, now))
|
|
47
|
+
return false;
|
|
48
|
+
const bytes = policyBytes(signed.policy);
|
|
49
|
+
const signature = Buffer.from(signed.signature, "base64");
|
|
50
|
+
for (const key of operatorKeys) {
|
|
51
|
+
if (!/^[0-9a-f]{64}$/i.test(key))
|
|
52
|
+
continue;
|
|
53
|
+
if (verifyDetached(Buffer.from(key, "hex"), bytes, signature))
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
export function validPolicy(policy, expectedTopic, now = Math.floor(Date.now() / 1000)) {
|
|
59
|
+
if (!Number.isInteger(policy.version) || policy.version < 1)
|
|
60
|
+
return false;
|
|
61
|
+
if (policy.topic !== expectedTopic)
|
|
62
|
+
return false;
|
|
63
|
+
if (policy.protocol !== PROTOCOL_VERSION)
|
|
64
|
+
return false;
|
|
65
|
+
if (!Number.isInteger(policy.issuedAt) || policy.issuedAt < 0)
|
|
66
|
+
return false;
|
|
67
|
+
if (policy.notBefore !== undefined &&
|
|
68
|
+
(!Number.isInteger(policy.notBefore) || policy.notBefore > now)) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
if (policy.notAfter !== undefined &&
|
|
72
|
+
policy.notAfter > 0 &&
|
|
73
|
+
(!Number.isInteger(policy.notAfter) || policy.notAfter < now)) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
const sharding = policy.sharding;
|
|
77
|
+
if (!sharding || typeof sharding !== "object")
|
|
78
|
+
return false;
|
|
79
|
+
if (!positive(sharding.maxVesselsPerShard))
|
|
80
|
+
return false;
|
|
81
|
+
if (!positive(sharding.maxVesselsPerInstance))
|
|
82
|
+
return false;
|
|
83
|
+
if (!nonNegative(sharding.lookAheadKm))
|
|
84
|
+
return false;
|
|
85
|
+
if (!nonNegative(sharding.splitSettleMinutes))
|
|
86
|
+
return false;
|
|
87
|
+
if (!nonNegative(sharding.mergeSettleMinutes))
|
|
88
|
+
return false;
|
|
89
|
+
if (typeof sharding.mergeBelowRatio !== "number" ||
|
|
90
|
+
sharding.mergeBelowRatio < 0 ||
|
|
91
|
+
sharding.mergeBelowRatio > 1) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
if (!nonNegative(sharding.boundaryHysteresisMeters))
|
|
95
|
+
return false;
|
|
96
|
+
const election = policy.election;
|
|
97
|
+
if (!election || typeof election !== "object")
|
|
98
|
+
return false;
|
|
99
|
+
if (!positive(election.epochMinutes))
|
|
100
|
+
return false;
|
|
101
|
+
if (!nonNegative(election.minUptimeMinutes))
|
|
102
|
+
return false;
|
|
103
|
+
const rates = policy.rates;
|
|
104
|
+
if (!rates || typeof rates !== "object")
|
|
105
|
+
return false;
|
|
106
|
+
if (!rate(rates.maxSnapshotHzUnderway))
|
|
107
|
+
return false;
|
|
108
|
+
if (!rate(rates.maxSnapshotHzMoored))
|
|
109
|
+
return false;
|
|
110
|
+
const ais = policy.ais;
|
|
111
|
+
if (!ais || typeof ais !== "object")
|
|
112
|
+
return false;
|
|
113
|
+
if (!nonNegative(ais.maxTargets))
|
|
114
|
+
return false;
|
|
115
|
+
if (!rate(ais.maxRateHz, true))
|
|
116
|
+
return false;
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
export function encodePolicy(signed) {
|
|
120
|
+
return Buffer.from(JSON.stringify(signed), "utf8");
|
|
121
|
+
}
|
|
122
|
+
export function decodePolicy(data) {
|
|
123
|
+
const parsed = JSON.parse(data.toString("utf8"));
|
|
124
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
125
|
+
throw new Error("policy: expected an object");
|
|
126
|
+
}
|
|
127
|
+
const signed = parsed;
|
|
128
|
+
if (signed.policy === null ||
|
|
129
|
+
typeof signed.policy !== "object" ||
|
|
130
|
+
typeof signed.signature !== "string") {
|
|
131
|
+
throw new Error("policy: malformed record");
|
|
132
|
+
}
|
|
133
|
+
return signed;
|
|
134
|
+
}
|
|
135
|
+
function positive(value) {
|
|
136
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
137
|
+
}
|
|
138
|
+
function nonNegative(value) {
|
|
139
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
140
|
+
}
|
|
141
|
+
function rate(value, allowZero = false) {
|
|
142
|
+
return (typeof value === "number" &&
|
|
143
|
+
Number.isFinite(value) &&
|
|
144
|
+
(allowZero ? value >= 0 : value > 0) &&
|
|
145
|
+
value <= MAX_POLICY_RATE_HZ);
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.js","sourceRoot":"","sources":["../../src/policy/policy.ts"],"names":[],"mappings":"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"}
|
package/docs/plans/README.md
CHANGED
|
@@ -37,13 +37,13 @@ of private groups.
|
|
|
37
37
|
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
38
38
|
| Stack | Holepunch: Hyperswarm + `@hyperswarm/secret-stream` + Protomux + Hypercore/Hyperbee + Autobase. |
|
|
39
39
|
| Transport | Live plane = generation-rotated Hypercore channels; durable plane = Hypercore/Hyperbee/Autobase. |
|
|
40
|
-
| Public network | `all-vessels`, joined
|
|
40
|
+
| Public network | `all-vessels`, joined by default; transmission is opt-in (`receive-only` default), with four consent modes. |
|
|
41
41
|
| Sharding | Global by default; adaptive geographic shards that subdivide to keep a configured vessel count. |
|
|
42
42
|
| Relays | No fixed relays; a peer-elected aggregator per shard, gossip fallback, optional standalone bootstrap peer. |
|
|
43
43
|
| Identity | Persistent Ed25519 keypair per instance; groups keyed by identity, not by a shared secret. |
|
|
44
44
|
| Groups | Arbitrary number; roles, signed roster, simple invite links, re-key and member removal. |
|
|
45
45
|
| Injection | Received vessels become real Signal K contexts; local data wins; no bridging between networks. |
|
|
46
|
-
| Shared data | Per-data-set
|
|
46
|
+
| Shared data | Per-data-set matrix in the webapp (Public + active groups); curated defaults (no `electrical.*`). |
|
|
47
47
|
| Security | One authorized writer per context; groups approve the member's MMSI; public uses a claim registry + attestors (MMSI ownership unproven). |
|
|
48
48
|
|
|
49
49
|
## Still open
|
|
@@ -13,8 +13,8 @@ of the design must not violate.
|
|
|
13
13
|
**Goal.** Let Signal K instances form peer-to-peer meshes and share vessel data in
|
|
14
14
|
real time:
|
|
15
15
|
|
|
16
|
-
- one **global public network** (`all-vessels`), joined
|
|
17
|
-
default
|
|
16
|
+
- one **global public network** (`all-vessels`), joined with a receive-only
|
|
17
|
+
default; transmission and consent are per-instance;
|
|
18
18
|
- any number of **private groups**, invite-only and encrypted.
|
|
19
19
|
|
|
20
20
|
A vessel may participate in both at once, with a different view of its data in
|
package/docs/plans/networks.md
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
Two classes of network, both live at once:
|
|
6
6
|
|
|
7
|
-
- **`all-vessels`** — one global public network, joined
|
|
8
|
-
default
|
|
7
|
+
- **`all-vessels`** — one global public network, joined with a receive-only
|
|
8
|
+
default; transmission is opt-in; implemented as adaptive geographic shards.
|
|
9
9
|
- **Groups** — any number of private, encrypted, invite-only networks with roles,
|
|
10
10
|
a signed roster, and key rotation.
|
|
11
11
|
|
|
@@ -39,12 +39,16 @@ according to that network's policy, and ingests what each network carries.
|
|
|
39
39
|
`H("signalk-p2p/all-vessels/v1")`, so every instance agrees without coordination.
|
|
40
40
|
- **Open join.** No membership, no invite, no encryption. Identity is a
|
|
41
41
|
pseudonymous Ed25519 key.
|
|
42
|
-
- **Default on.** The plugin joins
|
|
42
|
+
- **Default on, receive-only.** The plugin joins immediately but publishes
|
|
43
|
+
nothing until the owner chooses a consent mode. `receive-only`,
|
|
44
|
+
`underway-gated`, `coarse-position` and `opt-out` are available.
|
|
43
45
|
- **Audiences** follow [`shared-data.md`](shared-data.md): each data set is shared
|
|
44
|
-
publicly, with selected groups, or not at all
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
publicly, with selected groups, or not at all, using the webapp matrix.
|
|
47
|
+
- **AIS** defaults to receive-only/private, capped and sampled by policy.
|
|
48
|
+
- **Topology and rates are centrally controlled.** Shard sizes, look-ahead,
|
|
49
|
+
split/merge thresholds, election epoch/min-uptime, public snapshot rate caps
|
|
50
|
+
and AIS caps come from a signed, versioned `NetworkPolicy`; clients cannot
|
|
51
|
+
override them. See §2.1.
|
|
48
52
|
- **No fixed relays.** The mesh elects a relay/aggregator per shard from
|
|
49
53
|
reachable peers; bootstrap peers are optional and replaceable.
|
|
50
54
|
- **Write authorization** is enforced by a replicated claim registry: each context
|
|
@@ -55,6 +59,30 @@ according to that network's policy, and ingests what each network carries.
|
|
|
55
59
|
The user-visible model is one network. Under the hood it is a set of geographic
|
|
56
60
|
shards (§3), so no instance replicates the whole world.
|
|
57
61
|
|
|
62
|
+
### 2.1 Network policy
|
|
63
|
+
|
|
64
|
+
A small signed policy carries the network-wide settings:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
NetworkPolicy {
|
|
68
|
+
version, issuedAt, notBefore?, notAfter?
|
|
69
|
+
topic, protocol
|
|
70
|
+
sharding { maxVesselsPerShard, maxVesselsPerInstance, lookAheadKm,
|
|
71
|
+
splitSettleMinutes, mergeSettleMinutes, mergeBelowRatio,
|
|
72
|
+
boundaryHysteresisMeters }
|
|
73
|
+
election { epochMinutes, minUptimeMinutes }
|
|
74
|
+
rates { maxSnapshotHzUnderway, maxSnapshotHzMoored }
|
|
75
|
+
ais { maxTargets, maxRateHz }
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
It is signed by the network operator. A signed default ships with the plugin; a
|
|
80
|
+
newer signed policy received over the mesh overrides it and is cached. Clients
|
|
81
|
+
verify the signature against pinned operator keys, reject rollback and invalid
|
|
82
|
+
values, and fall back to the shipped default if no live policy is available.
|
|
83
|
+
The operator's private key never goes to a relay; the standalone peer can
|
|
84
|
+
republish a signed policy file.
|
|
85
|
+
|
|
58
86
|
Write authorization, group approval and the public claim registry are specified in
|
|
59
87
|
[`security.md`](security.md).
|
|
60
88
|
|
package/docs/plans/peer-node.md
CHANGED
|
@@ -62,7 +62,8 @@ npx signalk-p2p-peer \
|
|
|
62
62
|
--topic signalk-p2p/all-vessels/v1 \
|
|
63
63
|
--batch-interval 2000 \
|
|
64
64
|
--history 10 \
|
|
65
|
-
--port 49737
|
|
65
|
+
--port 49737 \
|
|
66
|
+
--policy /etc/signalk-p2p/policy.json
|
|
66
67
|
```
|
|
67
68
|
|
|
68
69
|
During development:
|
|
@@ -81,10 +82,14 @@ npm run peer:start # compiled dist/peer/cli.js
|
|
|
81
82
|
"batchIntervalMs": 2000,
|
|
82
83
|
"maxBatchBytes": 65536,
|
|
83
84
|
"historyBatches": 10,
|
|
84
|
-
"port": 49737
|
|
85
|
+
"port": 49737,
|
|
86
|
+
"policyFile": "/etc/signalk-p2p/policy.json"
|
|
85
87
|
}
|
|
86
88
|
```
|
|
87
89
|
|
|
90
|
+
`policyFile` is optional. When set, the peer verifies and republishes the signed
|
|
91
|
+
network policy every five minutes; it never needs the operator private key.
|
|
92
|
+
|
|
88
93
|
```bash
|
|
89
94
|
npx signalk-p2p-peer --config /etc/signalk-p2p/peer.json
|
|
90
95
|
```
|
|
@@ -237,7 +237,8 @@ A group that is removed or left is dropped from every data set's group list.
|
|
|
237
237
|
|
|
238
238
|
## 4. Consent modes (public audience)
|
|
239
239
|
|
|
240
|
-
The public network is joined
|
|
240
|
+
The public network is joined by default but **receive-only** until the owner opts
|
|
241
|
+
in; "transmitting" is
|
|
241
242
|
configurable. The mode applies to position-bearing data sets (Position and Depth)
|
|
242
243
|
when they are shared publicly.
|
|
243
244
|
|
|
@@ -248,7 +249,7 @@ when they are shared publicly.
|
|
|
248
249
|
| `receive-only` | Join and ingest, but publish nothing of your own. | Maximum privacy; no contribution. |
|
|
249
250
|
| `coarse-position` | Publish position rounded to a grid (default 5 km) instead of the exact fix. | Approximate only; not anonymity. |
|
|
250
251
|
|
|
251
|
-
The mode is per network. Groups use the same modes and default to `
|
|
252
|
+
The mode is per network. Groups use the same modes and default to `receive-only` (a
|
|
252
253
|
group is already a trusted audience). A boat can be `opt-out` to its club and
|
|
253
254
|
`underway-gated` to the world.
|
|
254
255
|
|
|
@@ -311,7 +312,7 @@ The AIS data set uses the same three-way control as the others:
|
|
|
311
312
|
- **Share with groups** — publish into the selected groups;
|
|
312
313
|
- **Don't share** — no AIS over P2P.
|
|
313
314
|
|
|
314
|
-
Defaults: public **
|
|
315
|
+
Defaults: public **off**, groups **off**; AIS is receive-only/private by default.
|
|
315
316
|
|
|
316
317
|
### 6.2 Direction
|
|
317
318
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "signalk-p2p-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Private and public peer-to-peer mesh networks for Signal K vessels.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"signalk-node-server-plugin",
|
|
@@ -46,7 +46,8 @@
|
|
|
46
46
|
"deploy",
|
|
47
47
|
"public",
|
|
48
48
|
"icon.svg",
|
|
49
|
-
"CHANGELOG.md"
|
|
49
|
+
"CHANGELOG.md",
|
|
50
|
+
"policy"
|
|
50
51
|
],
|
|
51
52
|
"scripts": {
|
|
52
53
|
"build": "tsc -b",
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"policy": {
|
|
3
|
+
"version": 1,
|
|
4
|
+
"topic": "signalk-p2p/all-vessels/v1",
|
|
5
|
+
"protocol": 1,
|
|
6
|
+
"issuedAt": 1794023095,
|
|
7
|
+
"sharding": {
|
|
8
|
+
"maxVesselsPerShard": 500,
|
|
9
|
+
"maxVesselsPerInstance": 1000,
|
|
10
|
+
"lookAheadKm": 50,
|
|
11
|
+
"splitSettleMinutes": 10,
|
|
12
|
+
"mergeSettleMinutes": 60,
|
|
13
|
+
"mergeBelowRatio": 0.25,
|
|
14
|
+
"boundaryHysteresisMeters": 1000
|
|
15
|
+
},
|
|
16
|
+
"election": {
|
|
17
|
+
"epochMinutes": 10,
|
|
18
|
+
"minUptimeMinutes": 10
|
|
19
|
+
},
|
|
20
|
+
"rates": {
|
|
21
|
+
"maxSnapshotHzUnderway": 0.05,
|
|
22
|
+
"maxSnapshotHzMoored": 0.01
|
|
23
|
+
},
|
|
24
|
+
"ais": {
|
|
25
|
+
"maxTargets": 100,
|
|
26
|
+
"maxRateHz": 0.02
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"signature": "bnGdM4p1nOlYh9oudCtSt5dBvhlQFQwkg3gYedh+/SothLJKuhDdy7qoKxLTTYp6RaVp8XTL2Dj4BDcR12l9CA=="
|
|
30
|
+
}
|