wire-mesh-core 1.47.0 → 1.48.1
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/adapters/frame-codec.cjs +9 -0
- package/dist/adapters/frame-codec.d.cts +3 -1
- package/dist/adapters/frame-codec.d.mts +3 -1
- package/dist/adapters/frame-codec.mjs +9 -1
- package/dist/adapters/threshold-wasm.cjs +60 -2
- package/dist/adapters/threshold-wasm.d.cts +28 -2
- package/dist/adapters/threshold-wasm.d.mts +28 -2
- package/dist/adapters/threshold-wasm.mjs +54 -3
- package/dist/domain/mesh-session.cjs +28 -18
- package/dist/domain/mesh-session.d.cts +3 -1
- package/dist/domain/mesh-session.d.mts +3 -1
- package/dist/domain/mesh-session.mjs +29 -19
- package/package.json +1 -1
|
@@ -5,6 +5,14 @@ let cbor2 = require("cbor2");
|
|
|
5
5
|
function messageFromFrame(frame) {
|
|
6
6
|
return new Uint8Array((0, cbor2.encode)(frame, cbor2.cdeEncodeOptions));
|
|
7
7
|
}
|
|
8
|
+
/** Wraps frame as a relay-data-frame's own opaque payload, stamping to-device when the caller knows which established pairing to address it to (wire-mesh#30) -- the outbound counterpart to tryDecodeFrame's own doc comment below, which describes the inbound side of the same relay-data envelope. Omitting toDevice leaves the frame unaddressed, which the receiving hub then routes via its own most-recently-established-pairing fallback. */
|
|
9
|
+
function wrapRelayData(frame, toDevice) {
|
|
10
|
+
return {
|
|
11
|
+
type: "relay-data",
|
|
12
|
+
payload: messageFromFrame(frame),
|
|
13
|
+
...toDevice !== void 0 ? { "to-device": toDevice } : {}
|
|
14
|
+
};
|
|
15
|
+
}
|
|
8
16
|
/** A frame that fails schema validation, caught separately from a decode failure so it can be dropped without disconnecting. */
|
|
9
17
|
var SchemaInvalidFrameError = class extends Error {
|
|
10
18
|
constructor(message) {
|
|
@@ -35,3 +43,4 @@ exports.SchemaInvalidFrameError = SchemaInvalidFrameError;
|
|
|
35
43
|
exports.decodeMessage = decodeMessage;
|
|
36
44
|
exports.messageFromFrame = messageFromFrame;
|
|
37
45
|
exports.tryDecodeFrame = tryDecodeFrame;
|
|
46
|
+
exports.wrapRelayData = wrapRelayData;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { N as Frame } from "../protocol-CNFRVx9w.cjs";
|
|
1
|
+
import { D as DeviceId, N as Frame, ft as RelayDataFrame } from "../protocol-CNFRVx9w.cjs";
|
|
2
2
|
//#region src/adapters/frame-codec.d.ts
|
|
3
3
|
export declare function messageFromFrame(frame: Frame): Uint8Array<ArrayBuffer>;
|
|
4
|
+
/** Wraps frame as a relay-data-frame's own opaque payload, stamping to-device when the caller knows which established pairing to address it to (wire-mesh#30) -- the outbound counterpart to tryDecodeFrame's own doc comment below, which describes the inbound side of the same relay-data envelope. Omitting toDevice leaves the frame unaddressed, which the receiving hub then routes via its own most-recently-established-pairing fallback. */
|
|
5
|
+
export declare function wrapRelayData(frame: Frame, toDevice?: DeviceId): RelayDataFrame;
|
|
4
6
|
/** A frame that fails schema validation, caught separately from a decode failure so it can be dropped without disconnecting. */
|
|
5
7
|
export declare class SchemaInvalidFrameError extends Error {
|
|
6
8
|
constructor(message: string);
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { N as Frame } from "../protocol-CNFRVx9w.mjs";
|
|
1
|
+
import { D as DeviceId, N as Frame, ft as RelayDataFrame } from "../protocol-CNFRVx9w.mjs";
|
|
2
2
|
//#region src/adapters/frame-codec.d.ts
|
|
3
3
|
export declare function messageFromFrame(frame: Frame): Uint8Array<ArrayBuffer>;
|
|
4
|
+
/** Wraps frame as a relay-data-frame's own opaque payload, stamping to-device when the caller knows which established pairing to address it to (wire-mesh#30) -- the outbound counterpart to tryDecodeFrame's own doc comment below, which describes the inbound side of the same relay-data envelope. Omitting toDevice leaves the frame unaddressed, which the receiving hub then routes via its own most-recently-established-pairing fallback. */
|
|
5
|
+
export declare function wrapRelayData(frame: Frame, toDevice?: DeviceId): RelayDataFrame;
|
|
4
6
|
/** A frame that fails schema validation, caught separately from a decode failure so it can be dropped without disconnecting. */
|
|
5
7
|
export declare class SchemaInvalidFrameError extends Error {
|
|
6
8
|
constructor(message: string);
|
|
@@ -4,6 +4,14 @@ import { cdeDecodeOptions, cdeEncodeOptions, decode, encode } from "cbor2";
|
|
|
4
4
|
function messageFromFrame(frame) {
|
|
5
5
|
return new Uint8Array(encode(frame, cdeEncodeOptions));
|
|
6
6
|
}
|
|
7
|
+
/** Wraps frame as a relay-data-frame's own opaque payload, stamping to-device when the caller knows which established pairing to address it to (wire-mesh#30) -- the outbound counterpart to tryDecodeFrame's own doc comment below, which describes the inbound side of the same relay-data envelope. Omitting toDevice leaves the frame unaddressed, which the receiving hub then routes via its own most-recently-established-pairing fallback. */
|
|
8
|
+
function wrapRelayData(frame, toDevice) {
|
|
9
|
+
return {
|
|
10
|
+
type: "relay-data",
|
|
11
|
+
payload: messageFromFrame(frame),
|
|
12
|
+
...toDevice !== void 0 ? { "to-device": toDevice } : {}
|
|
13
|
+
};
|
|
14
|
+
}
|
|
7
15
|
/** A frame that fails schema validation, caught separately from a decode failure so it can be dropped without disconnecting. */
|
|
8
16
|
var SchemaInvalidFrameError = class extends Error {
|
|
9
17
|
constructor(message) {
|
|
@@ -30,4 +38,4 @@ function tryDecodeFrame(bytes) {
|
|
|
30
38
|
return result.success ? result.data : null;
|
|
31
39
|
}
|
|
32
40
|
//#endregion
|
|
33
|
-
export { SchemaInvalidFrameError, decodeMessage, messageFromFrame, tryDecodeFrame };
|
|
41
|
+
export { SchemaInvalidFrameError, decodeMessage, messageFromFrame, tryDecodeFrame, wrapRelayData };
|
|
@@ -41,6 +41,23 @@ function dkgRound1(ownDeviceId, maxSigners, minSigners) {
|
|
|
41
41
|
package: toBufferSource(out.package)
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
|
+
/** Splits a serialized DKG round-1 package (dkgRound1's own `package` output) into `threshold-keygen-round1`'s own wire shape: the Feldman commitment as an array of independently-serialized coefficients (`commitment: [* bstr]`) and the Schnorr proof of knowledge as a separate byte string -- unlike frost-core's own combined-blob serialization. The inverse of combineRound1Package. */
|
|
45
|
+
function splitRound1Package(serializedPackage) {
|
|
46
|
+
const out = wasm_dist_wire_mesh_threshold_wasm_js.split_round1_package(serializedPackage);
|
|
47
|
+
const commitment = [];
|
|
48
|
+
for (const coefficient of out.commitment) {
|
|
49
|
+
if (!isUint8Array(coefficient)) throw new Error("wasm returned a non-Uint8Array entry in split_round1_package's own commitment array");
|
|
50
|
+
commitment.push(toBufferSource(coefficient));
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
commitment,
|
|
54
|
+
proofOfKnowledge: toBufferSource(out.proofOfKnowledge)
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/** Reconstructs a serialized DKG round-1 package (the same combined-blob shape dkgRound2/dkgRound3/dkgTranscriptDigest expect) from the two independently-serialized wire fields `threshold-keygen-round1` carries. The inverse of splitRound1Package. */
|
|
58
|
+
function combineRound1Package(commitment, proofOfKnowledge) {
|
|
59
|
+
return toBufferSource(wasm_dist_wire_mesh_threshold_wasm_js.combine_round1_package([...commitment], proofOfKnowledge));
|
|
60
|
+
}
|
|
44
61
|
/** Round 2: verifies every other participant's round-1 proof of knowledge and computes this participant's own pairwise sub-shares. `round1` MUST NOT include this participant's own package. */
|
|
45
62
|
function dkgRound2(ownSecretPackage, round1) {
|
|
46
63
|
const [ids, packages] = toParallelArrays(round1);
|
|
@@ -84,6 +101,18 @@ function signingRound1Commit(ownKeyPackage) {
|
|
|
84
101
|
commitments: toBufferSource(out.commitments)
|
|
85
102
|
};
|
|
86
103
|
}
|
|
104
|
+
/** Splits a serialized SigningCommitments blob (signingRound1Commit's own `commitments` output) into the two independently-serialized halves `threshold-commitment`'s wire shape carries (`hiding: bstr, binding: bstr`) -- unlike frost-core's own combined-blob serialization, which is opaque and not spec-shaped. The inverse of combineCommitments. */
|
|
105
|
+
function splitCommitments(commitments) {
|
|
106
|
+
const out = wasm_dist_wire_mesh_threshold_wasm_js.split_commitments(commitments);
|
|
107
|
+
return {
|
|
108
|
+
hiding: toBufferSource(out.hiding),
|
|
109
|
+
binding: toBufferSource(out.binding)
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/** Reconstructs a serialized SigningCommitments blob (the same combined-blob shape signingBuildPackage/signingRound2Sign expect) from the two independently-serialized halves `threshold-commitment` carries on the wire. The inverse of splitCommitments. */
|
|
113
|
+
function combineCommitments(hiding, binding) {
|
|
114
|
+
return toBufferSource(wasm_dist_wire_mesh_threshold_wasm_js.combine_commitments(hiding, binding));
|
|
115
|
+
}
|
|
87
116
|
/** Coordinator-side: builds the signing-package bytes every participant's round 2 is computed against. */
|
|
88
117
|
function signingBuildPackage(commitments, message) {
|
|
89
118
|
const [ids, values] = toParallelArrays(commitments);
|
|
@@ -110,30 +139,59 @@ function reshareRound1(ownDeviceId, ownOldSigningShare, survivorDeviceIds, newPa
|
|
|
110
139
|
outgoing: fromParallelArrays(out.recipientIds, out.shares)
|
|
111
140
|
};
|
|
112
141
|
}
|
|
142
|
+
/** Splits a survivor's serialized broadcast commitment (reshareRound1's own `commitment` output) into `threshold-keygen-round1`'s own wire shape: an array of independently-serialized coefficients (`commitment: [* bstr]`) -- unlike reshareRound1's own whole-blob serialization. The inverse of reshareCombineCommitmentParts. */
|
|
143
|
+
function reshareSplitCommitment(commitment) {
|
|
144
|
+
const out = [];
|
|
145
|
+
for (const part of wasm_dist_wire_mesh_threshold_wasm_js.reshare_split_commitment(commitment)) {
|
|
146
|
+
if (!isUint8Array(part)) throw new Error("wasm returned a non-Uint8Array entry in reshare_split_commitment's own output array");
|
|
147
|
+
out.push(toBufferSource(part));
|
|
148
|
+
}
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
/** Reconstructs a survivor's serialized broadcast commitment (the same whole-blob shape reshareCombineCommitments expects each entry of its own commitments array to be) from the wire's own per-coefficient array. The inverse of reshareSplitCommitment. */
|
|
152
|
+
function reshareCombineCommitmentParts(parts) {
|
|
153
|
+
return toBufferSource(wasm_dist_wire_mesh_threshold_wasm_js.reshare_combine_commitment_parts([...parts]));
|
|
154
|
+
}
|
|
113
155
|
/** Sums T survivors' broadcast commitment vectors into the one combined VSS commitment `reshareDerivePublicKeyPackage` needs. */
|
|
114
156
|
function reshareCombineCommitments(commitments) {
|
|
115
157
|
return toBufferSource(wasm_dist_wire_mesh_threshold_wasm_js.reshare_combine_commitments([...commitments]));
|
|
116
158
|
}
|
|
117
|
-
/** The
|
|
159
|
+
/** The echo-broadcast transcript digest a member of the new participant set sends on threshold-keygen-confirm for a reshare -- the reshare analogue of dkgTranscriptDigest, structurally distinct because a reshare's own survivor commitment carries no proof-of-knowledge component: deserializing one as a DKG round-1 package fails outright, since the two are different wire shapes entirely. `survivorCommitments` entries are each the same whole-blob shape reshareRound1's own `commitment` output is (NOT the wire's own split [* bstr] parts -- combine those first via reshareCombineCommitmentParts if collected off the wire). */
|
|
160
|
+
function reshareTranscriptDigest(survivorCommitments, groupVerifyingKey) {
|
|
161
|
+
const [ids, commitments] = toParallelArrays(survivorCommitments);
|
|
162
|
+
return toBufferSource(wasm_dist_wire_mesh_threshold_wasm_js.reshare_transcript_digest(ids, commitments, groupVerifyingKey));
|
|
163
|
+
}
|
|
164
|
+
/** The group's derived public key package given the combined commitment and the new participant set. `groupVerifyingKey` is what `threshold-keygen-confirm`'s own `group-key` field carries and what a reshare's own verifier obligation checks against `existing-group-key` (MUST equal it -- a reshare that changes the group key is a takeover, not a reshare). */
|
|
118
165
|
function reshareDerivePublicKeyPackage(combinedCommitment, newParticipantDeviceIds) {
|
|
119
|
-
|
|
166
|
+
const out = wasm_dist_wire_mesh_threshold_wasm_js.reshare_derive_public_key_package(combinedCommitment, [...newParticipantDeviceIds]);
|
|
167
|
+
return {
|
|
168
|
+
publicKeyPackage: toBufferSource(out.publicKeyPackage),
|
|
169
|
+
groupVerifyingKey: toBufferSource(out.groupVerifyingKey)
|
|
170
|
+
};
|
|
120
171
|
}
|
|
121
172
|
/** New-participant side (local): verifies each received share against its own embedded commitment, sums the validated shares, and builds this participant's final key package. */
|
|
122
173
|
function reshareCombineReceivedShares(ownDeviceId, received, publicKeyPackage, newMinSigners) {
|
|
123
174
|
return toBufferSource(wasm_dist_wire_mesh_threshold_wasm_js.reshare_combine_received_shares(ownDeviceId, [...received], publicKeyPackage, newMinSigners));
|
|
124
175
|
}
|
|
125
176
|
//#endregion
|
|
177
|
+
exports.combineCommitments = combineCommitments;
|
|
178
|
+
exports.combineRound1Package = combineRound1Package;
|
|
126
179
|
exports.dkgConfirmMatches = dkgConfirmMatches;
|
|
127
180
|
exports.dkgRound1 = dkgRound1;
|
|
128
181
|
exports.dkgRound2 = dkgRound2;
|
|
129
182
|
exports.dkgRound3 = dkgRound3;
|
|
130
183
|
exports.dkgTranscriptDigest = dkgTranscriptDigest;
|
|
131
184
|
exports.keyPackageSigningShare = keyPackageSigningShare;
|
|
185
|
+
exports.reshareCombineCommitmentParts = reshareCombineCommitmentParts;
|
|
132
186
|
exports.reshareCombineCommitments = reshareCombineCommitments;
|
|
133
187
|
exports.reshareCombineReceivedShares = reshareCombineReceivedShares;
|
|
134
188
|
exports.reshareDerivePublicKeyPackage = reshareDerivePublicKeyPackage;
|
|
135
189
|
exports.reshareRound1 = reshareRound1;
|
|
190
|
+
exports.reshareSplitCommitment = reshareSplitCommitment;
|
|
191
|
+
exports.reshareTranscriptDigest = reshareTranscriptDigest;
|
|
136
192
|
exports.signingAggregate = signingAggregate;
|
|
137
193
|
exports.signingBuildPackage = signingBuildPackage;
|
|
138
194
|
exports.signingRound1Commit = signingRound1Commit;
|
|
139
195
|
exports.signingRound2Sign = signingRound2Sign;
|
|
196
|
+
exports.splitCommitments = splitCommitments;
|
|
197
|
+
exports.splitRound1Package = splitRound1Package;
|
|
@@ -11,6 +11,14 @@ export interface DkgRound1Result {
|
|
|
11
11
|
}
|
|
12
12
|
/** Round 1: samples this participant's own polynomial and commits to it. See `wire_mesh_threshold::dkg::round1`'s own doc comment for the full design. */
|
|
13
13
|
export declare function dkgRound1(ownDeviceId: DeviceId, maxSigners: number, minSigners: number): DkgRound1Result;
|
|
14
|
+
export interface SplitRound1PackageResult {
|
|
15
|
+
commitment: Uint8Array<ArrayBuffer>[];
|
|
16
|
+
proofOfKnowledge: Uint8Array<ArrayBuffer>;
|
|
17
|
+
}
|
|
18
|
+
/** Splits a serialized DKG round-1 package (dkgRound1's own `package` output) into `threshold-keygen-round1`'s own wire shape: the Feldman commitment as an array of independently-serialized coefficients (`commitment: [* bstr]`) and the Schnorr proof of knowledge as a separate byte string -- unlike frost-core's own combined-blob serialization. The inverse of combineRound1Package. */
|
|
19
|
+
export declare function splitRound1Package(serializedPackage: Uint8Array): SplitRound1PackageResult;
|
|
20
|
+
/** Reconstructs a serialized DKG round-1 package (the same combined-blob shape dkgRound2/dkgRound3/dkgTranscriptDigest expect) from the two independently-serialized wire fields `threshold-keygen-round1` carries. The inverse of splitRound1Package. */
|
|
21
|
+
export declare function combineRound1Package(commitment: readonly Uint8Array[], proofOfKnowledge: Uint8Array): Uint8Array<ArrayBuffer>;
|
|
14
22
|
export interface DkgRound2Result {
|
|
15
23
|
secretPackage: Uint8Array<ArrayBuffer>;
|
|
16
24
|
outgoing: DeviceKeyed[];
|
|
@@ -38,6 +46,14 @@ export interface SigningRound1Result {
|
|
|
38
46
|
* The pure-crypto half of round 1: generates a fresh nonce pair and its commitments. Persisting `nonces` durably before this participant's own round-1 response leaves the device is the CALLER's own obligation -- nonce reuse across two released signature shares discloses this participant's long-term key share outright. This function has no storage dependency and cannot enforce that obligation itself.
|
|
39
47
|
*/
|
|
40
48
|
export declare function signingRound1Commit(ownKeyPackage: Uint8Array): SigningRound1Result;
|
|
49
|
+
export interface SplitCommitmentsResult {
|
|
50
|
+
hiding: Uint8Array<ArrayBuffer>;
|
|
51
|
+
binding: Uint8Array<ArrayBuffer>;
|
|
52
|
+
}
|
|
53
|
+
/** Splits a serialized SigningCommitments blob (signingRound1Commit's own `commitments` output) into the two independently-serialized halves `threshold-commitment`'s wire shape carries (`hiding: bstr, binding: bstr`) -- unlike frost-core's own combined-blob serialization, which is opaque and not spec-shaped. The inverse of combineCommitments. */
|
|
54
|
+
export declare function splitCommitments(commitments: Uint8Array): SplitCommitmentsResult;
|
|
55
|
+
/** Reconstructs a serialized SigningCommitments blob (the same combined-blob shape signingBuildPackage/signingRound2Sign expect) from the two independently-serialized halves `threshold-commitment` carries on the wire. The inverse of splitCommitments. */
|
|
56
|
+
export declare function combineCommitments(hiding: Uint8Array, binding: Uint8Array): Uint8Array<ArrayBuffer>;
|
|
41
57
|
/** Coordinator-side: builds the signing-package bytes every participant's round 2 is computed against. */
|
|
42
58
|
export declare function signingBuildPackage(commitments: readonly DeviceKeyed[], message: Uint8Array): Uint8Array<ArrayBuffer>;
|
|
43
59
|
/** Round 2: releases this participant's signature share. The CALLER MUST have already taken (one-shot) `nonces` from its own durable nonce store before calling this. */
|
|
@@ -54,10 +70,20 @@ export interface ReshareRound1Result {
|
|
|
54
70
|
* Survivor side: re-shares this device's own Lagrange-weighted sub-share of the group secret across a new (possibly different) participant set. See `wire_mesh_threshold::reshare`'s own module doc comment (Rust side) for the full design rationale -- deliberately not built on a zero-secret "refresh" delta, which cannot onboard a device with no prior share or safely change the threshold.
|
|
55
71
|
*/
|
|
56
72
|
export declare function reshareRound1(ownDeviceId: DeviceId, ownOldSigningShare: Uint8Array, survivorDeviceIds: readonly DeviceId[], newParticipantDeviceIds: readonly DeviceId[], newMinSigners: number): ReshareRound1Result;
|
|
73
|
+
/** Splits a survivor's serialized broadcast commitment (reshareRound1's own `commitment` output) into `threshold-keygen-round1`'s own wire shape: an array of independently-serialized coefficients (`commitment: [* bstr]`) -- unlike reshareRound1's own whole-blob serialization. The inverse of reshareCombineCommitmentParts. */
|
|
74
|
+
export declare function reshareSplitCommitment(commitment: Uint8Array): Uint8Array<ArrayBuffer>[];
|
|
75
|
+
/** Reconstructs a survivor's serialized broadcast commitment (the same whole-blob shape reshareCombineCommitments expects each entry of its own commitments array to be) from the wire's own per-coefficient array. The inverse of reshareSplitCommitment. */
|
|
76
|
+
export declare function reshareCombineCommitmentParts(parts: readonly Uint8Array[]): Uint8Array<ArrayBuffer>;
|
|
57
77
|
/** Sums T survivors' broadcast commitment vectors into the one combined VSS commitment `reshareDerivePublicKeyPackage` needs. */
|
|
58
78
|
export declare function reshareCombineCommitments(commitments: readonly Uint8Array[]): Uint8Array<ArrayBuffer>;
|
|
59
|
-
/** The
|
|
60
|
-
export declare function
|
|
79
|
+
/** The echo-broadcast transcript digest a member of the new participant set sends on threshold-keygen-confirm for a reshare -- the reshare analogue of dkgTranscriptDigest, structurally distinct because a reshare's own survivor commitment carries no proof-of-knowledge component: deserializing one as a DKG round-1 package fails outright, since the two are different wire shapes entirely. `survivorCommitments` entries are each the same whole-blob shape reshareRound1's own `commitment` output is (NOT the wire's own split [* bstr] parts -- combine those first via reshareCombineCommitmentParts if collected off the wire). */
|
|
80
|
+
export declare function reshareTranscriptDigest(survivorCommitments: readonly DeviceKeyed[], groupVerifyingKey: Uint8Array): Uint8Array<ArrayBuffer>;
|
|
81
|
+
export interface ReshareDerivePublicKeyPackageResult {
|
|
82
|
+
publicKeyPackage: Uint8Array<ArrayBuffer>;
|
|
83
|
+
groupVerifyingKey: Uint8Array<ArrayBuffer>;
|
|
84
|
+
}
|
|
85
|
+
/** The group's derived public key package given the combined commitment and the new participant set. `groupVerifyingKey` is what `threshold-keygen-confirm`'s own `group-key` field carries and what a reshare's own verifier obligation checks against `existing-group-key` (MUST equal it -- a reshare that changes the group key is a takeover, not a reshare). */
|
|
86
|
+
export declare function reshareDerivePublicKeyPackage(combinedCommitment: Uint8Array, newParticipantDeviceIds: readonly DeviceId[]): ReshareDerivePublicKeyPackageResult;
|
|
61
87
|
/** New-participant side (local): verifies each received share against its own embedded commitment, sums the validated shares, and builds this participant's final key package. */
|
|
62
88
|
export declare function reshareCombineReceivedShares(ownDeviceId: DeviceId, received: readonly Uint8Array[], publicKeyPackage: Uint8Array, newMinSigners: number): Uint8Array<ArrayBuffer>;
|
|
63
89
|
//#endregion
|
|
@@ -11,6 +11,14 @@ export interface DkgRound1Result {
|
|
|
11
11
|
}
|
|
12
12
|
/** Round 1: samples this participant's own polynomial and commits to it. See `wire_mesh_threshold::dkg::round1`'s own doc comment for the full design. */
|
|
13
13
|
export declare function dkgRound1(ownDeviceId: DeviceId, maxSigners: number, minSigners: number): DkgRound1Result;
|
|
14
|
+
export interface SplitRound1PackageResult {
|
|
15
|
+
commitment: Uint8Array<ArrayBuffer>[];
|
|
16
|
+
proofOfKnowledge: Uint8Array<ArrayBuffer>;
|
|
17
|
+
}
|
|
18
|
+
/** Splits a serialized DKG round-1 package (dkgRound1's own `package` output) into `threshold-keygen-round1`'s own wire shape: the Feldman commitment as an array of independently-serialized coefficients (`commitment: [* bstr]`) and the Schnorr proof of knowledge as a separate byte string -- unlike frost-core's own combined-blob serialization. The inverse of combineRound1Package. */
|
|
19
|
+
export declare function splitRound1Package(serializedPackage: Uint8Array): SplitRound1PackageResult;
|
|
20
|
+
/** Reconstructs a serialized DKG round-1 package (the same combined-blob shape dkgRound2/dkgRound3/dkgTranscriptDigest expect) from the two independently-serialized wire fields `threshold-keygen-round1` carries. The inverse of splitRound1Package. */
|
|
21
|
+
export declare function combineRound1Package(commitment: readonly Uint8Array[], proofOfKnowledge: Uint8Array): Uint8Array<ArrayBuffer>;
|
|
14
22
|
export interface DkgRound2Result {
|
|
15
23
|
secretPackage: Uint8Array<ArrayBuffer>;
|
|
16
24
|
outgoing: DeviceKeyed[];
|
|
@@ -38,6 +46,14 @@ export interface SigningRound1Result {
|
|
|
38
46
|
* The pure-crypto half of round 1: generates a fresh nonce pair and its commitments. Persisting `nonces` durably before this participant's own round-1 response leaves the device is the CALLER's own obligation -- nonce reuse across two released signature shares discloses this participant's long-term key share outright. This function has no storage dependency and cannot enforce that obligation itself.
|
|
39
47
|
*/
|
|
40
48
|
export declare function signingRound1Commit(ownKeyPackage: Uint8Array): SigningRound1Result;
|
|
49
|
+
export interface SplitCommitmentsResult {
|
|
50
|
+
hiding: Uint8Array<ArrayBuffer>;
|
|
51
|
+
binding: Uint8Array<ArrayBuffer>;
|
|
52
|
+
}
|
|
53
|
+
/** Splits a serialized SigningCommitments blob (signingRound1Commit's own `commitments` output) into the two independently-serialized halves `threshold-commitment`'s wire shape carries (`hiding: bstr, binding: bstr`) -- unlike frost-core's own combined-blob serialization, which is opaque and not spec-shaped. The inverse of combineCommitments. */
|
|
54
|
+
export declare function splitCommitments(commitments: Uint8Array): SplitCommitmentsResult;
|
|
55
|
+
/** Reconstructs a serialized SigningCommitments blob (the same combined-blob shape signingBuildPackage/signingRound2Sign expect) from the two independently-serialized halves `threshold-commitment` carries on the wire. The inverse of splitCommitments. */
|
|
56
|
+
export declare function combineCommitments(hiding: Uint8Array, binding: Uint8Array): Uint8Array<ArrayBuffer>;
|
|
41
57
|
/** Coordinator-side: builds the signing-package bytes every participant's round 2 is computed against. */
|
|
42
58
|
export declare function signingBuildPackage(commitments: readonly DeviceKeyed[], message: Uint8Array): Uint8Array<ArrayBuffer>;
|
|
43
59
|
/** Round 2: releases this participant's signature share. The CALLER MUST have already taken (one-shot) `nonces` from its own durable nonce store before calling this. */
|
|
@@ -54,10 +70,20 @@ export interface ReshareRound1Result {
|
|
|
54
70
|
* Survivor side: re-shares this device's own Lagrange-weighted sub-share of the group secret across a new (possibly different) participant set. See `wire_mesh_threshold::reshare`'s own module doc comment (Rust side) for the full design rationale -- deliberately not built on a zero-secret "refresh" delta, which cannot onboard a device with no prior share or safely change the threshold.
|
|
55
71
|
*/
|
|
56
72
|
export declare function reshareRound1(ownDeviceId: DeviceId, ownOldSigningShare: Uint8Array, survivorDeviceIds: readonly DeviceId[], newParticipantDeviceIds: readonly DeviceId[], newMinSigners: number): ReshareRound1Result;
|
|
73
|
+
/** Splits a survivor's serialized broadcast commitment (reshareRound1's own `commitment` output) into `threshold-keygen-round1`'s own wire shape: an array of independently-serialized coefficients (`commitment: [* bstr]`) -- unlike reshareRound1's own whole-blob serialization. The inverse of reshareCombineCommitmentParts. */
|
|
74
|
+
export declare function reshareSplitCommitment(commitment: Uint8Array): Uint8Array<ArrayBuffer>[];
|
|
75
|
+
/** Reconstructs a survivor's serialized broadcast commitment (the same whole-blob shape reshareCombineCommitments expects each entry of its own commitments array to be) from the wire's own per-coefficient array. The inverse of reshareSplitCommitment. */
|
|
76
|
+
export declare function reshareCombineCommitmentParts(parts: readonly Uint8Array[]): Uint8Array<ArrayBuffer>;
|
|
57
77
|
/** Sums T survivors' broadcast commitment vectors into the one combined VSS commitment `reshareDerivePublicKeyPackage` needs. */
|
|
58
78
|
export declare function reshareCombineCommitments(commitments: readonly Uint8Array[]): Uint8Array<ArrayBuffer>;
|
|
59
|
-
/** The
|
|
60
|
-
export declare function
|
|
79
|
+
/** The echo-broadcast transcript digest a member of the new participant set sends on threshold-keygen-confirm for a reshare -- the reshare analogue of dkgTranscriptDigest, structurally distinct because a reshare's own survivor commitment carries no proof-of-knowledge component: deserializing one as a DKG round-1 package fails outright, since the two are different wire shapes entirely. `survivorCommitments` entries are each the same whole-blob shape reshareRound1's own `commitment` output is (NOT the wire's own split [* bstr] parts -- combine those first via reshareCombineCommitmentParts if collected off the wire). */
|
|
80
|
+
export declare function reshareTranscriptDigest(survivorCommitments: readonly DeviceKeyed[], groupVerifyingKey: Uint8Array): Uint8Array<ArrayBuffer>;
|
|
81
|
+
export interface ReshareDerivePublicKeyPackageResult {
|
|
82
|
+
publicKeyPackage: Uint8Array<ArrayBuffer>;
|
|
83
|
+
groupVerifyingKey: Uint8Array<ArrayBuffer>;
|
|
84
|
+
}
|
|
85
|
+
/** The group's derived public key package given the combined commitment and the new participant set. `groupVerifyingKey` is what `threshold-keygen-confirm`'s own `group-key` field carries and what a reshare's own verifier obligation checks against `existing-group-key` (MUST equal it -- a reshare that changes the group key is a takeover, not a reshare). */
|
|
86
|
+
export declare function reshareDerivePublicKeyPackage(combinedCommitment: Uint8Array, newParticipantDeviceIds: readonly DeviceId[]): ReshareDerivePublicKeyPackageResult;
|
|
61
87
|
/** New-participant side (local): verifies each received share against its own embedded commitment, sums the validated shares, and builds this participant's final key package. */
|
|
62
88
|
export declare function reshareCombineReceivedShares(ownDeviceId: DeviceId, received: readonly Uint8Array[], publicKeyPackage: Uint8Array, newMinSigners: number): Uint8Array<ArrayBuffer>;
|
|
63
89
|
//#endregion
|
|
@@ -38,6 +38,23 @@ function dkgRound1(ownDeviceId, maxSigners, minSigners) {
|
|
|
38
38
|
package: toBufferSource(out.package)
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
|
+
/** Splits a serialized DKG round-1 package (dkgRound1's own `package` output) into `threshold-keygen-round1`'s own wire shape: the Feldman commitment as an array of independently-serialized coefficients (`commitment: [* bstr]`) and the Schnorr proof of knowledge as a separate byte string -- unlike frost-core's own combined-blob serialization. The inverse of combineRound1Package. */
|
|
42
|
+
function splitRound1Package(serializedPackage) {
|
|
43
|
+
const out = wasm.split_round1_package(serializedPackage);
|
|
44
|
+
const commitment = [];
|
|
45
|
+
for (const coefficient of out.commitment) {
|
|
46
|
+
if (!isUint8Array(coefficient)) throw new Error("wasm returned a non-Uint8Array entry in split_round1_package's own commitment array");
|
|
47
|
+
commitment.push(toBufferSource(coefficient));
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
commitment,
|
|
51
|
+
proofOfKnowledge: toBufferSource(out.proofOfKnowledge)
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/** Reconstructs a serialized DKG round-1 package (the same combined-blob shape dkgRound2/dkgRound3/dkgTranscriptDigest expect) from the two independently-serialized wire fields `threshold-keygen-round1` carries. The inverse of splitRound1Package. */
|
|
55
|
+
function combineRound1Package(commitment, proofOfKnowledge) {
|
|
56
|
+
return toBufferSource(wasm.combine_round1_package([...commitment], proofOfKnowledge));
|
|
57
|
+
}
|
|
41
58
|
/** Round 2: verifies every other participant's round-1 proof of knowledge and computes this participant's own pairwise sub-shares. `round1` MUST NOT include this participant's own package. */
|
|
42
59
|
function dkgRound2(ownSecretPackage, round1) {
|
|
43
60
|
const [ids, packages] = toParallelArrays(round1);
|
|
@@ -81,6 +98,18 @@ function signingRound1Commit(ownKeyPackage) {
|
|
|
81
98
|
commitments: toBufferSource(out.commitments)
|
|
82
99
|
};
|
|
83
100
|
}
|
|
101
|
+
/** Splits a serialized SigningCommitments blob (signingRound1Commit's own `commitments` output) into the two independently-serialized halves `threshold-commitment`'s wire shape carries (`hiding: bstr, binding: bstr`) -- unlike frost-core's own combined-blob serialization, which is opaque and not spec-shaped. The inverse of combineCommitments. */
|
|
102
|
+
function splitCommitments(commitments) {
|
|
103
|
+
const out = wasm.split_commitments(commitments);
|
|
104
|
+
return {
|
|
105
|
+
hiding: toBufferSource(out.hiding),
|
|
106
|
+
binding: toBufferSource(out.binding)
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/** Reconstructs a serialized SigningCommitments blob (the same combined-blob shape signingBuildPackage/signingRound2Sign expect) from the two independently-serialized halves `threshold-commitment` carries on the wire. The inverse of splitCommitments. */
|
|
110
|
+
function combineCommitments(hiding, binding) {
|
|
111
|
+
return toBufferSource(wasm.combine_commitments(hiding, binding));
|
|
112
|
+
}
|
|
84
113
|
/** Coordinator-side: builds the signing-package bytes every participant's round 2 is computed against. */
|
|
85
114
|
function signingBuildPackage(commitments, message) {
|
|
86
115
|
const [ids, values] = toParallelArrays(commitments);
|
|
@@ -107,17 +136,39 @@ function reshareRound1(ownDeviceId, ownOldSigningShare, survivorDeviceIds, newPa
|
|
|
107
136
|
outgoing: fromParallelArrays(out.recipientIds, out.shares)
|
|
108
137
|
};
|
|
109
138
|
}
|
|
139
|
+
/** Splits a survivor's serialized broadcast commitment (reshareRound1's own `commitment` output) into `threshold-keygen-round1`'s own wire shape: an array of independently-serialized coefficients (`commitment: [* bstr]`) -- unlike reshareRound1's own whole-blob serialization. The inverse of reshareCombineCommitmentParts. */
|
|
140
|
+
function reshareSplitCommitment(commitment) {
|
|
141
|
+
const out = [];
|
|
142
|
+
for (const part of wasm.reshare_split_commitment(commitment)) {
|
|
143
|
+
if (!isUint8Array(part)) throw new Error("wasm returned a non-Uint8Array entry in reshare_split_commitment's own output array");
|
|
144
|
+
out.push(toBufferSource(part));
|
|
145
|
+
}
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
148
|
+
/** Reconstructs a survivor's serialized broadcast commitment (the same whole-blob shape reshareCombineCommitments expects each entry of its own commitments array to be) from the wire's own per-coefficient array. The inverse of reshareSplitCommitment. */
|
|
149
|
+
function reshareCombineCommitmentParts(parts) {
|
|
150
|
+
return toBufferSource(wasm.reshare_combine_commitment_parts([...parts]));
|
|
151
|
+
}
|
|
110
152
|
/** Sums T survivors' broadcast commitment vectors into the one combined VSS commitment `reshareDerivePublicKeyPackage` needs. */
|
|
111
153
|
function reshareCombineCommitments(commitments) {
|
|
112
154
|
return toBufferSource(wasm.reshare_combine_commitments([...commitments]));
|
|
113
155
|
}
|
|
114
|
-
/** The
|
|
156
|
+
/** The echo-broadcast transcript digest a member of the new participant set sends on threshold-keygen-confirm for a reshare -- the reshare analogue of dkgTranscriptDigest, structurally distinct because a reshare's own survivor commitment carries no proof-of-knowledge component: deserializing one as a DKG round-1 package fails outright, since the two are different wire shapes entirely. `survivorCommitments` entries are each the same whole-blob shape reshareRound1's own `commitment` output is (NOT the wire's own split [* bstr] parts -- combine those first via reshareCombineCommitmentParts if collected off the wire). */
|
|
157
|
+
function reshareTranscriptDigest(survivorCommitments, groupVerifyingKey) {
|
|
158
|
+
const [ids, commitments] = toParallelArrays(survivorCommitments);
|
|
159
|
+
return toBufferSource(wasm.reshare_transcript_digest(ids, commitments, groupVerifyingKey));
|
|
160
|
+
}
|
|
161
|
+
/** The group's derived public key package given the combined commitment and the new participant set. `groupVerifyingKey` is what `threshold-keygen-confirm`'s own `group-key` field carries and what a reshare's own verifier obligation checks against `existing-group-key` (MUST equal it -- a reshare that changes the group key is a takeover, not a reshare). */
|
|
115
162
|
function reshareDerivePublicKeyPackage(combinedCommitment, newParticipantDeviceIds) {
|
|
116
|
-
|
|
163
|
+
const out = wasm.reshare_derive_public_key_package(combinedCommitment, [...newParticipantDeviceIds]);
|
|
164
|
+
return {
|
|
165
|
+
publicKeyPackage: toBufferSource(out.publicKeyPackage),
|
|
166
|
+
groupVerifyingKey: toBufferSource(out.groupVerifyingKey)
|
|
167
|
+
};
|
|
117
168
|
}
|
|
118
169
|
/** New-participant side (local): verifies each received share against its own embedded commitment, sums the validated shares, and builds this participant's final key package. */
|
|
119
170
|
function reshareCombineReceivedShares(ownDeviceId, received, publicKeyPackage, newMinSigners) {
|
|
120
171
|
return toBufferSource(wasm.reshare_combine_received_shares(ownDeviceId, [...received], publicKeyPackage, newMinSigners));
|
|
121
172
|
}
|
|
122
173
|
//#endregion
|
|
123
|
-
export { dkgConfirmMatches, dkgRound1, dkgRound2, dkgRound3, dkgTranscriptDigest, keyPackageSigningShare, reshareCombineCommitments, reshareCombineReceivedShares, reshareDerivePublicKeyPackage, reshareRound1, signingAggregate, signingBuildPackage, signingRound1Commit, signingRound2Sign };
|
|
174
|
+
export { combineCommitments, combineRound1Package, dkgConfirmMatches, dkgRound1, dkgRound2, dkgRound3, dkgTranscriptDigest, keyPackageSigningShare, reshareCombineCommitmentParts, reshareCombineCommitments, reshareCombineReceivedShares, reshareDerivePublicKeyPackage, reshareRound1, reshareSplitCommitment, reshareTranscriptDigest, signingAggregate, signingBuildPackage, signingRound1Commit, signingRound2Sign, splitCommitments, splitRound1Package };
|
|
@@ -2,6 +2,17 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
2
2
|
const require_domain_device_id = require("./device-id.cjs");
|
|
3
3
|
const require_domain_handshake = require("./handshake.cjs");
|
|
4
4
|
const require_adapters_frame_codec = require("../adapters/frame-codec.cjs");
|
|
5
|
+
//#region src/domain/relay-pairing.ts
|
|
6
|
+
function createRelayPairings() {
|
|
7
|
+
const established = /* @__PURE__ */ new Map();
|
|
8
|
+
return {
|
|
9
|
+
has: (device) => established.has(require_domain_device_id.deviceIdToHex(device)),
|
|
10
|
+
add: (device) => {
|
|
11
|
+
established.set(require_domain_device_id.deviceIdToHex(device), device);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
5
16
|
//#region src/domain/mesh-session.ts
|
|
6
17
|
const MS_PER_SECOND = 1e3;
|
|
7
18
|
/** peer-advert's own three typed fields -- reserved so a `sendGossipUpdate` caller can never override the session's own device-id, address list, or freshness timestamp by supplying an extension of the same name. */
|
|
@@ -45,7 +56,7 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
45
56
|
let attempt = 0;
|
|
46
57
|
let currentToken = null;
|
|
47
58
|
let nextRequestId = 0;
|
|
48
|
-
|
|
59
|
+
const relayPairings = createRelayPairings();
|
|
49
60
|
const pendingManageRequests = /* @__PURE__ */ new Map();
|
|
50
61
|
const incomingWaiters = [];
|
|
51
62
|
const incomingBacklog = [];
|
|
@@ -90,15 +101,11 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
90
101
|
...token !== null ? { token } : {}
|
|
91
102
|
};
|
|
92
103
|
}
|
|
93
|
-
/** Sends a frame, wrapping it as relay-data first when viaRelay is set -- the single choke point every outbound manage-request/manage-response passes through, so a consumer of sendManageRequest/respond never needs its own relay-wrapping logic. */
|
|
94
|
-
async function transmit(frame, viaRelay) {
|
|
104
|
+
/** Sends a frame, wrapping it as relay-data first when viaRelay is set -- the single choke point every outbound manage-request/manage-response passes through, so a consumer of sendManageRequest/respond never needs its own relay-wrapping logic. When relaying, toDevice is stamped onto the outer relay-data-frame's own `to-device` field so the hub addresses it to the correct pairing directly (wire-mesh#30) rather than falling back to whichever pairing it last saw -- the one case this is omitted is a response to a request that itself arrived with no from-device to echo back, which is left to that same hub fallback exactly as an unaddressed relay-data always has been. */
|
|
105
|
+
async function transmit(frame, viaRelay, toDevice) {
|
|
95
106
|
if (connection === null) throw new Error("not connected");
|
|
96
107
|
if (viaRelay) {
|
|
97
|
-
|
|
98
|
-
type: "relay-data",
|
|
99
|
-
payload: require_adapters_frame_codec.messageFromFrame(frame)
|
|
100
|
-
};
|
|
101
|
-
await connection.send(relayFrame);
|
|
108
|
+
await connection.send(require_adapters_frame_codec.wrapRelayData(frame, toDevice));
|
|
102
109
|
return;
|
|
103
110
|
}
|
|
104
111
|
await connection.send(frame);
|
|
@@ -111,15 +118,18 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
111
118
|
pending.resolve(frame.outcome);
|
|
112
119
|
}
|
|
113
120
|
}
|
|
114
|
-
|
|
121
|
+
/** relayFrame is present only for a manage-request that arrived wrapped in relay-data, and is that same outer relay-data-frame -- its own to-device/from-device fields carry whatever addressing it received. See IncomingManageRequest's own fromDevice/toDevice doc comments for what each means and why neither is ever guessed from pairing state. */
|
|
122
|
+
function applyManageRequest(frame, relayFrame) {
|
|
115
123
|
const requestId = frame["request-id"];
|
|
116
|
-
const fromDevice =
|
|
124
|
+
const fromDevice = relayFrame?.["from-device"];
|
|
125
|
+
const toDevice = relayFrame?.["to-device"];
|
|
117
126
|
emitIncomingManageRequest({
|
|
118
127
|
requestId,
|
|
119
128
|
command: frame.command,
|
|
120
129
|
scope: frame.scope,
|
|
121
130
|
...frame.token !== void 0 ? { token: frame.token } : {},
|
|
122
131
|
...fromDevice !== void 0 ? { fromDevice } : {},
|
|
132
|
+
...toDevice !== void 0 ? { toDevice } : {},
|
|
123
133
|
respond: async (outcome) => {
|
|
124
134
|
const response = {
|
|
125
135
|
type: "manage-response",
|
|
@@ -130,7 +140,7 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
130
140
|
direction: "sent",
|
|
131
141
|
frame: response
|
|
132
142
|
});
|
|
133
|
-
await transmit(response,
|
|
143
|
+
await transmit(response, relayFrame !== void 0, fromDevice);
|
|
134
144
|
emit();
|
|
135
145
|
}
|
|
136
146
|
});
|
|
@@ -144,7 +154,7 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
144
154
|
frame: inner
|
|
145
155
|
});
|
|
146
156
|
if (inner.type === "manage-response") applyManageResponse(inner);
|
|
147
|
-
else applyManageRequest(inner,
|
|
157
|
+
else applyManageRequest(inner, frame);
|
|
148
158
|
return;
|
|
149
159
|
}
|
|
150
160
|
frameLog.push({
|
|
@@ -165,15 +175,15 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
165
175
|
});
|
|
166
176
|
onPeerAdvert?.(advert);
|
|
167
177
|
}
|
|
168
|
-
else if (frame.type === "relay-inbound")
|
|
178
|
+
else if (frame.type === "relay-inbound") relayPairings.add(frame["source-device"]);
|
|
169
179
|
else if (frame.type === "manage-response") applyManageResponse(frame);
|
|
170
|
-
else if (frame.type === "manage-request") applyManageRequest(frame
|
|
180
|
+
else if (frame.type === "manage-request") applyManageRequest(frame);
|
|
171
181
|
else if (frame.type === "revocation-announce") for (const entry of frame.entries) emitRevocationEntry(entry);
|
|
172
182
|
}
|
|
173
|
-
/** Establishes a relay-connect pairing to targetDevice if this session isn't already paired with it -- a no-op when it already is, whether that pairing was established by this session's own prior relay-connect (initiator role) or learned from an incoming relay-inbound (target role, replying back to whoever dialed it). relay-connect has no ack frame: the initiator proceeds to relay-data right after sending it. */
|
|
183
|
+
/** Establishes a relay-connect pairing to targetDevice if this session isn't already paired with it -- a no-op when it already is, whether that pairing was established by this session's own prior relay-connect (initiator role) or learned from an incoming relay-inbound (target role, replying back to whoever dialed it). Pairing with a new target never tears down an existing pairing with a different one: this connection can hold several simultaneously (wire-mesh#30's own multiplexed adjacency map), so a later request back to an already-paired target must not re-send relay-connect for it. relay-connect has no ack frame: the initiator proceeds to relay-data right after sending it. */
|
|
174
184
|
async function ensureRelayPairing(targetDevice) {
|
|
175
185
|
if (connection === null) throw new Error("not connected");
|
|
176
|
-
if (
|
|
186
|
+
if (relayPairings.has(targetDevice)) return;
|
|
177
187
|
const relayConnect = {
|
|
178
188
|
type: "relay-connect",
|
|
179
189
|
"target-device": targetDevice
|
|
@@ -183,7 +193,7 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
183
193
|
frame: relayConnect
|
|
184
194
|
});
|
|
185
195
|
await connection.send(relayConnect);
|
|
186
|
-
|
|
196
|
+
relayPairings.add(targetDevice);
|
|
187
197
|
emit();
|
|
188
198
|
}
|
|
189
199
|
let localHandshakeSent = localHandshake([]);
|
|
@@ -395,7 +405,7 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
395
405
|
direction: "sent",
|
|
396
406
|
frame
|
|
397
407
|
});
|
|
398
|
-
await transmit(frame, targetDevice !== void 0);
|
|
408
|
+
await transmit(frame, targetDevice !== void 0, targetDevice);
|
|
399
409
|
emit();
|
|
400
410
|
if (timeoutMs === void 0) return outcome;
|
|
401
411
|
return Promise.race([outcome, new Promise((resolve) => {
|
|
@@ -38,8 +38,10 @@ export interface IncomingManageRequest {
|
|
|
38
38
|
command: ManageCommand;
|
|
39
39
|
scope: CapabilityScope;
|
|
40
40
|
token?: CapabilityToken;
|
|
41
|
-
/** The device-id of the peer this request was relayed on behalf of, present only when the request arrived wrapped in a relay-data frame
|
|
41
|
+
/** The device-id of the peer this request was relayed on behalf of, read directly from the enclosing relay-data-frame's own `from-device` field (stamped by the hub on every frame it forwards, wire-mesh#30) -- present only when the request arrived wrapped in a relay-data frame that carried one. A caller that needs to address a further request back to the same peer (one not sent via respond(), which already routes back correctly on its own) passes this as sendManageRequest's targetDevice. Never inferred from which relay pairing happens to be most recently established: a connection can hold several concurrent pairings (wire-mesh#30's own multiplexed adjacency map), so only the frame's own per-message addressing can say who actually sent it. */
|
|
42
42
|
fromDevice?: DeviceId;
|
|
43
|
+
/** The device-id this request's relay-data frame was explicitly addressed to, read from its own `to-device` field -- present only when the request arrived relay-wrapped and the frame carried one. A caller fronting more than one locally-addressable device behind a single hub connection (a gateway advertising several local peers through the same relay pairing) uses this to decide whether the request is for this device or should be routed on to a different local peer it also advertises; this session has no such routing logic of its own, since it represents exactly one identity. */
|
|
44
|
+
toDevice?: DeviceId;
|
|
43
45
|
respond: (outcome: ManageOutcome) => Promise<void>;
|
|
44
46
|
}
|
|
45
47
|
export type HandshakeStatus = {
|
|
@@ -38,8 +38,10 @@ export interface IncomingManageRequest {
|
|
|
38
38
|
command: ManageCommand;
|
|
39
39
|
scope: CapabilityScope;
|
|
40
40
|
token?: CapabilityToken;
|
|
41
|
-
/** The device-id of the peer this request was relayed on behalf of, present only when the request arrived wrapped in a relay-data frame
|
|
41
|
+
/** The device-id of the peer this request was relayed on behalf of, read directly from the enclosing relay-data-frame's own `from-device` field (stamped by the hub on every frame it forwards, wire-mesh#30) -- present only when the request arrived wrapped in a relay-data frame that carried one. A caller that needs to address a further request back to the same peer (one not sent via respond(), which already routes back correctly on its own) passes this as sendManageRequest's targetDevice. Never inferred from which relay pairing happens to be most recently established: a connection can hold several concurrent pairings (wire-mesh#30's own multiplexed adjacency map), so only the frame's own per-message addressing can say who actually sent it. */
|
|
42
42
|
fromDevice?: DeviceId;
|
|
43
|
+
/** The device-id this request's relay-data frame was explicitly addressed to, read from its own `to-device` field -- present only when the request arrived relay-wrapped and the frame carried one. A caller fronting more than one locally-addressable device behind a single hub connection (a gateway advertising several local peers through the same relay pairing) uses this to decide whether the request is for this device or should be routed on to a different local peer it also advertises; this session has no such routing logic of its own, since it represents exactly one identity. */
|
|
44
|
+
toDevice?: DeviceId;
|
|
43
45
|
respond: (outcome: ManageOutcome) => Promise<void>;
|
|
44
46
|
}
|
|
45
47
|
export type HandshakeStatus = {
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { deviceIdToHex } from "./device-id.mjs";
|
|
2
2
|
import { negotiate } from "./handshake.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { tryDecodeFrame, wrapRelayData } from "../adapters/frame-codec.mjs";
|
|
4
|
+
//#region src/domain/relay-pairing.ts
|
|
5
|
+
function createRelayPairings() {
|
|
6
|
+
const established = /* @__PURE__ */ new Map();
|
|
7
|
+
return {
|
|
8
|
+
has: (device) => established.has(deviceIdToHex(device)),
|
|
9
|
+
add: (device) => {
|
|
10
|
+
established.set(deviceIdToHex(device), device);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
4
15
|
//#region src/domain/mesh-session.ts
|
|
5
16
|
const MS_PER_SECOND = 1e3;
|
|
6
17
|
/** peer-advert's own three typed fields -- reserved so a `sendGossipUpdate` caller can never override the session's own device-id, address list, or freshness timestamp by supplying an extension of the same name. */
|
|
@@ -44,7 +55,7 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
44
55
|
let attempt = 0;
|
|
45
56
|
let currentToken = null;
|
|
46
57
|
let nextRequestId = 0;
|
|
47
|
-
|
|
58
|
+
const relayPairings = createRelayPairings();
|
|
48
59
|
const pendingManageRequests = /* @__PURE__ */ new Map();
|
|
49
60
|
const incomingWaiters = [];
|
|
50
61
|
const incomingBacklog = [];
|
|
@@ -89,15 +100,11 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
89
100
|
...token !== null ? { token } : {}
|
|
90
101
|
};
|
|
91
102
|
}
|
|
92
|
-
/** Sends a frame, wrapping it as relay-data first when viaRelay is set -- the single choke point every outbound manage-request/manage-response passes through, so a consumer of sendManageRequest/respond never needs its own relay-wrapping logic. */
|
|
93
|
-
async function transmit(frame, viaRelay) {
|
|
103
|
+
/** Sends a frame, wrapping it as relay-data first when viaRelay is set -- the single choke point every outbound manage-request/manage-response passes through, so a consumer of sendManageRequest/respond never needs its own relay-wrapping logic. When relaying, toDevice is stamped onto the outer relay-data-frame's own `to-device` field so the hub addresses it to the correct pairing directly (wire-mesh#30) rather than falling back to whichever pairing it last saw -- the one case this is omitted is a response to a request that itself arrived with no from-device to echo back, which is left to that same hub fallback exactly as an unaddressed relay-data always has been. */
|
|
104
|
+
async function transmit(frame, viaRelay, toDevice) {
|
|
94
105
|
if (connection === null) throw new Error("not connected");
|
|
95
106
|
if (viaRelay) {
|
|
96
|
-
|
|
97
|
-
type: "relay-data",
|
|
98
|
-
payload: messageFromFrame(frame)
|
|
99
|
-
};
|
|
100
|
-
await connection.send(relayFrame);
|
|
107
|
+
await connection.send(wrapRelayData(frame, toDevice));
|
|
101
108
|
return;
|
|
102
109
|
}
|
|
103
110
|
await connection.send(frame);
|
|
@@ -110,15 +117,18 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
110
117
|
pending.resolve(frame.outcome);
|
|
111
118
|
}
|
|
112
119
|
}
|
|
113
|
-
|
|
120
|
+
/** relayFrame is present only for a manage-request that arrived wrapped in relay-data, and is that same outer relay-data-frame -- its own to-device/from-device fields carry whatever addressing it received. See IncomingManageRequest's own fromDevice/toDevice doc comments for what each means and why neither is ever guessed from pairing state. */
|
|
121
|
+
function applyManageRequest(frame, relayFrame) {
|
|
114
122
|
const requestId = frame["request-id"];
|
|
115
|
-
const fromDevice =
|
|
123
|
+
const fromDevice = relayFrame?.["from-device"];
|
|
124
|
+
const toDevice = relayFrame?.["to-device"];
|
|
116
125
|
emitIncomingManageRequest({
|
|
117
126
|
requestId,
|
|
118
127
|
command: frame.command,
|
|
119
128
|
scope: frame.scope,
|
|
120
129
|
...frame.token !== void 0 ? { token: frame.token } : {},
|
|
121
130
|
...fromDevice !== void 0 ? { fromDevice } : {},
|
|
131
|
+
...toDevice !== void 0 ? { toDevice } : {},
|
|
122
132
|
respond: async (outcome) => {
|
|
123
133
|
const response = {
|
|
124
134
|
type: "manage-response",
|
|
@@ -129,7 +139,7 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
129
139
|
direction: "sent",
|
|
130
140
|
frame: response
|
|
131
141
|
});
|
|
132
|
-
await transmit(response,
|
|
142
|
+
await transmit(response, relayFrame !== void 0, fromDevice);
|
|
133
143
|
emit();
|
|
134
144
|
}
|
|
135
145
|
});
|
|
@@ -143,7 +153,7 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
143
153
|
frame: inner
|
|
144
154
|
});
|
|
145
155
|
if (inner.type === "manage-response") applyManageResponse(inner);
|
|
146
|
-
else applyManageRequest(inner,
|
|
156
|
+
else applyManageRequest(inner, frame);
|
|
147
157
|
return;
|
|
148
158
|
}
|
|
149
159
|
frameLog.push({
|
|
@@ -164,15 +174,15 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
164
174
|
});
|
|
165
175
|
onPeerAdvert?.(advert);
|
|
166
176
|
}
|
|
167
|
-
else if (frame.type === "relay-inbound")
|
|
177
|
+
else if (frame.type === "relay-inbound") relayPairings.add(frame["source-device"]);
|
|
168
178
|
else if (frame.type === "manage-response") applyManageResponse(frame);
|
|
169
|
-
else if (frame.type === "manage-request") applyManageRequest(frame
|
|
179
|
+
else if (frame.type === "manage-request") applyManageRequest(frame);
|
|
170
180
|
else if (frame.type === "revocation-announce") for (const entry of frame.entries) emitRevocationEntry(entry);
|
|
171
181
|
}
|
|
172
|
-
/** Establishes a relay-connect pairing to targetDevice if this session isn't already paired with it -- a no-op when it already is, whether that pairing was established by this session's own prior relay-connect (initiator role) or learned from an incoming relay-inbound (target role, replying back to whoever dialed it). relay-connect has no ack frame: the initiator proceeds to relay-data right after sending it. */
|
|
182
|
+
/** Establishes a relay-connect pairing to targetDevice if this session isn't already paired with it -- a no-op when it already is, whether that pairing was established by this session's own prior relay-connect (initiator role) or learned from an incoming relay-inbound (target role, replying back to whoever dialed it). Pairing with a new target never tears down an existing pairing with a different one: this connection can hold several simultaneously (wire-mesh#30's own multiplexed adjacency map), so a later request back to an already-paired target must not re-send relay-connect for it. relay-connect has no ack frame: the initiator proceeds to relay-data right after sending it. */
|
|
173
183
|
async function ensureRelayPairing(targetDevice) {
|
|
174
184
|
if (connection === null) throw new Error("not connected");
|
|
175
|
-
if (
|
|
185
|
+
if (relayPairings.has(targetDevice)) return;
|
|
176
186
|
const relayConnect = {
|
|
177
187
|
type: "relay-connect",
|
|
178
188
|
"target-device": targetDevice
|
|
@@ -182,7 +192,7 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
182
192
|
frame: relayConnect
|
|
183
193
|
});
|
|
184
194
|
await connection.send(relayConnect);
|
|
185
|
-
|
|
195
|
+
relayPairings.add(targetDevice);
|
|
186
196
|
emit();
|
|
187
197
|
}
|
|
188
198
|
let localHandshakeSent = localHandshake([]);
|
|
@@ -394,7 +404,7 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
|
|
|
394
404
|
direction: "sent",
|
|
395
405
|
frame
|
|
396
406
|
});
|
|
397
|
-
await transmit(frame, targetDevice !== void 0);
|
|
407
|
+
await transmit(frame, targetDevice !== void 0, targetDevice);
|
|
398
408
|
emit();
|
|
399
409
|
if (timeoutMs === void 0) return outcome;
|
|
400
410
|
return Promise.race([outcome, new Promise((resolve) => {
|