@peers-app/peers-sdk 0.23.14 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/context/user-context.d.ts +8 -0
- package/dist/context/user-context.js +9 -1
- package/dist/contracts/index.d.ts +1 -0
- package/dist/contracts/index.js +4 -1
- package/dist/contracts/system/index.d.ts +2 -0
- package/dist/contracts/system/index.js +5 -1
- package/dist/contracts/system/invites-contract.d.ts +121 -0
- package/dist/contracts/system/invites-contract.js +150 -0
- package/dist/data/create-group.d.ts +24 -0
- package/dist/data/create-group.js +61 -0
- package/dist/data/group-secret.d.ts +10 -0
- package/dist/data/group-secret.js +26 -0
- package/dist/data/group-share.d.ts +0 -20
- package/dist/data/group-share.js +0 -35
- package/dist/data/index.d.ts +2 -0
- package/dist/data/index.js +2 -0
- package/dist/data/invites.d.ts +350 -0
- package/dist/data/invites.js +99 -0
- package/dist/data/orm/data-query.d.ts +1 -0
- package/dist/data/orm/sql.data-source.d.ts +3 -2
- package/dist/data/orm/sql.data-source.js +5 -3
- package/dist/data/orm/subscribable.data-source.d.ts +3 -2
- package/dist/data/orm/subscribable.data-source.js +7 -11
- package/dist/data/users.d.ts +11 -0
- package/dist/data/users.js +17 -0
- package/dist/device/device-election.d.ts +23 -3
- package/dist/device/device-election.js +15 -0
- package/dist/device/device-election.test.js +73 -0
- package/dist/device-pairing/constants.d.ts +27 -9
- package/dist/device-pairing/constants.js +28 -10
- package/dist/device-pairing/cpace.d.ts +46 -0
- package/dist/device-pairing/cpace.js +173 -0
- package/dist/device-pairing/cpace.test.js +116 -0
- package/dist/device-pairing/crypto.d.ts +40 -15
- package/dist/device-pairing/crypto.js +74 -33
- package/dist/device-pairing/device-pairing.test.js +156 -119
- package/dist/device-pairing/index.d.ts +3 -1
- package/dist/device-pairing/index.js +3 -1
- package/dist/device-pairing/pairing-code.d.ts +27 -0
- package/dist/device-pairing/pairing-code.js +84 -0
- package/dist/device-pairing/pgp-word-list.d.ts +14 -0
- package/dist/device-pairing/pgp-word-list.js +531 -0
- package/dist/device-pairing/signaling.d.ts +17 -6
- package/dist/device-pairing/signaling.js +31 -8
- package/dist/device-pairing/transcript.d.ts +3 -3
- package/dist/device-pairing/transcript.js +6 -13
- package/dist/device-pairing/types.d.ts +870 -224
- package/dist/device-pairing/types.js +186 -108
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -2
- package/dist/invites/data-box-schema.d.ts +15 -0
- package/dist/invites/data-box-schema.js +15 -0
- package/dist/invites/group-approval.d.ts +43 -0
- package/dist/invites/group-approval.js +155 -0
- package/dist/invites/group-approval.test.js +221 -0
- package/dist/invites/index.d.ts +5 -0
- package/dist/{group-invite → invites}/index.js +5 -3
- package/dist/invites/invite-epoch.d.ts +18 -0
- package/dist/invites/invite-epoch.js +35 -0
- package/dist/invites/invite-messages.d.ts +1354 -0
- package/dist/invites/invite-messages.js +145 -0
- package/dist/invites/invite-token.d.ts +334 -0
- package/dist/invites/invite-token.js +178 -0
- package/dist/invites/invite-token.test.d.ts +1 -0
- package/dist/invites/invite-token.test.js +123 -0
- package/dist/keys.d.ts +17 -1
- package/dist/keys.js +58 -5
- package/dist/keys.test.js +113 -0
- package/dist/rpc-types.d.ts +5 -4
- package/dist/rpc-types.js +2 -1
- package/dist/serial-json.d.ts +4 -1
- package/dist/serial-json.js +4 -1
- package/dist/system-ids.d.ts +22 -0
- package/dist/system-ids.js +23 -1
- package/package.json +1 -1
- package/dist/device-pairing/invitation.d.ts +0 -13
- package/dist/device-pairing/invitation.js +0 -85
- package/dist/group-invite/group-invite.d.ts +0 -53
- package/dist/group-invite/group-invite.js +0 -231
- package/dist/group-invite/group-invite.pvars.d.ts +0 -94
- package/dist/group-invite/group-invite.pvars.js +0 -139
- package/dist/group-invite/group-invite.types.d.ts +0 -124
- package/dist/group-invite/group-invite.types.js +0 -8
- package/dist/group-invite/index.d.ts +0 -3
- package/dist/user-connect/connection-code.d.ts +0 -67
- package/dist/user-connect/connection-code.js +0 -176
- package/dist/user-connect/connection-code.test.js +0 -213
- package/dist/user-connect/index.d.ts +0 -3
- package/dist/user-connect/index.js +0 -19
- package/dist/user-connect/user-connect.pvars.d.ts +0 -20
- package/dist/user-connect/user-connect.pvars.js +0 -63
- package/dist/user-connect/user-connect.pvars.test.js +0 -46
- package/dist/user-connect/user-connect.types.d.ts +0 -94
- package/dist/user-connect/user-connect.types.js +0 -68
- /package/dist/{user-connect/connection-code.test.d.ts → device-pairing/cpace.test.d.ts} +0 -0
- /package/dist/{user-connect/user-connect.pvars.test.d.ts → invites/group-approval.test.d.ts} +0 -0
|
@@ -308,6 +308,79 @@ describe("device-election", () => {
|
|
|
308
308
|
expect(result).toBeDefined();
|
|
309
309
|
expect([device1, device2, device3]).toContain(result?.deviceId);
|
|
310
310
|
});
|
|
311
|
+
describe("alternativeRoutes", () => {
|
|
312
|
+
it("never sheds a device whose only known route is through us when others have alternatives", () => {
|
|
313
|
+
const stranded = (0, utils_1.newid)();
|
|
314
|
+
const others = [(0, utils_1.newid)(), (0, utils_1.newid)(), (0, utils_1.newid)()];
|
|
315
|
+
const connections = [
|
|
316
|
+
// worst latency and most groups would normally make this the victim
|
|
317
|
+
{ ...createConnection(stranded, 900, 0.5), groups: ["g"], alternativeRoutes: 0 },
|
|
318
|
+
...others.map((id) => ({
|
|
319
|
+
...createConnection(id, 5, 0.001),
|
|
320
|
+
groups: ["g", "h"],
|
|
321
|
+
alternativeRoutes: 3,
|
|
322
|
+
})),
|
|
323
|
+
];
|
|
324
|
+
for (let i = 0; i < 20; i++) {
|
|
325
|
+
const result = (0, device_election_1.getLeastPreferredConnection)({
|
|
326
|
+
connections: (0, lodash_1.shuffle)(connections),
|
|
327
|
+
preferredDeviceIds: [],
|
|
328
|
+
preferredByDeviceIds: [],
|
|
329
|
+
});
|
|
330
|
+
expect(result).toBeDefined();
|
|
331
|
+
expect(result?.deviceId).not.toBe(stranded);
|
|
332
|
+
expect(others).toContain(result?.deviceId);
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
it("falls back to the usual ordering when no candidate is known to have alternatives", () => {
|
|
336
|
+
const device1 = (0, utils_1.newid)();
|
|
337
|
+
const device2 = (0, utils_1.newid)();
|
|
338
|
+
const connections = [
|
|
339
|
+
{ ...createConnection(device1, 10, 0.001), groups: ["g"], alternativeRoutes: 0 },
|
|
340
|
+
{ ...createConnection(device2, 500, 0.5), groups: ["g"], alternativeRoutes: 0 },
|
|
341
|
+
];
|
|
342
|
+
const result = (0, device_election_1.getLeastPreferredConnection)({
|
|
343
|
+
connections,
|
|
344
|
+
preferredDeviceIds: [],
|
|
345
|
+
preferredByDeviceIds: [],
|
|
346
|
+
});
|
|
347
|
+
expect(result?.deviceId).toBe(device2);
|
|
348
|
+
});
|
|
349
|
+
it("treats unknown topology as neither protected nor preferred", () => {
|
|
350
|
+
const unknown = (0, utils_1.newid)();
|
|
351
|
+
const stranded = (0, utils_1.newid)();
|
|
352
|
+
const connected = (0, utils_1.newid)();
|
|
353
|
+
const connections = [
|
|
354
|
+
{ ...createConnection(unknown, 500, 0.5), groups: ["g"] },
|
|
355
|
+
{ ...createConnection(stranded, 10, 0.001), groups: ["g"], alternativeRoutes: 0 },
|
|
356
|
+
{ ...createConnection(connected, 10, 0.001), groups: ["g"], alternativeRoutes: 2 },
|
|
357
|
+
];
|
|
358
|
+
for (let i = 0; i < 20; i++) {
|
|
359
|
+
const result = (0, device_election_1.getLeastPreferredConnection)({
|
|
360
|
+
connections: (0, lodash_1.shuffle)(connections),
|
|
361
|
+
preferredDeviceIds: [],
|
|
362
|
+
preferredByDeviceIds: [],
|
|
363
|
+
});
|
|
364
|
+
// the stranded device is excluded; unknown stays in the pool and loses on latency
|
|
365
|
+
expect(result?.deviceId).toBe(unknown);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
it("still respects preference tiers before route protection", () => {
|
|
369
|
+
const stranded = (0, utils_1.newid)();
|
|
370
|
+
const preferred = (0, utils_1.newid)();
|
|
371
|
+
const connections = [
|
|
372
|
+
{ ...createConnection(stranded), groups: ["g"], alternativeRoutes: 0 },
|
|
373
|
+
{ ...createConnection(preferred), groups: ["g"], alternativeRoutes: 4 },
|
|
374
|
+
];
|
|
375
|
+
// the only non-preferred candidate is the stranded one, so the tier leaves just it
|
|
376
|
+
const result = (0, device_election_1.getLeastPreferredConnection)({
|
|
377
|
+
connections,
|
|
378
|
+
preferredDeviceIds: [preferred],
|
|
379
|
+
preferredByDeviceIds: [],
|
|
380
|
+
});
|
|
381
|
+
expect(result?.deviceId).toBe(stranded);
|
|
382
|
+
});
|
|
383
|
+
});
|
|
311
384
|
});
|
|
312
385
|
describe("Network simulations", () => {
|
|
313
386
|
it.skip("should work as connections grow", () => {
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Wire-protocol version for the temporary device-pairing ceremony.
|
|
3
|
+
*
|
|
4
|
+
* - v2: reversed roles + PAKE.
|
|
5
|
+
* - v3: negotiated data transport (WebRTC or direct WebSocket); endpoint nonces move from the
|
|
6
|
+
* SDP descriptions into the transport messages and the transcript binds `transportBinding`.
|
|
7
|
+
*/
|
|
8
|
+
export declare const DEVICE_PAIRING_PROTOCOL_VERSION: 3;
|
|
9
|
+
/** Maximum lifetime of a pairing code and its signaling room. */
|
|
4
10
|
export declare const DEVICE_PAIRING_TTL_MS: number;
|
|
11
|
+
/** Largest nameplate the service allocates; codes read `<1..9999>-<word>-<word>-<word>`. */
|
|
12
|
+
export declare const DEVICE_PAIRING_MAX_NAMEPLATE = 9999;
|
|
5
13
|
/** Socket.IO namespace reserved for device-pairing signaling. */
|
|
6
14
|
export declare const DEVICE_PAIRING_SIGNALING_NAMESPACE = "/device-pairing-v1";
|
|
7
15
|
/** Dedicated Socket.IO engine path with a pairing-sized transport buffer. */
|
|
@@ -22,13 +30,23 @@ export declare const DEVICE_PAIRING_MAX_SIGNAL_BYTES: number;
|
|
|
22
30
|
export declare const DEVICE_PAIRING_MAX_TRANSPORT_BYTES: number;
|
|
23
31
|
/** Maximum ICE candidates accepted from either endpoint. */
|
|
24
32
|
export declare const DEVICE_PAIRING_MAX_ICE_CANDIDATES_PER_ROLE = 64;
|
|
33
|
+
/** Maximum direct WebSocket URLs a destination may advertise in its transport options. */
|
|
34
|
+
export declare const DEVICE_PAIRING_MAX_WEBSOCKET_URLS = 8;
|
|
35
|
+
/**
|
|
36
|
+
* Socket.IO namespace a destination hosts for the direct WebSocket pairing transport. The
|
|
37
|
+
* source authenticates with the PAKE-derived WebSocket token in `auth.token`.
|
|
38
|
+
*/
|
|
39
|
+
export declare const DEVICE_PAIRING_WEBSOCKET_NAMESPACE = "/device-pairing-transport-v1";
|
|
25
40
|
/** Maximum tolerated source/destination wall-clock difference. */
|
|
26
41
|
export declare const DEVICE_PAIRING_CLOCK_SKEW_MS: number;
|
|
27
42
|
/** Event name used to publish renderer-safe pairing state. */
|
|
28
43
|
export declare const DEVICE_PAIRING_STATE_EVENT = "device-pairing:state";
|
|
29
|
-
/** Domain-separation label for the
|
|
30
|
-
export declare const
|
|
31
|
-
/** Domain-separation label for
|
|
32
|
-
export declare const
|
|
33
|
-
/**
|
|
34
|
-
|
|
44
|
+
/** Domain-separation label for the PAKE-derived signaling authentication key. */
|
|
45
|
+
export declare const DEVICE_PAIRING_SIGNALING_LABEL = "peers-pairing-signaling-v3";
|
|
46
|
+
/** Domain-separation label for the PAKE-derived transcript confirmation key. */
|
|
47
|
+
export declare const DEVICE_PAIRING_CONFIRM_LABEL = "peers-pairing-confirm-v3";
|
|
48
|
+
/**
|
|
49
|
+
* Domain-separation label for the token a source presents when it connects to a destination's
|
|
50
|
+
* direct WebSocket pairing listener. Derived from the signaling key; never sent via the relay.
|
|
51
|
+
*/
|
|
52
|
+
export declare const DEVICE_PAIRING_WEBSOCKET_TOKEN_LABEL = "peers-pairing-websocket-token-v3";
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
/**
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
exports.DEVICE_PAIRING_WEBSOCKET_TOKEN_LABEL = exports.DEVICE_PAIRING_CONFIRM_LABEL = exports.DEVICE_PAIRING_SIGNALING_LABEL = exports.DEVICE_PAIRING_STATE_EVENT = exports.DEVICE_PAIRING_CLOCK_SKEW_MS = exports.DEVICE_PAIRING_WEBSOCKET_NAMESPACE = exports.DEVICE_PAIRING_MAX_WEBSOCKET_URLS = exports.DEVICE_PAIRING_MAX_ICE_CANDIDATES_PER_ROLE = exports.DEVICE_PAIRING_MAX_TRANSPORT_BYTES = exports.DEVICE_PAIRING_MAX_SIGNAL_BYTES = exports.DEVICE_PAIRING_SIGNALING_EVENTS = exports.DEVICE_PAIRING_SIGNALING_PATH = exports.DEVICE_PAIRING_SIGNALING_NAMESPACE = exports.DEVICE_PAIRING_MAX_NAMEPLATE = exports.DEVICE_PAIRING_TTL_MS = exports.DEVICE_PAIRING_PROTOCOL_VERSION = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Wire-protocol version for the temporary device-pairing ceremony.
|
|
6
|
+
*
|
|
7
|
+
* - v2: reversed roles + PAKE.
|
|
8
|
+
* - v3: negotiated data transport (WebRTC or direct WebSocket); endpoint nonces move from the
|
|
9
|
+
* SDP descriptions into the transport messages and the transcript binds `transportBinding`.
|
|
10
|
+
*/
|
|
11
|
+
exports.DEVICE_PAIRING_PROTOCOL_VERSION = 3;
|
|
12
|
+
/** Maximum lifetime of a pairing code and its signaling room. */
|
|
7
13
|
exports.DEVICE_PAIRING_TTL_MS = 5 * 60 * 1000;
|
|
14
|
+
/** Largest nameplate the service allocates; codes read `<1..9999>-<word>-<word>-<word>`. */
|
|
15
|
+
exports.DEVICE_PAIRING_MAX_NAMEPLATE = 9999;
|
|
8
16
|
/** Socket.IO namespace reserved for device-pairing signaling. */
|
|
9
17
|
exports.DEVICE_PAIRING_SIGNALING_NAMESPACE = "/device-pairing-v1";
|
|
10
18
|
/** Dedicated Socket.IO engine path with a pairing-sized transport buffer. */
|
|
@@ -25,13 +33,23 @@ exports.DEVICE_PAIRING_MAX_SIGNAL_BYTES = 72 * 1024;
|
|
|
25
33
|
exports.DEVICE_PAIRING_MAX_TRANSPORT_BYTES = 96 * 1024;
|
|
26
34
|
/** Maximum ICE candidates accepted from either endpoint. */
|
|
27
35
|
exports.DEVICE_PAIRING_MAX_ICE_CANDIDATES_PER_ROLE = 64;
|
|
36
|
+
/** Maximum direct WebSocket URLs a destination may advertise in its transport options. */
|
|
37
|
+
exports.DEVICE_PAIRING_MAX_WEBSOCKET_URLS = 8;
|
|
38
|
+
/**
|
|
39
|
+
* Socket.IO namespace a destination hosts for the direct WebSocket pairing transport. The
|
|
40
|
+
* source authenticates with the PAKE-derived WebSocket token in `auth.token`.
|
|
41
|
+
*/
|
|
42
|
+
exports.DEVICE_PAIRING_WEBSOCKET_NAMESPACE = "/device-pairing-transport-v1";
|
|
28
43
|
/** Maximum tolerated source/destination wall-clock difference. */
|
|
29
44
|
exports.DEVICE_PAIRING_CLOCK_SKEW_MS = 2 * 60 * 1000;
|
|
30
45
|
/** Event name used to publish renderer-safe pairing state. */
|
|
31
46
|
exports.DEVICE_PAIRING_STATE_EVENT = "device-pairing:state";
|
|
32
|
-
/** Domain-separation label for the
|
|
33
|
-
exports.
|
|
34
|
-
/** Domain-separation label for
|
|
35
|
-
exports.
|
|
36
|
-
/**
|
|
37
|
-
|
|
47
|
+
/** Domain-separation label for the PAKE-derived signaling authentication key. */
|
|
48
|
+
exports.DEVICE_PAIRING_SIGNALING_LABEL = "peers-pairing-signaling-v3";
|
|
49
|
+
/** Domain-separation label for the PAKE-derived transcript confirmation key. */
|
|
50
|
+
exports.DEVICE_PAIRING_CONFIRM_LABEL = "peers-pairing-confirm-v3";
|
|
51
|
+
/**
|
|
52
|
+
* Domain-separation label for the token a source presents when it connects to a destination's
|
|
53
|
+
* direct WebSocket pairing listener. Derived from the signaling key; never sent via the relay.
|
|
54
|
+
*/
|
|
55
|
+
exports.DEVICE_PAIRING_WEBSOCKET_TOKEN_LABEL = "peers-pairing-websocket-token-v3";
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** Role in the one-round CPace exchange; the initiator's share is listed first in the transcript. */
|
|
2
|
+
export type CpaceRole = "initiator" | "responder";
|
|
3
|
+
/** Inputs that both parties must agree on for the exchange to succeed. */
|
|
4
|
+
export interface CpaceParameters {
|
|
5
|
+
/** Password-related string (PRS); for pairing, the code words. */
|
|
6
|
+
password: Uint8Array;
|
|
7
|
+
/** Channel identifier (CI) binding the exchange to one rendezvous. */
|
|
8
|
+
channelId: Uint8Array;
|
|
9
|
+
/** Optional session id (sid); empty when the channel identifier is already unique. */
|
|
10
|
+
sessionId?: Uint8Array;
|
|
11
|
+
/** Associated data sent alongside the initiator's share. */
|
|
12
|
+
initiatorAd?: Uint8Array;
|
|
13
|
+
/** Associated data sent alongside the responder's share. */
|
|
14
|
+
responderAd?: Uint8Array;
|
|
15
|
+
}
|
|
16
|
+
/** One party's in-progress exchange. */
|
|
17
|
+
export interface CpaceSession {
|
|
18
|
+
/** Public share (32-byte X25519 u-coordinate) to send to the other party. */
|
|
19
|
+
readonly share: Uint8Array;
|
|
20
|
+
/**
|
|
21
|
+
* Consumes the remote share and returns the 64-byte ISK.
|
|
22
|
+
* @throws when the remote share encodes a low-order point (the exchange must be aborted).
|
|
23
|
+
*/
|
|
24
|
+
finish(remoteShare: Uint8Array): Uint8Array;
|
|
25
|
+
}
|
|
26
|
+
/** LEB128 length prefix as defined by the draft's `prepend_len`. */
|
|
27
|
+
export declare function cpacePrependLength(data: Uint8Array): Uint8Array;
|
|
28
|
+
/** Length-prefixed concatenation (`lv_cat`). */
|
|
29
|
+
export declare function cpaceLvCat(...parts: Uint8Array[]): Uint8Array;
|
|
30
|
+
/** Builds the `generator_string` that is hashed into the secret generator. */
|
|
31
|
+
export declare function cpaceGeneratorString(password: Uint8Array, channelId: Uint8Array, sessionId: Uint8Array): Uint8Array;
|
|
32
|
+
/** Elligator 2 map from a field element to a Curve25519 u-coordinate (RFC 9380 `map_to_curve_elligator2`). */
|
|
33
|
+
export declare function cpaceElligator2(u: bigint): Uint8Array;
|
|
34
|
+
/** Derives the password-dependent generator `g` for one exchange. */
|
|
35
|
+
export declare function cpaceCalculateGenerator(password: Uint8Array, channelId: Uint8Array, sessionId?: Uint8Array): Uint8Array;
|
|
36
|
+
/** X25519 scalar multiplication that rejects results in the small subgroup (`scalar_mult_vfy`). */
|
|
37
|
+
export declare function cpaceScalarMultVerify(scalar: Uint8Array, point: Uint8Array): Uint8Array;
|
|
38
|
+
/** Initiator/responder transcript (`transcript_ir`). */
|
|
39
|
+
export declare function cpaceTranscript(initiatorShare: Uint8Array, initiatorAd: Uint8Array, responderShare: Uint8Array, responderAd: Uint8Array): Uint8Array;
|
|
40
|
+
/** Final ISK derivation: `H(lv_cat(DSI_ISK, sid, K) || transcript)`. */
|
|
41
|
+
export declare function cpaceDeriveIsk(sharedPoint: Uint8Array, sessionId: Uint8Array, transcript: Uint8Array): Uint8Array;
|
|
42
|
+
/**
|
|
43
|
+
* Starts one side of a CPace exchange. The returned share is safe to send in the clear.
|
|
44
|
+
* `scalar` may be injected for deterministic test vectors; production callers omit it.
|
|
45
|
+
*/
|
|
46
|
+
export declare function cpaceStart(role: CpaceRole, parameters: CpaceParameters, scalar?: Uint8Array): CpaceSession;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cpacePrependLength = cpacePrependLength;
|
|
4
|
+
exports.cpaceLvCat = cpaceLvCat;
|
|
5
|
+
exports.cpaceGeneratorString = cpaceGeneratorString;
|
|
6
|
+
exports.cpaceElligator2 = cpaceElligator2;
|
|
7
|
+
exports.cpaceCalculateGenerator = cpaceCalculateGenerator;
|
|
8
|
+
exports.cpaceScalarMultVerify = cpaceScalarMultVerify;
|
|
9
|
+
exports.cpaceTranscript = cpaceTranscript;
|
|
10
|
+
exports.cpaceDeriveIsk = cpaceDeriveIsk;
|
|
11
|
+
exports.cpaceStart = cpaceStart;
|
|
12
|
+
const sha2_1 = require("@noble/hashes/sha2");
|
|
13
|
+
const nacl = require("tweetnacl");
|
|
14
|
+
/**
|
|
15
|
+
* CPace balanced PAKE over X25519 with SHA-512 (cipher suite `CPace255`), implemented per
|
|
16
|
+
* draft-irtf-cfrg-cpace. Both parties derive the same 64-byte intermediate session key (ISK)
|
|
17
|
+
* if and only if they used the same password, channel identifier, and session id, and saw
|
|
18
|
+
* each other's public shares unmodified. A wrong password yields an unrelated ISK; nothing
|
|
19
|
+
* about the password can be recovered from the transcript.
|
|
20
|
+
*
|
|
21
|
+
* Group operations use `tweetnacl`'s X25519 (`scalarMult`); the Elligator 2 map that turns
|
|
22
|
+
* the hashed password into a secret generator is implemented here on `bigint` field
|
|
23
|
+
* arithmetic (Curve25519, `A = 486662`, `Z = 2`). Verified against the draft's test vectors.
|
|
24
|
+
*/
|
|
25
|
+
const FIELD_PRIME = 2n ** 255n - 19n;
|
|
26
|
+
const CURVE_A = 486662n;
|
|
27
|
+
const ELLIGATOR_Z = 2n;
|
|
28
|
+
const FIELD_SIZE_BYTES = 32;
|
|
29
|
+
const HASH_BLOCK_BYTES = 128;
|
|
30
|
+
const DSI = new TextEncoder().encode("CPace255");
|
|
31
|
+
const DSI_ISK = new TextEncoder().encode("CPace255_ISK");
|
|
32
|
+
function mod(value) {
|
|
33
|
+
const result = value % FIELD_PRIME;
|
|
34
|
+
return result >= 0n ? result : result + FIELD_PRIME;
|
|
35
|
+
}
|
|
36
|
+
function modPow(base, exponent) {
|
|
37
|
+
let result = 1n;
|
|
38
|
+
let current = mod(base);
|
|
39
|
+
let remaining = exponent;
|
|
40
|
+
while (remaining > 0n) {
|
|
41
|
+
if (remaining & 1n) {
|
|
42
|
+
result = mod(result * current);
|
|
43
|
+
}
|
|
44
|
+
current = mod(current * current);
|
|
45
|
+
remaining >>= 1n;
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
function modInverse(value) {
|
|
50
|
+
return modPow(value, FIELD_PRIME - 2n);
|
|
51
|
+
}
|
|
52
|
+
/** LEB128 length prefix as defined by the draft's `prepend_len`. */
|
|
53
|
+
function cpacePrependLength(data) {
|
|
54
|
+
const prefix = [];
|
|
55
|
+
let length = data.length;
|
|
56
|
+
while (true) {
|
|
57
|
+
if (length < 128) {
|
|
58
|
+
prefix.push(length);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
prefix.push((length & 0x7f) + 0x80);
|
|
62
|
+
}
|
|
63
|
+
length >>= 7;
|
|
64
|
+
if (length === 0) {
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return concatBytes(Uint8Array.from(prefix), data);
|
|
69
|
+
}
|
|
70
|
+
/** Length-prefixed concatenation (`lv_cat`). */
|
|
71
|
+
function cpaceLvCat(...parts) {
|
|
72
|
+
return concatBytes(...parts.map((part) => cpacePrependLength(part)));
|
|
73
|
+
}
|
|
74
|
+
function concatBytes(...parts) {
|
|
75
|
+
const total = parts.reduce((sum, part) => sum + part.length, 0);
|
|
76
|
+
const result = new Uint8Array(total);
|
|
77
|
+
let offset = 0;
|
|
78
|
+
for (const part of parts) {
|
|
79
|
+
result.set(part, offset);
|
|
80
|
+
offset += part.length;
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
/** Builds the `generator_string` that is hashed into the secret generator. */
|
|
85
|
+
function cpaceGeneratorString(password, channelId, sessionId) {
|
|
86
|
+
const zeroPadLength = Math.max(0, HASH_BLOCK_BYTES - 1 - cpacePrependLength(password).length - cpacePrependLength(DSI).length);
|
|
87
|
+
return cpaceLvCat(DSI, password, new Uint8Array(zeroPadLength), channelId, sessionId);
|
|
88
|
+
}
|
|
89
|
+
function decodeUCoordinate(bytes) {
|
|
90
|
+
const copy = Uint8Array.from(bytes);
|
|
91
|
+
copy[FIELD_SIZE_BYTES - 1] &= 0x7f;
|
|
92
|
+
let value = 0n;
|
|
93
|
+
for (let index = copy.length - 1; index >= 0; index--) {
|
|
94
|
+
value = (value << 8n) | BigInt(copy[index]);
|
|
95
|
+
}
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
function encodeUCoordinate(value) {
|
|
99
|
+
const bytes = new Uint8Array(FIELD_SIZE_BYTES);
|
|
100
|
+
let remaining = mod(value);
|
|
101
|
+
for (let index = 0; index < FIELD_SIZE_BYTES; index++) {
|
|
102
|
+
bytes[index] = Number(remaining & 0xffn);
|
|
103
|
+
remaining >>= 8n;
|
|
104
|
+
}
|
|
105
|
+
return bytes;
|
|
106
|
+
}
|
|
107
|
+
/** Elligator 2 map from a field element to a Curve25519 u-coordinate (RFC 9380 `map_to_curve_elligator2`). */
|
|
108
|
+
function cpaceElligator2(u) {
|
|
109
|
+
const v = mod(-CURVE_A * modInverse(1n + ELLIGATOR_Z * u * u));
|
|
110
|
+
const legendre = modPow(mod(v * v * v + CURVE_A * v * v + v), (FIELD_PRIME - 1n) / 2n);
|
|
111
|
+
const x = mod(legendre * v - (1n - legendre) * CURVE_A * modInverse(2n));
|
|
112
|
+
return encodeUCoordinate(x);
|
|
113
|
+
}
|
|
114
|
+
/** Derives the password-dependent generator `g` for one exchange. */
|
|
115
|
+
function cpaceCalculateGenerator(password, channelId, sessionId = new Uint8Array(0)) {
|
|
116
|
+
const digest = (0, sha2_1.sha512)(cpaceGeneratorString(password, channelId, sessionId));
|
|
117
|
+
return cpaceElligator2(decodeUCoordinate(digest.slice(0, FIELD_SIZE_BYTES)));
|
|
118
|
+
}
|
|
119
|
+
function isAllZero(bytes) {
|
|
120
|
+
let accumulator = 0;
|
|
121
|
+
for (const byte of bytes) {
|
|
122
|
+
accumulator |= byte;
|
|
123
|
+
}
|
|
124
|
+
return accumulator === 0;
|
|
125
|
+
}
|
|
126
|
+
/** X25519 scalar multiplication that rejects results in the small subgroup (`scalar_mult_vfy`). */
|
|
127
|
+
function cpaceScalarMultVerify(scalar, point) {
|
|
128
|
+
if (scalar.length !== FIELD_SIZE_BYTES || point.length !== FIELD_SIZE_BYTES) {
|
|
129
|
+
throw new Error("CPace shares must be 32 bytes");
|
|
130
|
+
}
|
|
131
|
+
const result = nacl.scalarMult(scalar, point);
|
|
132
|
+
if (isAllZero(result)) {
|
|
133
|
+
throw new Error("CPace received a low-order point");
|
|
134
|
+
}
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
/** Initiator/responder transcript (`transcript_ir`). */
|
|
138
|
+
function cpaceTranscript(initiatorShare, initiatorAd, responderShare, responderAd) {
|
|
139
|
+
return concatBytes(cpaceLvCat(initiatorShare, initiatorAd), cpaceLvCat(responderShare, responderAd));
|
|
140
|
+
}
|
|
141
|
+
/** Final ISK derivation: `H(lv_cat(DSI_ISK, sid, K) || transcript)`. */
|
|
142
|
+
function cpaceDeriveIsk(sharedPoint, sessionId, transcript) {
|
|
143
|
+
return (0, sha2_1.sha512)(concatBytes(cpaceLvCat(DSI_ISK, sessionId, sharedPoint), transcript));
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Starts one side of a CPace exchange. The returned share is safe to send in the clear.
|
|
147
|
+
* `scalar` may be injected for deterministic test vectors; production callers omit it.
|
|
148
|
+
*/
|
|
149
|
+
function cpaceStart(role, parameters, scalar = nacl.randomBytes(FIELD_SIZE_BYTES)) {
|
|
150
|
+
const sessionId = parameters.sessionId ?? new Uint8Array(0);
|
|
151
|
+
const initiatorAd = parameters.initiatorAd ?? new Uint8Array(0);
|
|
152
|
+
const responderAd = parameters.responderAd ?? new Uint8Array(0);
|
|
153
|
+
const generator = cpaceCalculateGenerator(parameters.password, parameters.channelId, sessionId);
|
|
154
|
+
const share = cpaceScalarMultVerify(scalar, generator);
|
|
155
|
+
let finished = false;
|
|
156
|
+
return {
|
|
157
|
+
share,
|
|
158
|
+
finish(remoteShare) {
|
|
159
|
+
if (finished) {
|
|
160
|
+
throw new Error("CPace session already finished");
|
|
161
|
+
}
|
|
162
|
+
finished = true;
|
|
163
|
+
const sharedPoint = cpaceScalarMultVerify(scalar, remoteShare);
|
|
164
|
+
const transcript = role === "initiator"
|
|
165
|
+
? cpaceTranscript(share, initiatorAd, remoteShare, responderAd)
|
|
166
|
+
: cpaceTranscript(remoteShare, initiatorAd, share, responderAd);
|
|
167
|
+
const isk = cpaceDeriveIsk(sharedPoint, sessionId, transcript);
|
|
168
|
+
scalar.fill(0);
|
|
169
|
+
sharedPoint.fill(0);
|
|
170
|
+
return isk;
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const cpace_1 = require("./cpace");
|
|
4
|
+
// Test vectors from draft-irtf-cfrg-cpace, "CPace using group X25519 and hash SHA-512".
|
|
5
|
+
const VECTOR = {
|
|
6
|
+
PRS: "50617373776F7264",
|
|
7
|
+
CI: "0B415F696E69746961746F720B425F726573706F6E646572",
|
|
8
|
+
sid: "7E4B4791D6A8EF019B936C79FB7F2C57",
|
|
9
|
+
g: "D04BF6D41F6A289632A2E929FA29BEBD51092512A7829FDDE7D314B62F05A73F",
|
|
10
|
+
ya: "21B4F4BD9E64ED355C3EB676A28EBEDAF6D8F17BDC365995B319097153044080",
|
|
11
|
+
ADa: "414461",
|
|
12
|
+
Ya: "1D13C89278CDADD826F6D8D7F887701430F8380DDC17611CDD6DC989CE0C9F32",
|
|
13
|
+
yb: "848B0779FF415F0AF4EA14DF9DD1D3C29AC41D836C7808896C4EBA19C51AC40A",
|
|
14
|
+
ADb: "414462",
|
|
15
|
+
Yb: "248CCCF6D5CDC3646F0AD593F9E6CEF4E69D4945F8372E623512ECEA32185623",
|
|
16
|
+
K: "5B067EFFBDC0B2A0E1D907B21EBB25CFEDB96A852179A847C37E43EE71322C6B",
|
|
17
|
+
ISK_IR: "6E19B875F7A561D6B3CA3DBB9EF42AC55DE3E717881018204B8922B4D5E53BB2AA82C300BEA7B65D2B671DA71922DDF6472301B79BC270ADFA8BF413285F2263",
|
|
18
|
+
};
|
|
19
|
+
// scalar_mult_vfy vectors: low-order (and non-canonical low-order) inputs abort; the rest
|
|
20
|
+
// must produce the draft's exact outputs, proving bit #255 is cleared before the ladder.
|
|
21
|
+
const VFY_SCALAR = "AF46E36BF0527C9D3B16154B82465EDD62144C0AC1FC5A18506A2244BA449AFF";
|
|
22
|
+
const LOW_ORDER_POINTS = [
|
|
23
|
+
"0000000000000000000000000000000000000000000000000000000000000000",
|
|
24
|
+
"0100000000000000000000000000000000000000000000000000000000000000",
|
|
25
|
+
"ECFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F",
|
|
26
|
+
"E0EB7A7C3B41B8AE1656E3FAF19FC46ADA098DEB9C32B1FD866205165F49B800",
|
|
27
|
+
"5F9C95BCA3508C24B1D0B1559C83EF5B04445CC4581C8E86D8224EDDD09F1157",
|
|
28
|
+
"EDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F",
|
|
29
|
+
"EEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F",
|
|
30
|
+
];
|
|
31
|
+
const NON_CANONICAL_POINTS = [
|
|
32
|
+
[
|
|
33
|
+
"DAFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
|
|
34
|
+
"D8E2C776BBACD510D09FD9278B7EDCD25FC5AE9ADFBA3B6E040E8D3B71B21806",
|
|
35
|
+
],
|
|
36
|
+
[
|
|
37
|
+
"DBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
|
|
38
|
+
"C85C655EBE8BE44BA9C0FFDE69F2FE10194458D137F09BBFF725CE58803CDB38",
|
|
39
|
+
],
|
|
40
|
+
[
|
|
41
|
+
"D9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
|
|
42
|
+
"DB64DAFA9B8FDD136914E61461935FE92AA372CB056314E1231BC4EC12417456",
|
|
43
|
+
],
|
|
44
|
+
[
|
|
45
|
+
"CDEB7A7C3B41B8AE1656E3FAF19FC46ADA098DEB9C32B1FD866205165F49B880",
|
|
46
|
+
"E062DCD5376D58297BE2618C7498F55BAA07D7E03184E8AADA20BCA28888BF7A",
|
|
47
|
+
],
|
|
48
|
+
[
|
|
49
|
+
"4C9C95BCA3508C24B1D0B1559C83EF5B04445CC4581C8E86D8224EDDD09F11D7",
|
|
50
|
+
"993C6AD11C4C29DA9A56F7691FD0FF8D732E49DE6250B6C2E80003FF4629A175",
|
|
51
|
+
],
|
|
52
|
+
];
|
|
53
|
+
function hex(value) {
|
|
54
|
+
return Uint8Array.from(Buffer.from(value, "hex"));
|
|
55
|
+
}
|
|
56
|
+
function toHex(value) {
|
|
57
|
+
return Buffer.from(value).toString("hex").toUpperCase();
|
|
58
|
+
}
|
|
59
|
+
describe("cpace string utilities", () => {
|
|
60
|
+
it("matches prepend_len and lv_cat vectors", () => {
|
|
61
|
+
expect(toHex((0, cpace_1.cpacePrependLength)(new Uint8Array(0)))).toBe("00");
|
|
62
|
+
expect(toHex((0, cpace_1.cpacePrependLength)(Buffer.from("1234")))).toBe("0431323334");
|
|
63
|
+
expect(toHex((0, cpace_1.cpacePrependLength)(new Uint8Array(128))).slice(0, 4)).toBe("8001");
|
|
64
|
+
expect(toHex((0, cpace_1.cpaceLvCat)(Buffer.from("1234"), Buffer.from("5"), new Uint8Array(0), Buffer.from("678")))).toBe("043132333401350003363738");
|
|
65
|
+
});
|
|
66
|
+
it("matches transcript_ir vector", () => {
|
|
67
|
+
expect(toHex((0, cpace_1.cpaceTranscript)(Buffer.from("123"), Buffer.from("PartyA"), Buffer.from("234"), Buffer.from("PartyB")))).toBe("03313233065061727479410332333406506172747942");
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
describe("cpace CPace255 vectors", () => {
|
|
71
|
+
it("derives the generator string and secret generator", () => {
|
|
72
|
+
const generatorString = (0, cpace_1.cpaceGeneratorString)(hex(VECTOR.PRS), hex(VECTOR.CI), hex(VECTOR.sid));
|
|
73
|
+
expect(generatorString).toHaveLength(170);
|
|
74
|
+
expect(toHex(generatorString).startsWith("0843506163653235350850617373776F72646D00")).toBe(true);
|
|
75
|
+
expect(toHex((0, cpace_1.cpaceCalculateGenerator)(hex(VECTOR.PRS), hex(VECTOR.CI), hex(VECTOR.sid)))).toBe(VECTOR.g);
|
|
76
|
+
});
|
|
77
|
+
it("derives both shares, the shared point, and the initiator/responder ISK", () => {
|
|
78
|
+
const parameters = {
|
|
79
|
+
password: hex(VECTOR.PRS),
|
|
80
|
+
channelId: hex(VECTOR.CI),
|
|
81
|
+
sessionId: hex(VECTOR.sid),
|
|
82
|
+
initiatorAd: hex(VECTOR.ADa),
|
|
83
|
+
responderAd: hex(VECTOR.ADb),
|
|
84
|
+
};
|
|
85
|
+
const initiator = (0, cpace_1.cpaceStart)("initiator", parameters, hex(VECTOR.ya));
|
|
86
|
+
const responder = (0, cpace_1.cpaceStart)("responder", parameters, hex(VECTOR.yb));
|
|
87
|
+
expect(toHex(initiator.share)).toBe(VECTOR.Ya);
|
|
88
|
+
expect(toHex(responder.share)).toBe(VECTOR.Yb);
|
|
89
|
+
expect(toHex((0, cpace_1.cpaceScalarMultVerify)(hex(VECTOR.ya), hex(VECTOR.Yb)))).toBe(VECTOR.K);
|
|
90
|
+
expect(toHex((0, cpace_1.cpaceDeriveIsk)(hex(VECTOR.K), hex(VECTOR.sid), (0, cpace_1.cpaceTranscript)(hex(VECTOR.Ya), hex(VECTOR.ADa), hex(VECTOR.Yb), hex(VECTOR.ADb))))).toBe(VECTOR.ISK_IR);
|
|
91
|
+
const initiatorIsk = toHex(initiator.finish(hex(VECTOR.Yb)));
|
|
92
|
+
const responderIsk = toHex(responder.finish(hex(VECTOR.Ya)));
|
|
93
|
+
expect(initiatorIsk).toBe(VECTOR.ISK_IR);
|
|
94
|
+
expect(responderIsk).toBe(VECTOR.ISK_IR);
|
|
95
|
+
});
|
|
96
|
+
it("rejects low-order points and clears bit #255 of non-canonical inputs", () => {
|
|
97
|
+
for (const point of LOW_ORDER_POINTS) {
|
|
98
|
+
expect(() => (0, cpace_1.cpaceScalarMultVerify)(hex(VFY_SCALAR), hex(point))).toThrow(/low-order/);
|
|
99
|
+
}
|
|
100
|
+
for (const [point, expected] of NON_CANONICAL_POINTS) {
|
|
101
|
+
expect(toHex((0, cpace_1.cpaceScalarMultVerify)(hex(VFY_SCALAR), hex(point)))).toBe(expected);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
it("produces unrelated keys for different passwords and identical keys for the same password", () => {
|
|
105
|
+
const shared = { channelId: Buffer.from("channel"), sessionId: new Uint8Array(0) };
|
|
106
|
+
const good = (0, cpace_1.cpaceStart)("initiator", { ...shared, password: Buffer.from("guitar-revenge") });
|
|
107
|
+
const right = (0, cpace_1.cpaceStart)("responder", { ...shared, password: Buffer.from("guitar-revenge") });
|
|
108
|
+
const wrong = (0, cpace_1.cpaceStart)("responder", { ...shared, password: Buffer.from("guitar-reward") });
|
|
109
|
+
const rightShare = Uint8Array.from(right.share);
|
|
110
|
+
const wrongShare = Uint8Array.from(wrong.share);
|
|
111
|
+
const goodShare = Uint8Array.from(good.share);
|
|
112
|
+
expect(toHex(right.finish(goodShare))).toBe(toHex(good.finish(rightShare)));
|
|
113
|
+
const second = (0, cpace_1.cpaceStart)("initiator", { ...shared, password: Buffer.from("guitar-revenge") });
|
|
114
|
+
expect(toHex(wrong.finish(Uint8Array.from(second.share)))).not.toBe(toHex(second.finish(wrongShare)));
|
|
115
|
+
});
|
|
116
|
+
});
|
|
@@ -1,25 +1,50 @@
|
|
|
1
1
|
import type { IDeviceInfo } from "../data/devices";
|
|
2
2
|
import type { Device } from "../device/device";
|
|
3
|
-
import { type
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
import { type PairingCode } from "./pairing-code";
|
|
4
|
+
import { type PairingConfirmation, type PairingConfirmationContents, type PairingCredentialContents, type PairingRole, type SignedPairingInstallationReceipt, type UnsignedPairingSignal } from "./types";
|
|
5
|
+
/** Symmetric keys both endpoints derive from the PAKE intermediate session key. */
|
|
6
|
+
export interface PairingSessionKeys {
|
|
7
|
+
/** Authenticates transport, SDP, and ICE signaling relayed by the service. */
|
|
8
|
+
signalingKey: string;
|
|
9
|
+
/** Authenticates the post-handshake transcript confirmation. */
|
|
10
|
+
confirmKey: string;
|
|
11
|
+
}
|
|
12
|
+
/** One endpoint's in-progress PAKE over the pairing code. */
|
|
13
|
+
export interface PairingPake {
|
|
14
|
+
/** Base64url public share to send in the `pake` signal. */
|
|
15
|
+
share: string;
|
|
16
|
+
/** Consumes the remote share and derives the session keys; throws on an invalid share. */
|
|
17
|
+
finish(remoteShare: string): PairingSessionKeys;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Starts CPace for one role. The source is the initiator and the destination the responder;
|
|
21
|
+
* the exchange is bound to the service origin and nameplate so a relayed share from another
|
|
22
|
+
* room cannot be replayed.
|
|
23
|
+
*/
|
|
24
|
+
export declare function startPairingPake(role: PairingRole, code: PairingCode, serviceOrigin: string): PairingPake;
|
|
25
|
+
/** Derives independent signaling and confirmation keys from a PAKE intermediate session key. */
|
|
26
|
+
export declare function derivePairingSessionKeys(isk: Uint8Array): PairingSessionKeys;
|
|
27
|
+
/**
|
|
28
|
+
* Derives the token a source presents (`auth.token`) when it connects to the destination's direct
|
|
29
|
+
* WebSocket pairing listener. Both endpoints compute it from the signaling key, so it never
|
|
30
|
+
* travels through the rendezvous; a socket without it is refused before any Connection exists.
|
|
31
|
+
*/
|
|
32
|
+
export declare function derivePairingWebSocketToken(signalingKey: string): string;
|
|
10
33
|
/** Computes the MAC over the canonical unsigned signaling envelope. */
|
|
11
34
|
export declare function computePairingSignalMac(signal: UnsignedPairingSignal, signalingKey: string): string;
|
|
12
35
|
/** Verifies a signaling MAC without short-circuiting on the first differing byte. */
|
|
13
36
|
export declare function isPairingSignalMacValid(signal: UnsignedPairingSignal, mac: string, signalingKey: string): boolean;
|
|
14
|
-
/**
|
|
15
|
-
export declare function
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
37
|
+
/** Builds the local `pairing.confirm` message authenticated with the PAKE confirmation key. */
|
|
38
|
+
export declare function createPairingConfirmation(contents: Omit<PairingConfirmationContents, "version">, confirmKey: string): PairingConfirmation;
|
|
39
|
+
/**
|
|
40
|
+
* Verifies the remote confirmation's MAC, role, transcript, and identity binding.
|
|
41
|
+
* A MAC failure means the other endpoint used a different code or saw a different transcript.
|
|
42
|
+
*/
|
|
43
|
+
export declare function verifyPairingConfirmation(value: unknown, confirmKey: string, expected: {
|
|
20
44
|
role: PairingRole;
|
|
21
|
-
|
|
22
|
-
|
|
45
|
+
transcriptHash: string;
|
|
46
|
+
device: IDeviceInfo;
|
|
47
|
+
}): PairingConfirmation;
|
|
23
48
|
/** Signs and encrypts account credentials directly to the temporary destination identity. */
|
|
24
49
|
export declare function createPairingCredentialEnvelope(sourceDevice: Device, destinationDevice: IDeviceInfo, credentials: {
|
|
25
50
|
userId: string;
|