@acpantenna/relay-protocol 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +54 -2
- package/dist/bip39-english.d.ts +28 -0
- package/dist/bip39-english.d.ts.map +1 -0
- package/dist/bip39-english.js +28 -0
- package/dist/bip39-english.js.map +1 -0
- package/dist/bytes.d.ts +21 -0
- package/dist/bytes.d.ts.map +1 -0
- package/dist/bytes.js +114 -0
- package/dist/bytes.js.map +1 -0
- package/dist/frames.d.ts +90 -0
- package/dist/frames.d.ts.map +1 -0
- package/dist/frames.js +234 -0
- package/dist/frames.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/link.d.ts +44 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +120 -0
- package/dist/link.js.map +1 -0
- package/dist/noise.d.ts +181 -0
- package/dist/noise.d.ts.map +1 -0
- package/dist/noise.js +490 -0
- package/dist/noise.js.map +1 -0
- package/dist/relay.d.ts +37 -0
- package/dist/relay.d.ts.map +1 -0
- package/dist/relay.js +70 -0
- package/dist/relay.js.map +1 -0
- package/dist/sas.d.ts +11 -0
- package/dist/sas.d.ts.map +1 -0
- package/dist/sas.js +26 -0
- package/dist/sas.js.map +1 -0
- package/dist/session.d.ts +298 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +608 -0
- package/dist/session.js.map +1 -0
- package/package.json +56 -5
- package/src/bip39-english.ts +30 -0
- package/src/bytes.ts +125 -0
- package/src/frames.ts +278 -0
- package/src/index.ts +14 -0
- package/src/link.ts +160 -0
- package/src/noise.ts +655 -0
- package/src/relay.ts +106 -0
- package/src/sas.ts +28 -0
- package/src/session.ts +848 -0
- package/test-vectors/frames.json +353 -0
- package/test-vectors/noise-kk.json +43 -0
- package/test-vectors/noise-nkpsk0.json +47 -0
- package/test-vectors/sas.json +21 -0
- package/test-vectors/session.json +58 -0
package/src/relay.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The relay's own messages: the text JSON around the opaque binary frames
|
|
3
|
+
* (spec §1). A peer's first message is its hello; the relay answers with
|
|
4
|
+
* status messages.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const RELAY_PROTOCOL_VERSION = 1;
|
|
8
|
+
|
|
9
|
+
export type RelayRole = "agent" | "browser";
|
|
10
|
+
|
|
11
|
+
export interface RelayHello {
|
|
12
|
+
v: 1;
|
|
13
|
+
role: RelayRole;
|
|
14
|
+
/** Agent only: an optional plan credential. */
|
|
15
|
+
token?: string;
|
|
16
|
+
/** Browser only: a remembered browser coming back to reconnect. */
|
|
17
|
+
resume?: true;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type RelayCloseReason =
|
|
21
|
+
| "time-limit"
|
|
22
|
+
| "ttl"
|
|
23
|
+
| "replaced"
|
|
24
|
+
| "limit"
|
|
25
|
+
| "protocol";
|
|
26
|
+
|
|
27
|
+
export type RelayStatus =
|
|
28
|
+
| { t: "waiting" }
|
|
29
|
+
| { t: "ready" }
|
|
30
|
+
| { t: "peer-left" }
|
|
31
|
+
| { t: "closed"; reason: RelayCloseReason };
|
|
32
|
+
|
|
33
|
+
/** WebSocket close code for a second browser in a room. */
|
|
34
|
+
export const CLOSE_CODE_ROOM_FULL = 4009;
|
|
35
|
+
|
|
36
|
+
/** The largest binary message a relay forwards: one Noise message. */
|
|
37
|
+
export const MAX_RELAY_BINARY_LENGTH = 65535 + 16;
|
|
38
|
+
|
|
39
|
+
/** The path of a room on a relay: `/v1/rooms/<room>`. */
|
|
40
|
+
export function roomPath(room: string): string {
|
|
41
|
+
return `/v1/rooms/${room}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Parses a status message from the relay, or returns undefined if it is not one. */
|
|
45
|
+
export function parseRelayStatus(text: string): RelayStatus | undefined {
|
|
46
|
+
let value: unknown;
|
|
47
|
+
try {
|
|
48
|
+
value = JSON.parse(text);
|
|
49
|
+
} catch {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
if (!value || typeof value !== "object") {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
const { t, reason } = value as { t?: unknown; reason?: unknown };
|
|
56
|
+
if (t === "waiting" || t === "ready" || t === "peer-left") {
|
|
57
|
+
return { t };
|
|
58
|
+
}
|
|
59
|
+
if (
|
|
60
|
+
t === "closed" &&
|
|
61
|
+
(reason === "time-limit" ||
|
|
62
|
+
reason === "ttl" ||
|
|
63
|
+
reason === "replaced" ||
|
|
64
|
+
reason === "limit" ||
|
|
65
|
+
reason === "protocol")
|
|
66
|
+
) {
|
|
67
|
+
return { t, reason };
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Parses a peer's hello, or returns undefined if it is not one. */
|
|
73
|
+
export function parseRelayHello(text: string): RelayHello | undefined {
|
|
74
|
+
let value: unknown;
|
|
75
|
+
try {
|
|
76
|
+
value = JSON.parse(text);
|
|
77
|
+
} catch {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
if (!value || typeof value !== "object") {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
const { v, role, token, resume } = value as {
|
|
84
|
+
v?: unknown;
|
|
85
|
+
role?: unknown;
|
|
86
|
+
token?: unknown;
|
|
87
|
+
resume?: unknown;
|
|
88
|
+
};
|
|
89
|
+
if (v !== RELAY_PROTOCOL_VERSION) {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
if (role === "browser" && token === undefined) {
|
|
93
|
+
if (resume === true) {
|
|
94
|
+
return { v, role, resume };
|
|
95
|
+
}
|
|
96
|
+
return resume === undefined ? { v, role } : undefined;
|
|
97
|
+
}
|
|
98
|
+
if (
|
|
99
|
+
role === "agent" &&
|
|
100
|
+
resume === undefined &&
|
|
101
|
+
(token === undefined || typeof token === "string")
|
|
102
|
+
) {
|
|
103
|
+
return token === undefined ? { v, role } : { v, role, token };
|
|
104
|
+
}
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
package/src/sas.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The short authentication string: four words both sides show, so the user
|
|
3
|
+
* can see they are talking to the same peer. They come from
|
|
4
|
+
* `BLAKE2s("acpantenna-sas" ‖ h)`, `h` being the final handshake hash: its
|
|
5
|
+
* first 44 bits, most significant first, as four 11-bit indexes into the
|
|
6
|
+
* BIP-39 English wordlist.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { blake2s } from "@noble/hashes/blake2.js";
|
|
10
|
+
import { BIP39_ENGLISH } from "./bip39-english.ts";
|
|
11
|
+
import { concatBytes, utf8Encode } from "./bytes.ts";
|
|
12
|
+
|
|
13
|
+
export const SAS_LABEL = "acpantenna-sas";
|
|
14
|
+
export const SAS_WORDS = 4;
|
|
15
|
+
|
|
16
|
+
export function sasWords(handshakeHash: Uint8Array): string[] {
|
|
17
|
+
const digest = blake2s(concatBytes(utf8Encode(SAS_LABEL), handshakeHash));
|
|
18
|
+
const words: string[] = [];
|
|
19
|
+
for (let word = 0; word < SAS_WORDS; word++) {
|
|
20
|
+
let index = 0;
|
|
21
|
+
for (let bit = word * 11; bit < word * 11 + 11; bit++) {
|
|
22
|
+
const byte = digest[bit >> 3] ?? 0;
|
|
23
|
+
index = (index << 1) | ((byte >> (7 - (bit & 7))) & 1);
|
|
24
|
+
}
|
|
25
|
+
words.push(BIP39_ENGLISH[index] as string);
|
|
26
|
+
}
|
|
27
|
+
return words;
|
|
28
|
+
}
|