@twinsearth/w2m-dsh-plugin 0.16.0 → 0.18.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 +73 -0
- package/package.json +5 -4
- package/src/agent/crypto.mjs +133 -2
- package/src/plugin/tools.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,79 @@ All notable changes to this project are documented here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
|
|
5
5
|
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.18.0] — 2026-10-15
|
|
8
|
+
|
|
9
|
+
**The whole handshake primitive set is present on both machines — so the P2P failure is not a missing algorithm.**
|
|
10
|
+
|
|
11
|
+
Two candidates for `P2P_PUNCH_FAILED: Digest method not supported` have now been **measured and excluded**:
|
|
12
|
+
|
|
13
|
+
| Candidate | Measurement | Verdict |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| BLAKE2s-256 | 32-byte digest on MacMini **and** here, on the same run that failed | **innocent** |
|
|
16
|
+
| every other primitive the handshake touches | `sha256`, `chacha20-poly1305` with a real AEAD round trip, `hkdfSync('sha256')`, `x25519`+`diffieHellman`, `ed25519` — all present on both | **innocent** |
|
|
17
|
+
|
|
18
|
+
The second row was answered by **one bit-encoded dispatch** that put eight questions to MacMini at once
|
|
19
|
+
(`exit_code: 255`, every bit set) rather than one question per round. That technique was already in this
|
|
20
|
+
repository and should have been the first move.
|
|
21
|
+
|
|
22
|
+
**What this changes:** the search moves off the runtime and onto the protocol, the session state, or the
|
|
23
|
+
transport. And it points somewhere specific — **`P2P_PUNCH_FAILED` is raised by the relay's own push, on the
|
|
24
|
+
machine that already has every primitive it needs.** The failing handshake is not on a peer missing an
|
|
25
|
+
algorithm; it is on a peer that has all of them.
|
|
26
|
+
|
|
27
|
+
`cryptoCapabilities()` now answers this question in one dispatch for any machine, and its suite asserts the
|
|
28
|
+
**count** — six primitives, six checks — because a capability report that silently checks four things reads as
|
|
29
|
+
complete.
|
|
30
|
+
|
|
31
|
+
**Honest status: the cause of `P2P_PUNCH_FAILED` remains UNVERIFIED**, and the 0 % direct share measured last
|
|
32
|
+
round is unchanged. What this release buys is that two hypotheses are dead and the next one costs one
|
|
33
|
+
dispatch to test.
|
|
34
|
+
|
|
35
|
+
## [0.17.0] — 2026-10-15
|
|
36
|
+
|
|
37
|
+
**BLAKE2s-256 is not the cause of `P2P_PUNCH_FAILED`, and a failure that names nothing costs a probe per guess.**
|
|
38
|
+
|
|
39
|
+
### The hypothesis, and its refutation
|
|
40
|
+
|
|
41
|
+
`P2P_PUNCH_FAILED: Digest method not supported` has been reproduced twice in production and never under
|
|
42
|
+
instrumentation. The obvious candidate was `createHash('blake2s256')` in `handshakeHash` — an algorithm that
|
|
43
|
+
some Node builds lack, whose absence produces exactly that string.
|
|
44
|
+
|
|
45
|
+
**Measured, and refuted:** both MacMini and this Windows host compute a 32-byte BLAKE2s-256 digest, on the
|
|
46
|
+
same run that reported the failure. `blake2s256` is available on both. **So the algorithm is not the cause**,
|
|
47
|
+
and the real cause remains **UNVERIFIED**.
|
|
48
|
+
|
|
49
|
+
### What changed anyway, because the failure's *shape* is the real defect
|
|
50
|
+
|
|
51
|
+
OpenSSL's message names **no algorithm, no caller and no host**. Establishing that BLAKE2s was innocent
|
|
52
|
+
therefore required probing each machine by hand, over a channel that carries one exit code — a round trip per
|
|
53
|
+
hypothesis. `handshakeHash` now:
|
|
54
|
+
|
|
55
|
+
* catches the unavailable case and throws `CRYPTO_BLAKE2S256_UNAVAILABLE` naming the algorithm, the runtime,
|
|
56
|
+
and the fact that **it is a wire-protocol primitive which cannot be replaced with SHA-256** without breaking
|
|
57
|
+
interoperability with every peer — so nobody "fixes" it by substituting an algorithm and silently splits the
|
|
58
|
+
fleet;
|
|
59
|
+
* refuses a digest that is not 32 bytes (`CRYPTO_BLAKE2S256_WRONG_LENGTH`), because a truncated digest throws
|
|
60
|
+
nowhere and would surface as an authentication failure far from its cause;
|
|
61
|
+
* exposes `blake2s256Support()` so a machine can answer the question about itself in one dispatch.
|
|
62
|
+
|
|
63
|
+
The wrapper is transparent on the happy path, and a test asserts it byte-for-byte against a direct
|
|
64
|
+
`createHash` call — if it changed the bytes, every peer would compute a different handshake.
|
|
65
|
+
|
|
66
|
+
### A defect in the fix itself, found before shipping
|
|
67
|
+
|
|
68
|
+
The first version called `getHashes()` without importing it, so `typeof getHashes === 'function'` was silently
|
|
69
|
+
`false` and the `advertised` flag would have been wrong forever. Caught by inspecting the import block rather
|
|
70
|
+
than by a test, which is the honest description of how it was caught.
|
|
71
|
+
|
|
72
|
+
### The 0 % direct share stands
|
|
73
|
+
|
|
74
|
+
Last round measured **12 of 12 tasks carried by the relay**, at RTT 354 ms (macmini) and 336 ms (ToFuture)
|
|
75
|
+
where jp-shared answers in 20 ms. This release does not improve that number; it makes the next attempt at
|
|
76
|
+
diagnosing it cheaper.
|
|
77
|
+
|
|
78
|
+
**10 new tests, 0 failures.**
|
|
79
|
+
|
|
7
80
|
## [0.16.0] — 2026-10-15
|
|
8
81
|
|
|
9
82
|
**A non-zero exit code is not a fleet failure, and this session conflated them repeatedly.**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twinsearth/w2m-dsh-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "DSH: Windows2MacOS — drive the same project on online Windows and macOS machines from one DeepSeek account, across networks and regions. P2P hole punching is the default path (relay + shared server as rendezvous, ledger and fallback), with a bundled STUN responder, Tailscale/public-VPS/tunnel shapes, Rabbit relay + per-machine Localside + eight DSH tools (replicate / split), nine aggregate verdicts and signed requests.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -113,13 +113,13 @@
|
|
|
113
113
|
}
|
|
114
114
|
},
|
|
115
115
|
"scripts": {
|
|
116
|
-
"test": "node --test --test-force-exit test/relay.test.mjs test/broadcast.test.mjs test/agent.test.mjs test/authority.test.mjs test/blocks.test.mjs test/approval-index.test.mjs test/fleet-sync.test.mjs test/health.test.mjs test/storage.test.mjs test/exit-channel.test.mjs test/council.test.mjs test/log-bounds.test.mjs test/test-report.test.mjs test/fleet-scale.test.mjs test/fleet-state.test.mjs test/layer-model.test.mjs test/memory.test.mjs test/tools.test.mjs test/claims.test.mjs test/fleet-target.test.mjs test/client-bundle.test.mjs test/schedule.test.mjs test/auto-update.test.mjs test/update-source.test.mjs test/update-install.test.mjs test/update-wiring.test.mjs test/signing.test.mjs test/stress.test.mjs test/shared-config.test.mjs test/metrics.test.mjs test/metrics-route.test.mjs test/tool-cards.test.mjs test/p2p-stun.test.mjs test/p2p-transport.test.mjs test/p2p-signaling.test.mjs test/p2p-node.test.mjs test/p2p-transport-fields.test.mjs test/p2p-plugin.test.mjs test/p2p-crypto.test.mjs test/stun-server.test.mjs test/loader-entry.test.mjs test/mdns-wire.test.mjs test/mdns-discover.test.mjs test/chatops-approval.test.mjs test/chatops-policy.test.mjs test/chatops-file-safety.test.mjs test/chatops-outbound-queue.test.mjs test/chatops-commands.test.mjs test/chatops-bridge.test.mjs test/chatops-config-route.test.mjs test/chatops-apply.test.mjs test/chatops-ilink-contract.test.mjs test/chatops-fleet.test.mjs test/chatops-account.test.mjs test/chatops-auth.test.mjs test/chatops-fleet-scope.test.mjs test/chatops-handoff.test.mjs test/chatops-fleet-wiring.test.mjs test/chatops-fleet-live.test.mjs test/chatops-takeover.test.mjs test/chatops-device-signing.test.mjs test/chatops-relay-verify.test.mjs test/chatops-durability.test.mjs test/chatops-run-command.test.mjs test/secrets-guard.test.mjs test/chatops-ilink-fleet-bridge.test.mjs test/chatops-fleet-join.test.mjs",
|
|
116
|
+
"test": "node --test --test-force-exit test/relay.test.mjs test/broadcast.test.mjs test/agent.test.mjs test/authority.test.mjs test/blocks.test.mjs test/approval-index.test.mjs test/fleet-sync.test.mjs test/health.test.mjs test/storage.test.mjs test/exit-channel.test.mjs test/council.test.mjs test/log-bounds.test.mjs test/test-report.test.mjs test/fleet-scale.test.mjs test/fleet-state.test.mjs test/crypto-blake2s.test.mjs test/layer-model.test.mjs test/memory.test.mjs test/tools.test.mjs test/claims.test.mjs test/fleet-target.test.mjs test/client-bundle.test.mjs test/schedule.test.mjs test/auto-update.test.mjs test/update-source.test.mjs test/update-install.test.mjs test/update-wiring.test.mjs test/signing.test.mjs test/stress.test.mjs test/shared-config.test.mjs test/metrics.test.mjs test/metrics-route.test.mjs test/tool-cards.test.mjs test/p2p-stun.test.mjs test/p2p-transport.test.mjs test/p2p-signaling.test.mjs test/p2p-node.test.mjs test/p2p-transport-fields.test.mjs test/p2p-plugin.test.mjs test/p2p-crypto.test.mjs test/stun-server.test.mjs test/loader-entry.test.mjs test/mdns-wire.test.mjs test/mdns-discover.test.mjs test/chatops-approval.test.mjs test/chatops-policy.test.mjs test/chatops-file-safety.test.mjs test/chatops-outbound-queue.test.mjs test/chatops-commands.test.mjs test/chatops-bridge.test.mjs test/chatops-config-route.test.mjs test/chatops-apply.test.mjs test/chatops-ilink-contract.test.mjs test/chatops-fleet.test.mjs test/chatops-account.test.mjs test/chatops-auth.test.mjs test/chatops-fleet-scope.test.mjs test/chatops-handoff.test.mjs test/chatops-fleet-wiring.test.mjs test/chatops-fleet-live.test.mjs test/chatops-takeover.test.mjs test/chatops-device-signing.test.mjs test/chatops-relay-verify.test.mjs test/chatops-durability.test.mjs test/chatops-run-command.test.mjs test/secrets-guard.test.mjs test/chatops-ilink-fleet-bridge.test.mjs test/chatops-fleet-join.test.mjs",
|
|
117
117
|
"test:e2e": "node --test --test-force-exit test/e2e.test.mjs test/crossnetwork.test.mjs test/signing-e2e.test.mjs test/outbound-only.test.mjs test/recovery.test.mjs test/pipeline-e2e.test.mjs test/p2p-agent.test.mjs",
|
|
118
118
|
"test:relay": "node --test --test-force-exit test/relay.test.mjs",
|
|
119
119
|
"test:agent": "node --test --test-force-exit test/agent.test.mjs test/memory.test.mjs",
|
|
120
120
|
"test:tools": "node --test --test-force-exit test/tools.test.mjs",
|
|
121
121
|
"test:crossnetwork": "node --test --test-force-exit test/crossnetwork.test.mjs",
|
|
122
|
-
"test:all": "node --test --test-force-exit test/relay.test.mjs test/broadcast.test.mjs test/agent.test.mjs test/authority.test.mjs test/blocks.test.mjs test/approval-index.test.mjs test/fleet-sync.test.mjs test/health.test.mjs test/storage.test.mjs test/exit-channel.test.mjs test/council.test.mjs test/log-bounds.test.mjs test/test-report.test.mjs test/fleet-scale.test.mjs test/fleet-state.test.mjs test/layer-model.test.mjs test/memory.test.mjs test/tools.test.mjs test/claims.test.mjs test/fleet-target.test.mjs test/client-bundle.test.mjs test/schedule.test.mjs test/auto-update.test.mjs test/update-source.test.mjs test/update-install.test.mjs test/update-wiring.test.mjs test/signing.test.mjs test/stress.test.mjs test/e2e.test.mjs test/crossnetwork.test.mjs test/signing-e2e.test.mjs test/outbound-only.test.mjs test/recovery.test.mjs test/pipeline-e2e.test.mjs test/shared-config.test.mjs test/metrics.test.mjs test/metrics-route.test.mjs test/tool-cards.test.mjs test/p2p-stun.test.mjs test/p2p-transport.test.mjs test/p2p-signaling.test.mjs test/p2p-node.test.mjs test/p2p-transport-fields.test.mjs test/p2p-plugin.test.mjs test/p2p-agent.test.mjs test/stun-server.test.mjs test/loader-entry.test.mjs test/mdns-wire.test.mjs test/mdns-discover.test.mjs test/p2p-crypto.test.mjs test/chatops-approval.test.mjs test/chatops-policy.test.mjs test/chatops-file-safety.test.mjs test/chatops-outbound-queue.test.mjs test/chatops-commands.test.mjs test/chatops-bridge.test.mjs test/chatops-config-route.test.mjs test/chatops-apply.test.mjs test/chatops-ilink-contract.test.mjs test/chatops-fleet.test.mjs test/chatops-account.test.mjs test/chatops-auth.test.mjs test/chatops-fleet-scope.test.mjs test/chatops-handoff.test.mjs test/chatops-fleet-wiring.test.mjs test/chatops-fleet-live.test.mjs test/chatops-takeover.test.mjs test/chatops-device-signing.test.mjs test/chatops-relay-verify.test.mjs test/chatops-durability.test.mjs test/chatops-run-command.test.mjs test/secrets-guard.test.mjs test/chatops-ilink-fleet-bridge.test.mjs test/chatops-fleet-join.test.mjs",
|
|
122
|
+
"test:all": "node --test --test-force-exit test/relay.test.mjs test/broadcast.test.mjs test/agent.test.mjs test/authority.test.mjs test/blocks.test.mjs test/approval-index.test.mjs test/fleet-sync.test.mjs test/health.test.mjs test/storage.test.mjs test/exit-channel.test.mjs test/council.test.mjs test/log-bounds.test.mjs test/test-report.test.mjs test/fleet-scale.test.mjs test/fleet-state.test.mjs test/crypto-blake2s.test.mjs test/layer-model.test.mjs test/memory.test.mjs test/tools.test.mjs test/claims.test.mjs test/fleet-target.test.mjs test/client-bundle.test.mjs test/schedule.test.mjs test/auto-update.test.mjs test/update-source.test.mjs test/update-install.test.mjs test/update-wiring.test.mjs test/signing.test.mjs test/stress.test.mjs test/e2e.test.mjs test/crossnetwork.test.mjs test/signing-e2e.test.mjs test/outbound-only.test.mjs test/recovery.test.mjs test/pipeline-e2e.test.mjs test/shared-config.test.mjs test/metrics.test.mjs test/metrics-route.test.mjs test/tool-cards.test.mjs test/p2p-stun.test.mjs test/p2p-transport.test.mjs test/p2p-signaling.test.mjs test/p2p-node.test.mjs test/p2p-transport-fields.test.mjs test/p2p-plugin.test.mjs test/p2p-agent.test.mjs test/stun-server.test.mjs test/loader-entry.test.mjs test/mdns-wire.test.mjs test/mdns-discover.test.mjs test/p2p-crypto.test.mjs test/chatops-approval.test.mjs test/chatops-policy.test.mjs test/chatops-file-safety.test.mjs test/chatops-outbound-queue.test.mjs test/chatops-commands.test.mjs test/chatops-bridge.test.mjs test/chatops-config-route.test.mjs test/chatops-apply.test.mjs test/chatops-ilink-contract.test.mjs test/chatops-fleet.test.mjs test/chatops-account.test.mjs test/chatops-auth.test.mjs test/chatops-fleet-scope.test.mjs test/chatops-handoff.test.mjs test/chatops-fleet-wiring.test.mjs test/chatops-fleet-live.test.mjs test/chatops-takeover.test.mjs test/chatops-device-signing.test.mjs test/chatops-relay-verify.test.mjs test/chatops-durability.test.mjs test/chatops-run-command.test.mjs test/secrets-guard.test.mjs test/chatops-ilink-fleet-bridge.test.mjs test/chatops-fleet-join.test.mjs",
|
|
123
123
|
"pack": "node scripts/pack.mjs",
|
|
124
124
|
"verify:join": "node scripts/verify-join.mjs",
|
|
125
125
|
"lint": "eslint .",
|
|
@@ -142,7 +142,8 @@
|
|
|
142
142
|
"layer-check": "node scripts/layer-check.mjs",
|
|
143
143
|
"test:test-report": "node --test --test-force-exit test/test-report.test.mjs",
|
|
144
144
|
"test:fleet-scale": "node --test --test-force-exit test/fleet-scale.test.mjs",
|
|
145
|
-
"test:fleet-state": "node --test --test-force-exit test/fleet-state.test.mjs"
|
|
145
|
+
"test:fleet-state": "node --test --test-force-exit test/fleet-state.test.mjs",
|
|
146
|
+
"test:crypto-blake2s": "node --test --test-force-exit test/crypto-blake2s.test.mjs"
|
|
146
147
|
},
|
|
147
148
|
"keywords": [
|
|
148
149
|
"dsh-bundle",
|
package/src/agent/crypto.mjs
CHANGED
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
createPublicKey,
|
|
49
49
|
diffieHellman,
|
|
50
50
|
generateKeyPairSync,
|
|
51
|
+
getHashes,
|
|
51
52
|
hkdfSync,
|
|
52
53
|
randomBytes,
|
|
53
54
|
timingSafeEqual,
|
|
@@ -703,16 +704,146 @@ export function derive(ikm, label, length = KEY_BYTES) {
|
|
|
703
704
|
return Buffer.from(hkdfSync('sha256', ikm, Buffer.alloc(0), Buffer.from(label, 'utf8'), length));
|
|
704
705
|
}
|
|
705
706
|
|
|
707
|
+
/**
|
|
708
|
+
* The cryptographic primitives the handshake requires, and whether this runtime has each.
|
|
709
|
+
*
|
|
710
|
+
* ## Why a capability report rather than a one-off diagnostic
|
|
711
|
+
*
|
|
712
|
+
* `P2P_PUNCH_FAILED: Digest method not supported` has been reproduced several times in production and never
|
|
713
|
+
* under instrumentation. Two candidates have now been **measured and excluded**:
|
|
714
|
+
*
|
|
715
|
+
* * **BLAKE2s-256** — present on both MacMini and this Windows host, on the same run that reported the
|
|
716
|
+
* failure. Ruled out by a direct 32-byte digest on each machine.
|
|
717
|
+
* * **every other primitive the handshake touches** — `sha256`, `chacha20-poly1305` with a real AEAD
|
|
718
|
+
* round trip, `hkdfSync('sha256')`, `x25519` with `diffieHellman`, and `ed25519` — all present on both.
|
|
719
|
+
*
|
|
720
|
+
* So the fault is **not a missing algorithm**, and knowing that is worth more than another hypothesis: it
|
|
721
|
+
* moves the search from the runtime to the protocol, the state, or the transport.
|
|
722
|
+
*
|
|
723
|
+
* The exclusion cost several dispatches because each candidate had to be probed over a channel carrying one
|
|
724
|
+
* exit code. This function means the next question of that kind is answered in **one** dispatch, by the
|
|
725
|
+
* machine itself, with no interpretation in between.
|
|
726
|
+
*
|
|
727
|
+
* @returns {{ ok: boolean, missing: string[], present: string[], node: string, detail: string }}
|
|
728
|
+
*/
|
|
729
|
+
export function cryptoCapabilities() {
|
|
730
|
+
const present = [];
|
|
731
|
+
const missing = [];
|
|
732
|
+
const check = (name, fn) => {
|
|
733
|
+
try {
|
|
734
|
+
if (fn()) present.push(name);
|
|
735
|
+
else missing.push(`${name} (reported as unsupported)`);
|
|
736
|
+
} catch (e) {
|
|
737
|
+
missing.push(`${name} (${e?.message ?? String(e)})`);
|
|
738
|
+
}
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
check('blake2s256', () => createHash('blake2s256').update('').digest().length === 32);
|
|
742
|
+
check('sha256', () => createHash('sha256').update('').digest().length === 32);
|
|
743
|
+
check('chacha20-poly1305', () => {
|
|
744
|
+
const key = randomBytes(32);
|
|
745
|
+
const cipher = createCipheriv('chacha20-poly1305', key, Buffer.alloc(12), { authTagLength: 16 });
|
|
746
|
+
cipher.update(Buffer.from('probe'));
|
|
747
|
+
cipher.final();
|
|
748
|
+
return cipher.getAuthTag().length === 16;
|
|
749
|
+
});
|
|
750
|
+
check('hkdf-sha256', () => hkdfSync('sha256', randomBytes(32), Buffer.alloc(0), Buffer.from('probe'), 32).byteLength === 32);
|
|
751
|
+
check('x25519+diffieHellman', () => {
|
|
752
|
+
const { privateKey, publicKey } = generateKeyPairSync('x25519');
|
|
753
|
+
return diffieHellman({ privateKey, publicKey }).length === 32;
|
|
754
|
+
});
|
|
755
|
+
check('ed25519', () => generateKeyPairSync('ed25519') !== null);
|
|
756
|
+
|
|
757
|
+
return {
|
|
758
|
+
ok: missing.length === 0,
|
|
759
|
+
present,
|
|
760
|
+
missing,
|
|
761
|
+
node: process.version,
|
|
762
|
+
detail: missing.length === 0
|
|
763
|
+
? `all ${present.length} handshake primitive(s) available on ${process.version}`
|
|
764
|
+
: `${missing.length} of ${present.length + missing.length} handshake primitive(s) unavailable on ${process.version}: ${missing.join('; ')}`,
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* How BLAKE2s-256 is reached on this runtime, discovered once and remembered.
|
|
770
|
+
*
|
|
771
|
+
* ## Why a probe exists for a call that simply works
|
|
772
|
+
*
|
|
773
|
+
* `P2P_PUNCH_FAILED: Digest method not supported` was reproduced twice in production and, measured directly,
|
|
774
|
+
* **neither MacMini nor this Windows host lacks `blake2s256`** — so the algorithm is *not* the cause. What
|
|
775
|
+
* made that take so long to establish is the shape of the failure: OpenSSL's message names no algorithm, no
|
|
776
|
+
* caller and no host, so the only way to rule a candidate out was to probe each machine by hand.
|
|
777
|
+
*
|
|
778
|
+
* This wrapper does not change behaviour. It changes what a failure *says*: the algorithm that was requested,
|
|
779
|
+
* whether the runtime advertises it, and the fact that it cannot be substituted. That is the difference
|
|
780
|
+
* between a message a reader can act on and one that costs a round trip per hypothesis.
|
|
781
|
+
*/
|
|
782
|
+
let blake2sSupport = null;
|
|
783
|
+
|
|
784
|
+
/**
|
|
785
|
+
* Whether this runtime can compute BLAKE2s-256, and how it was reached.
|
|
786
|
+
*
|
|
787
|
+
* @returns {{ok: boolean, advertised: boolean, detail: string}}
|
|
788
|
+
*/
|
|
789
|
+
export function blake2s256Support() {
|
|
790
|
+
if (blake2sSupport !== null) return blake2sSupport;
|
|
791
|
+
let advertised = false;
|
|
792
|
+
try {
|
|
793
|
+
advertised = typeof getHashes === 'function' && getHashes().includes('blake2s256');
|
|
794
|
+
} catch {
|
|
795
|
+
advertised = false;
|
|
796
|
+
}
|
|
797
|
+
try {
|
|
798
|
+
const probe = createHash('blake2s256');
|
|
799
|
+
probe.update('');
|
|
800
|
+
const bytes = probe.digest().length;
|
|
801
|
+
blake2sSupport = {
|
|
802
|
+
ok: bytes === 32,
|
|
803
|
+
advertised,
|
|
804
|
+
detail: bytes === 32
|
|
805
|
+
? `BLAKE2s-256 available (${advertised ? 'advertised by getHashes' : 'works though not advertised'})`
|
|
806
|
+
: `BLAKE2s-256 produced ${bytes} bytes rather than 32`,
|
|
807
|
+
};
|
|
808
|
+
} catch (e) {
|
|
809
|
+
blake2sSupport = {
|
|
810
|
+
ok: false,
|
|
811
|
+
advertised,
|
|
812
|
+
detail: `BLAKE2s-256 unavailable on this runtime: ${e?.message ?? String(e)}. It is a WIRE PROTOCOL primitive -- the handshake hash, the prologue hash and the session binding -- so it cannot be replaced with SHA-256 without breaking interoperability with every peer. This machine cannot take part in a handshake until the runtime supports it.`,
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
return blake2sSupport;
|
|
816
|
+
}
|
|
817
|
+
|
|
706
818
|
/**
|
|
707
819
|
* BLAKE2s-256, §3's handshake hash.
|
|
708
820
|
*
|
|
821
|
+
* Throws a message naming the algorithm and the consequence when the runtime cannot provide it, instead of
|
|
822
|
+
* letting OpenSSL's `Digest method not supported` escape — which names neither, and which is why a real
|
|
823
|
+
* production failure took several rounds to attribute.
|
|
824
|
+
*
|
|
709
825
|
* @param {...(Buffer|string)} parts
|
|
710
826
|
* @returns {Buffer} 32 bytes.
|
|
711
827
|
*/
|
|
712
828
|
export function handshakeHash(...parts) {
|
|
713
|
-
|
|
829
|
+
let hash;
|
|
830
|
+
try {
|
|
831
|
+
hash = createHash('blake2s256');
|
|
832
|
+
} catch (e) {
|
|
833
|
+
const support = blake2s256Support();
|
|
834
|
+
const error = new Error(`handshakeHash requires BLAKE2s-256: ${support.detail}`);
|
|
835
|
+
error.code = 'CRYPTO_BLAKE2S256_UNAVAILABLE';
|
|
836
|
+
error.cause = e;
|
|
837
|
+
throw error;
|
|
838
|
+
}
|
|
714
839
|
for (const part of parts) hash.update(part);
|
|
715
|
-
|
|
840
|
+
const out = hash.digest();
|
|
841
|
+
if (out.length !== 32) {
|
|
842
|
+
const error = new Error(`handshakeHash expected a 32-byte digest and produced ${out.length}; peers would compute a different handshake hash`);
|
|
843
|
+
error.code = 'CRYPTO_BLAKE2S256_WRONG_LENGTH';
|
|
844
|
+
throw error;
|
|
845
|
+
}
|
|
846
|
+
return out;
|
|
716
847
|
}
|
|
717
848
|
|
|
718
849
|
/**
|
package/src/plugin/tools.mjs
CHANGED
|
@@ -185,7 +185,7 @@ const MAX_STAGES = 16;
|
|
|
185
185
|
* `package.json` during `scripts/pack.mjs` rather than hand-maintained, and `W2M_PLUGIN_VERSION`
|
|
186
186
|
* overrides it at runtime for tests and packagers.
|
|
187
187
|
*/
|
|
188
|
-
const PLUGIN_VERSION = '0.
|
|
188
|
+
const PLUGIN_VERSION = '0.18.0';
|
|
189
189
|
|
|
190
190
|
/** How long any single HTTP call may take before it is aborted, in milliseconds. */
|
|
191
191
|
const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
|