@2kw/ai 7.3.0-dev.1 → 7.3.0-dev.10
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/agent-config/schema.d.ts +45 -1
- package/dist/agent-config/schema.js +26 -2
- package/dist/agent-config/template.js +3 -0
- package/dist/agent-config/validate.js +13 -3
- package/dist/bridge/hub.d.ts +11 -0
- package/dist/bridge/hub.js +44 -0
- package/dist/bridge/inventory.d.ts +27 -0
- package/dist/bridge/inventory.js +41 -0
- package/dist/bridge/pairing.d.ts +41 -0
- package/dist/bridge/pairing.js +158 -0
- package/dist/bridge/paths.d.ts +7 -0
- package/dist/bridge/paths.js +16 -0
- package/dist/bridge/run.d.ts +12 -0
- package/dist/bridge/run.js +134 -0
- package/dist/bridge/shell.d.ts +17 -0
- package/dist/bridge/shell.js +48 -0
- package/dist/bridge/store.d.ts +33 -0
- package/dist/bridge/store.js +110 -0
- package/dist/bridge/toml.d.ts +16 -0
- package/dist/bridge/toml.js +39 -0
- package/dist/commands/bridge.d.ts +7 -0
- package/dist/commands/bridge.js +228 -0
- package/dist/commands/extractions.js +24 -5
- package/dist/index.js +2 -0
- package/dist/lib/agent-run.js +1 -1
- package/dist/relay-protocol/b64.d.ts +13 -0
- package/dist/relay-protocol/b64.js +34 -0
- package/dist/relay-protocol/canonical-json.d.ts +22 -0
- package/dist/relay-protocol/canonical-json.js +101 -0
- package/dist/relay-protocol/frames.d.ts +75 -0
- package/dist/relay-protocol/frames.js +224 -0
- package/dist/relay-protocol/headers.d.ts +18 -0
- package/dist/relay-protocol/headers.js +88 -0
- package/dist/relay-protocol/jws.d.ts +28 -0
- package/dist/relay-protocol/jws.js +113 -0
- package/dist/relay-protocol/keys.d.ts +26 -0
- package/dist/relay-protocol/keys.js +80 -0
- package/dist/relay-protocol/pairing.d.ts +26 -0
- package/dist/relay-protocol/pairing.js +49 -0
- package/dist/relay-protocol/wire.d.ts +101 -0
- package/dist/relay-protocol/wire.js +63 -0
- package/dist/relay-protocol/wordlist.d.ts +30 -0
- package/dist/relay-protocol/wordlist.js +289 -0
- package/package.json +4 -1
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { KeyObject } from "node:crypto";
|
|
2
|
+
export declare class JwsError extends Error {
|
|
3
|
+
readonly code: "malformed" | "bad_alg" | "bad_typ" | "unknown_kid" | "bad_signature" | "bad_claims";
|
|
4
|
+
constructor(code: "malformed" | "bad_alg" | "bad_typ" | "unknown_kid" | "bad_signature" | "bad_claims", message: string);
|
|
5
|
+
}
|
|
6
|
+
export interface JwsHeader {
|
|
7
|
+
alg: "EdDSA";
|
|
8
|
+
typ: string;
|
|
9
|
+
kid: string;
|
|
10
|
+
}
|
|
11
|
+
export interface DecodedJws {
|
|
12
|
+
header: JwsHeader;
|
|
13
|
+
claims: Record<string, unknown>;
|
|
14
|
+
signingInput: string;
|
|
15
|
+
signature: Buffer;
|
|
16
|
+
}
|
|
17
|
+
/** Contract C3. */
|
|
18
|
+
export declare function signJws(typ: string, kid: string, claims: Record<string, unknown>, key: KeyObject): string;
|
|
19
|
+
export declare function decodeJws(token: string): DecodedJws;
|
|
20
|
+
export declare function verifyJws(token: string, expectedTyp: string, keyFor: (kid: string) => KeyObject | undefined): DecodedJws;
|
|
21
|
+
/** Contract C8. */
|
|
22
|
+
export declare function enrolProofInput(token: string, kid: string, publicKey: string): string;
|
|
23
|
+
export declare function signEnrolProof(key: KeyObject, token: string, kid: string, publicKey: string): string;
|
|
24
|
+
/** Contract C5. */
|
|
25
|
+
export declare function mintRelayJwt(key: KeyObject, kid: string, relayId: string, audience: string, nowSec: number): string;
|
|
26
|
+
/** Checks the claims of an already signature-verified relay JWT; returns the relay id. */
|
|
27
|
+
export declare function verifyRelayJwtClaims(claims: Record<string, unknown>, audience: string, nowSec: number): string;
|
|
28
|
+
//# sourceMappingURL=jws.d.ts.map
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { b64url, fromB64url, randomHex } from "./b64.js";
|
|
2
|
+
import { signBytes, verifyBytes } from "./keys.js";
|
|
3
|
+
import { CLOCK_SKEW_SECONDS, MAX_JWT_LIFETIME_SECONDS, RELAY_TYP } from "./wire.js";
|
|
4
|
+
export class JwsError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
constructor(code, message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.name = "JwsError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function isRecord(value) {
|
|
13
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14
|
+
}
|
|
15
|
+
function part(json) {
|
|
16
|
+
return b64url(Buffer.from(JSON.stringify(json), "utf8"));
|
|
17
|
+
}
|
|
18
|
+
/** Contract C3. */
|
|
19
|
+
export function signJws(typ, kid, claims, key) {
|
|
20
|
+
const signingInput = `${part({ alg: "EdDSA", typ, kid })}.${part(claims)}`;
|
|
21
|
+
return `${signingInput}.${b64url(signBytes(key, signingInput))}`;
|
|
22
|
+
}
|
|
23
|
+
export function decodeJws(token) {
|
|
24
|
+
const parts = token.split(".");
|
|
25
|
+
if (parts.length !== 3) {
|
|
26
|
+
throw new JwsError("malformed", "a compact JWS has three parts");
|
|
27
|
+
}
|
|
28
|
+
const [headerPart, payloadPart, signaturePart] = parts;
|
|
29
|
+
let header;
|
|
30
|
+
let claims;
|
|
31
|
+
let signature;
|
|
32
|
+
try {
|
|
33
|
+
header = JSON.parse(fromB64url(headerPart).toString("utf8"));
|
|
34
|
+
claims = JSON.parse(fromB64url(payloadPart).toString("utf8"));
|
|
35
|
+
signature = fromB64url(signaturePart);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
throw new JwsError("malformed", "unparseable header, payload or signature");
|
|
39
|
+
}
|
|
40
|
+
if (!isRecord(header) || !isRecord(claims)) {
|
|
41
|
+
throw new JwsError("malformed", "header and payload must be JSON objects");
|
|
42
|
+
}
|
|
43
|
+
if (header.alg !== "EdDSA") {
|
|
44
|
+
throw new JwsError("bad_alg", "alg must be EdDSA");
|
|
45
|
+
}
|
|
46
|
+
if (typeof header.typ !== "string") {
|
|
47
|
+
throw new JwsError("malformed", "typ must be a string");
|
|
48
|
+
}
|
|
49
|
+
if (typeof header.kid !== "string" || header.kid.length < 1 || header.kid.length > 64) {
|
|
50
|
+
throw new JwsError("malformed", "kid must be a string of 1 to 64 characters");
|
|
51
|
+
}
|
|
52
|
+
if (signature.length !== 64) {
|
|
53
|
+
throw new JwsError("malformed", "an Ed25519 signature has 64 bytes");
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
header: { alg: "EdDSA", typ: header.typ, kid: header.kid },
|
|
57
|
+
claims,
|
|
58
|
+
signingInput: `${headerPart}.${payloadPart}`,
|
|
59
|
+
signature,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export function verifyJws(token, expectedTyp, keyFor) {
|
|
63
|
+
const decoded = decodeJws(token);
|
|
64
|
+
if (decoded.header.typ !== expectedTyp) {
|
|
65
|
+
throw new JwsError("bad_typ", `typ must be ${expectedTyp}`);
|
|
66
|
+
}
|
|
67
|
+
const key = keyFor(decoded.header.kid);
|
|
68
|
+
if (key === undefined) {
|
|
69
|
+
throw new JwsError("unknown_kid", "no key for this kid");
|
|
70
|
+
}
|
|
71
|
+
if (!verifyBytes(key, decoded.signingInput, decoded.signature)) {
|
|
72
|
+
throw new JwsError("bad_signature", "signature does not verify");
|
|
73
|
+
}
|
|
74
|
+
return decoded;
|
|
75
|
+
}
|
|
76
|
+
/** Contract C8. */
|
|
77
|
+
export function enrolProofInput(token, kid, publicKey) {
|
|
78
|
+
return `mcprelay-enrol-v1\n${token}\n${kid}\n${publicKey}`;
|
|
79
|
+
}
|
|
80
|
+
export function signEnrolProof(key, token, kid, publicKey) {
|
|
81
|
+
return b64url(signBytes(key, enrolProofInput(token, kid, publicKey)));
|
|
82
|
+
}
|
|
83
|
+
/** Contract C5. */
|
|
84
|
+
export function mintRelayJwt(key, kid, relayId, audience, nowSec) {
|
|
85
|
+
return signJws(RELAY_TYP, kid, { iss: relayId, aud: audience, iat: nowSec, exp: nowSec + MAX_JWT_LIFETIME_SECONDS, jti: randomHex(16) }, key);
|
|
86
|
+
}
|
|
87
|
+
/** Checks the claims of an already signature-verified relay JWT; returns the relay id. */
|
|
88
|
+
export function verifyRelayJwtClaims(claims, audience, nowSec) {
|
|
89
|
+
const { iss, aud, iat, exp } = claims;
|
|
90
|
+
if (typeof iss !== "string" || iss.length === 0) {
|
|
91
|
+
throw new JwsError("bad_claims", "relay JWT has no issuer");
|
|
92
|
+
}
|
|
93
|
+
if (aud !== audience) {
|
|
94
|
+
throw new JwsError("bad_claims", "relay JWT audience does not match");
|
|
95
|
+
}
|
|
96
|
+
if (typeof iat !== "number" || typeof exp !== "number" || !Number.isInteger(iat) || !Number.isInteger(exp)) {
|
|
97
|
+
throw new JwsError("bad_claims", "relay JWT times must be integers");
|
|
98
|
+
}
|
|
99
|
+
if (exp - iat > MAX_JWT_LIFETIME_SECONDS) {
|
|
100
|
+
throw new JwsError("bad_claims", "relay JWT lifetime exceeds 60 seconds");
|
|
101
|
+
}
|
|
102
|
+
if (exp < iat) {
|
|
103
|
+
throw new JwsError("bad_claims", "relay JWT expires before it was issued");
|
|
104
|
+
}
|
|
105
|
+
if (nowSec > exp + CLOCK_SKEW_SECONDS) {
|
|
106
|
+
throw new JwsError("bad_claims", "relay JWT expired");
|
|
107
|
+
}
|
|
108
|
+
if (iat > nowSec + CLOCK_SKEW_SECONDS) {
|
|
109
|
+
throw new JwsError("bad_claims", "relay JWT issued in the future");
|
|
110
|
+
}
|
|
111
|
+
return iss;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=jws.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type KeyObject } from "node:crypto";
|
|
2
|
+
export interface Jwk {
|
|
3
|
+
kty: "OKP";
|
|
4
|
+
crv: "Ed25519";
|
|
5
|
+
x: string;
|
|
6
|
+
kid: string;
|
|
7
|
+
alg: "EdDSA";
|
|
8
|
+
use: "sig";
|
|
9
|
+
}
|
|
10
|
+
export declare function privateKeyFromSeed(seed: Uint8Array): KeyObject;
|
|
11
|
+
export declare function privateKeyFromPem(pem: string): KeyObject;
|
|
12
|
+
export declare function privateKeyToPem(key: KeyObject): string;
|
|
13
|
+
export declare function generatePrivateKey(): KeyObject;
|
|
14
|
+
export declare function rawPublicKey(key: KeyObject): Buffer;
|
|
15
|
+
export declare function publicKeyFromRaw(raw: Uint8Array): KeyObject;
|
|
16
|
+
export declare function publicKeyFromJwk(jwk: {
|
|
17
|
+
kty?: unknown;
|
|
18
|
+
crv?: unknown;
|
|
19
|
+
x?: unknown;
|
|
20
|
+
}): KeyObject;
|
|
21
|
+
/** Contract C2: first 16 characters of base64url(SHA-256(raw public key)). */
|
|
22
|
+
export declare function kidOf(raw: Uint8Array): string;
|
|
23
|
+
export declare function jwkOf(raw: Uint8Array): Jwk;
|
|
24
|
+
export declare function signBytes(key: KeyObject, data: Uint8Array | string): Buffer;
|
|
25
|
+
export declare function verifyBytes(key: KeyObject, data: Uint8Array | string, signature: Uint8Array): boolean;
|
|
26
|
+
//# sourceMappingURL=keys.d.ts.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// GENERATED COPY of relay/src/protocol/keys.ts (#1377). Do not edit here: edit the relay's file,
|
|
2
|
+
// then run `npm run sync:relay-protocol` in cli/. `npm run check:relay-protocol` fails on any drift.
|
|
3
|
+
import { createPrivateKey, createPublicKey, generateKeyPairSync, sign, verify, } from "node:crypto";
|
|
4
|
+
import { b64url, fromB64url, sha256b64url } from "./b64.js";
|
|
5
|
+
// DER prefixes for Ed25519 keys (RFC 8410): PKCS#8 private key and SPKI public key.
|
|
6
|
+
const PKCS8_ED25519_PREFIX = Buffer.from("302e020100300506032b657004220420", "hex");
|
|
7
|
+
const SPKI_ED25519_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
|
|
8
|
+
export function privateKeyFromSeed(seed) {
|
|
9
|
+
if (seed.length !== 32) {
|
|
10
|
+
throw new Error("Ed25519 seed must be 32 bytes");
|
|
11
|
+
}
|
|
12
|
+
return createPrivateKey({
|
|
13
|
+
key: Buffer.concat([PKCS8_ED25519_PREFIX, Buffer.from(seed)]),
|
|
14
|
+
format: "der",
|
|
15
|
+
type: "pkcs8",
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
export function privateKeyFromPem(pem) {
|
|
19
|
+
const key = createPrivateKey(pem);
|
|
20
|
+
if (key.asymmetricKeyType !== "ed25519") {
|
|
21
|
+
throw new Error("not an Ed25519 private key");
|
|
22
|
+
}
|
|
23
|
+
return key;
|
|
24
|
+
}
|
|
25
|
+
export function privateKeyToPem(key) {
|
|
26
|
+
return key.export({ format: "pem", type: "pkcs8" }).toString();
|
|
27
|
+
}
|
|
28
|
+
export function generatePrivateKey() {
|
|
29
|
+
return generateKeyPairSync("ed25519").privateKey;
|
|
30
|
+
}
|
|
31
|
+
export function rawPublicKey(key) {
|
|
32
|
+
if (key.asymmetricKeyType !== "ed25519") {
|
|
33
|
+
throw new Error("not an Ed25519 key");
|
|
34
|
+
}
|
|
35
|
+
// createPublicKey derives the public key from a private one, but rejects a public KeyObject.
|
|
36
|
+
const publicKey = key.type === "public" ? key : createPublicKey(key);
|
|
37
|
+
const der = publicKey.export({ format: "der", type: "spki" });
|
|
38
|
+
return Buffer.from(der.subarray(der.length - 32));
|
|
39
|
+
}
|
|
40
|
+
export function publicKeyFromRaw(raw) {
|
|
41
|
+
if (raw.length !== 32) {
|
|
42
|
+
throw new Error("Ed25519 public key must be 32 bytes");
|
|
43
|
+
}
|
|
44
|
+
return createPublicKey({
|
|
45
|
+
key: Buffer.concat([SPKI_ED25519_PREFIX, Buffer.from(raw)]),
|
|
46
|
+
format: "der",
|
|
47
|
+
type: "spki",
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
export function publicKeyFromJwk(jwk) {
|
|
51
|
+
if (jwk.kty !== "OKP" || jwk.crv !== "Ed25519" || typeof jwk.x !== "string") {
|
|
52
|
+
throw new Error("not an Ed25519 JWK");
|
|
53
|
+
}
|
|
54
|
+
return publicKeyFromRaw(fromB64url(jwk.x));
|
|
55
|
+
}
|
|
56
|
+
/** Contract C2: first 16 characters of base64url(SHA-256(raw public key)). */
|
|
57
|
+
export function kidOf(raw) {
|
|
58
|
+
return sha256b64url(raw).slice(0, 16);
|
|
59
|
+
}
|
|
60
|
+
export function jwkOf(raw) {
|
|
61
|
+
return { kty: "OKP", crv: "Ed25519", x: b64url(raw), kid: kidOf(raw), alg: "EdDSA", use: "sig" };
|
|
62
|
+
}
|
|
63
|
+
function bytes(data) {
|
|
64
|
+
return typeof data === "string" ? Buffer.from(data, "utf8") : Buffer.from(data);
|
|
65
|
+
}
|
|
66
|
+
export function signBytes(key, data) {
|
|
67
|
+
return sign(null, bytes(data), key);
|
|
68
|
+
}
|
|
69
|
+
export function verifyBytes(key, data, signature) {
|
|
70
|
+
if (signature.length !== 64) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
return verify(null, bytes(data), key, Buffer.from(signature));
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=keys.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local bridge pairing (#1377, plan P3, P4, P6, P10): the redeem proof, the pairing poll token,
|
|
3
|
+
* the six-word fingerprint and the device configHash. The cli copies this file into
|
|
4
|
+
* `cli/src/relay-protocol/`, so it imports `node:crypto` and siblings only and runs on Node 18.
|
|
5
|
+
* test/vectors/pairing.json pins every function here for the backend too.
|
|
6
|
+
*/
|
|
7
|
+
import type { KeyObject } from "node:crypto";
|
|
8
|
+
export declare const PAIR_PROOF_PREFIX = "localbridge-pair-v1";
|
|
9
|
+
export declare const PAIRING_TYP = "localbridge-pairing+jwt";
|
|
10
|
+
/** The C8 shape: what the bridge signs to redeem a pairing code. */
|
|
11
|
+
export declare function pairProofInput(code: string, kid: string, publicKey: string, name: string, os: string): string;
|
|
12
|
+
export declare function signPairProof(key: KeyObject, code: string, kid: string, publicKey: string, name: string, os: string): string;
|
|
13
|
+
/** The pairing poll token: `iss` is the pairing id, `aud` is `<issuer>/v1/local-bridge/pairings`. */
|
|
14
|
+
export declare function mintPairingJwt(key: KeyObject, kid: string, pairingId: string, issuer: string, nowSec: number): string;
|
|
15
|
+
/** Six words: the first 66 bits of SHA-256(raw public key), big-endian, as six 11-bit indices. */
|
|
16
|
+
export declare function fingerprintWords(rawPublicKey: Uint8Array): string[];
|
|
17
|
+
export interface DeviceConfig {
|
|
18
|
+
shell: string | null;
|
|
19
|
+
workspaces: readonly {
|
|
20
|
+
name: string;
|
|
21
|
+
path: string;
|
|
22
|
+
}[];
|
|
23
|
+
}
|
|
24
|
+
/** C10 over what a device exposes: `{shell: <path or null>, workspaces sorted by name}`. */
|
|
25
|
+
export declare function deviceConfigHash(config: DeviceConfig): string;
|
|
26
|
+
//# sourceMappingURL=pairing.d.ts.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { b64url, randomHex, sha256, sha256b64url } from "./b64.js";
|
|
2
|
+
import { canonicalJson } from "./canonical-json.js";
|
|
3
|
+
import { signJws } from "./jws.js";
|
|
4
|
+
import { signBytes } from "./keys.js";
|
|
5
|
+
import { MAX_JWT_LIFETIME_SECONDS } from "./wire.js";
|
|
6
|
+
import { WORDLIST } from "./wordlist.js";
|
|
7
|
+
export const PAIR_PROOF_PREFIX = "localbridge-pair-v1";
|
|
8
|
+
export const PAIRING_TYP = "localbridge-pairing+jwt";
|
|
9
|
+
/** The C8 shape: what the bridge signs to redeem a pairing code. */
|
|
10
|
+
export function pairProofInput(code, kid, publicKey, name, os) {
|
|
11
|
+
return `${PAIR_PROOF_PREFIX}\n${code}\n${kid}\n${publicKey}\n${name}\n${os}`;
|
|
12
|
+
}
|
|
13
|
+
export function signPairProof(key, code, kid, publicKey, name, os) {
|
|
14
|
+
return b64url(signBytes(key, pairProofInput(code, kid, publicKey, name, os)));
|
|
15
|
+
}
|
|
16
|
+
/** The pairing poll token: `iss` is the pairing id, `aud` is `<issuer>/v1/local-bridge/pairings`. */
|
|
17
|
+
export function mintPairingJwt(key, kid, pairingId, issuer, nowSec) {
|
|
18
|
+
return signJws(PAIRING_TYP, kid, {
|
|
19
|
+
iss: pairingId,
|
|
20
|
+
aud: `${issuer}/v1/local-bridge/pairings`,
|
|
21
|
+
iat: nowSec,
|
|
22
|
+
exp: nowSec + MAX_JWT_LIFETIME_SECONDS,
|
|
23
|
+
jti: randomHex(16),
|
|
24
|
+
}, key);
|
|
25
|
+
}
|
|
26
|
+
/** Six words: the first 66 bits of SHA-256(raw public key), big-endian, as six 11-bit indices. */
|
|
27
|
+
export function fingerprintWords(rawPublicKey) {
|
|
28
|
+
if (rawPublicKey.length !== 32) {
|
|
29
|
+
throw new Error("Ed25519 public key must be 32 bytes");
|
|
30
|
+
}
|
|
31
|
+
const digest = sha256(rawPublicKey);
|
|
32
|
+
const words = [];
|
|
33
|
+
for (let i = 0; i < 6; i += 1) {
|
|
34
|
+
let index = 0;
|
|
35
|
+
for (let bit = i * 11; bit < (i + 1) * 11; bit += 1) {
|
|
36
|
+
index = (index << 1) | ((digest[bit >> 3] >> (7 - (bit & 7))) & 1);
|
|
37
|
+
}
|
|
38
|
+
words.push(WORDLIST[index]);
|
|
39
|
+
}
|
|
40
|
+
return words;
|
|
41
|
+
}
|
|
42
|
+
/** C10 over what a device exposes: `{shell: <path or null>, workspaces sorted by name}`. */
|
|
43
|
+
export function deviceConfigHash(config) {
|
|
44
|
+
const workspaces = config.workspaces
|
|
45
|
+
.map(({ name, path }) => ({ name, path }))
|
|
46
|
+
.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
47
|
+
return `sha256:${sha256b64url(canonicalJson({ shell: config.shell, workspaces }))}`;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=pairing.js.map
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/** Wire constants and message shapes shared by relay and hub. Spec §6, plan contract C4–C11. */
|
|
2
|
+
export declare const REQUEST_TYP = "mcprelay-request+jwt";
|
|
3
|
+
export declare const RELAY_TYP = "relay+jwt";
|
|
4
|
+
export declare const PROTOCOL_VERSION = 1;
|
|
5
|
+
export declare const MAX_BODY_BYTES: number;
|
|
6
|
+
export declare const MAX_FRAME_PAYLOAD_BYTES: number;
|
|
7
|
+
/**
|
|
8
|
+
* Frames a single POST to the hub may carry. The hub keeps the signatures of exactly this many
|
|
9
|
+
* recent frames, refuses a frames POST carrying more, and the relay never batches more — so an
|
|
10
|
+
* identical retry of the latest POST is always fully inside that window and is answered
|
|
11
|
+
* idempotently instead of being mistaken for a rewritten frame.
|
|
12
|
+
*/
|
|
13
|
+
export declare const MAX_FRAMES_PER_POST = 64;
|
|
14
|
+
/**
|
|
15
|
+
* Cancel ids one poll may carry, in either direction. The hub puts at most this many tombstones
|
|
16
|
+
* into a poll response, oldest first, and the relay acks at most this many per poll — because an
|
|
17
|
+
* ack rides in the poll's QUERY STRING, and a request line plus headers has to stay inside the
|
|
18
|
+
* 16 KiB Node allows and the 8 KiB nginx and Apache ship with. An id is `rq_` + 32 hex = 35
|
|
19
|
+
* characters plus a comma, so 64 ids are 2 304 bytes: under 3 KiB with the rest of the URL,
|
|
20
|
+
* whatever else a proxy on the hop adds. Anything past the cap simply waits for the next poll.
|
|
21
|
+
*/
|
|
22
|
+
export declare const MAX_CANCELS_PER_POLL = 64;
|
|
23
|
+
export declare const MAX_HEADER_BYTES: number;
|
|
24
|
+
export declare const CLOCK_SKEW_SECONDS = 30;
|
|
25
|
+
export declare const MAX_JWT_LIFETIME_SECONDS = 60;
|
|
26
|
+
export declare const POLL_WAIT_SECONDS = 25;
|
|
27
|
+
export declare const ACCEPTANCE_MS = 10000;
|
|
28
|
+
export declare const OWNER_LEASE_MS = 30000;
|
|
29
|
+
export declare const OFFLINE_MS = 60000;
|
|
30
|
+
export declare const CALL_TOTAL_MS: number;
|
|
31
|
+
export declare const LISTEN_TOTAL_MS: number;
|
|
32
|
+
export declare const MAX_HEAD_DEADLINE_MS: number;
|
|
33
|
+
export declare const CONTROL_RESERVE = 32;
|
|
34
|
+
export type RequestClass = "CALL" | "CONTROL" | "LISTEN";
|
|
35
|
+
export declare const REQUEST_CLASSES: readonly RequestClass[];
|
|
36
|
+
export type HeaderPair = [string, string];
|
|
37
|
+
export interface Envelope {
|
|
38
|
+
id: string;
|
|
39
|
+
server: string;
|
|
40
|
+
method: string;
|
|
41
|
+
url: string;
|
|
42
|
+
headers: HeaderPair[];
|
|
43
|
+
assertion: string;
|
|
44
|
+
/** base64url of the request body; the empty string for no body. */
|
|
45
|
+
body: string;
|
|
46
|
+
}
|
|
47
|
+
export interface PolledRequest {
|
|
48
|
+
requestClass: RequestClass;
|
|
49
|
+
envelope: Envelope;
|
|
50
|
+
}
|
|
51
|
+
export interface PollBody {
|
|
52
|
+
generation: string;
|
|
53
|
+
requests: PolledRequest[];
|
|
54
|
+
cancels: string[];
|
|
55
|
+
jwksRefresh: boolean;
|
|
56
|
+
}
|
|
57
|
+
export interface ForwardRequest {
|
|
58
|
+
relayId: string;
|
|
59
|
+
requestClass: RequestClass;
|
|
60
|
+
/** epoch seconds, equal to the assertion's dl claim */
|
|
61
|
+
headDeadlineAt: number;
|
|
62
|
+
envelope: Envelope;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Spec §6.7. The array is the source and the union is derived from it, so the set is enumerable
|
|
66
|
+
* at run time: the golden vectors publish a row per code and a unit test checks that table
|
|
67
|
+
* against this list, which is what stops the documented codes and the implemented ones drifting.
|
|
68
|
+
*/
|
|
69
|
+
export declare const ERROR_CODES: readonly ["connector_not_in_inventory", "relay_not_active", "relay_offline", "relay_unavailable", "relay_unknown", "not_allowlisted", "assertion_invalid", "config_changed", "stdio_capacity", "upstream_unreachable", "upstream_timeout", "upstream_incomplete", "outcome_unknown", "response_invalid", "body_too_large", "session_lost", "relay_in_use", "request_unknown", "protocol_unsupported"];
|
|
70
|
+
export type ErrorCode = (typeof ERROR_CODES)[number];
|
|
71
|
+
export type ForwardLine = {
|
|
72
|
+
kind: "hub";
|
|
73
|
+
code: ErrorCode;
|
|
74
|
+
message: string;
|
|
75
|
+
} | {
|
|
76
|
+
kind: "frame";
|
|
77
|
+
frame: import("./frames.js").Frame;
|
|
78
|
+
};
|
|
79
|
+
export interface RequestAssertionClaims {
|
|
80
|
+
iss: string;
|
|
81
|
+
aud: string;
|
|
82
|
+
org: string;
|
|
83
|
+
rid: string;
|
|
84
|
+
srv: string;
|
|
85
|
+
cnu: string;
|
|
86
|
+
cfg: string;
|
|
87
|
+
cls: RequestClass;
|
|
88
|
+
htm: string;
|
|
89
|
+
htu: string;
|
|
90
|
+
hdh: string;
|
|
91
|
+
bdh: string;
|
|
92
|
+
dl: number;
|
|
93
|
+
sub: string;
|
|
94
|
+
agt?: string;
|
|
95
|
+
run?: string;
|
|
96
|
+
cid?: string;
|
|
97
|
+
iat: number;
|
|
98
|
+
exp: number;
|
|
99
|
+
jti: string;
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=wire.d.ts.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// GENERATED COPY of relay/src/protocol/wire.ts (#1377). Do not edit here: edit the relay's file,
|
|
2
|
+
// then run `npm run sync:relay-protocol` in cli/. `npm run check:relay-protocol` fails on any drift.
|
|
3
|
+
/** Wire constants and message shapes shared by relay and hub. Spec §6, plan contract C4–C11. */
|
|
4
|
+
export const REQUEST_TYP = "mcprelay-request+jwt";
|
|
5
|
+
export const RELAY_TYP = "relay+jwt";
|
|
6
|
+
export const PROTOCOL_VERSION = 1;
|
|
7
|
+
export const MAX_BODY_BYTES = 8 * 1024 * 1024;
|
|
8
|
+
export const MAX_FRAME_PAYLOAD_BYTES = 1024 * 1024;
|
|
9
|
+
/**
|
|
10
|
+
* Frames a single POST to the hub may carry. The hub keeps the signatures of exactly this many
|
|
11
|
+
* recent frames, refuses a frames POST carrying more, and the relay never batches more — so an
|
|
12
|
+
* identical retry of the latest POST is always fully inside that window and is answered
|
|
13
|
+
* idempotently instead of being mistaken for a rewritten frame.
|
|
14
|
+
*/
|
|
15
|
+
export const MAX_FRAMES_PER_POST = 64;
|
|
16
|
+
/**
|
|
17
|
+
* Cancel ids one poll may carry, in either direction. The hub puts at most this many tombstones
|
|
18
|
+
* into a poll response, oldest first, and the relay acks at most this many per poll — because an
|
|
19
|
+
* ack rides in the poll's QUERY STRING, and a request line plus headers has to stay inside the
|
|
20
|
+
* 16 KiB Node allows and the 8 KiB nginx and Apache ship with. An id is `rq_` + 32 hex = 35
|
|
21
|
+
* characters plus a comma, so 64 ids are 2 304 bytes: under 3 KiB with the rest of the URL,
|
|
22
|
+
* whatever else a proxy on the hop adds. Anything past the cap simply waits for the next poll.
|
|
23
|
+
*/
|
|
24
|
+
export const MAX_CANCELS_PER_POLL = 64;
|
|
25
|
+
export const MAX_HEADER_BYTES = 64 * 1024;
|
|
26
|
+
export const CLOCK_SKEW_SECONDS = 30;
|
|
27
|
+
export const MAX_JWT_LIFETIME_SECONDS = 60;
|
|
28
|
+
export const POLL_WAIT_SECONDS = 25;
|
|
29
|
+
export const ACCEPTANCE_MS = 10_000;
|
|
30
|
+
export const OWNER_LEASE_MS = 30_000;
|
|
31
|
+
export const OFFLINE_MS = 60_000;
|
|
32
|
+
export const CALL_TOTAL_MS = 10 * 60_000;
|
|
33
|
+
export const LISTEN_TOTAL_MS = 60 * 60_000;
|
|
34
|
+
export const MAX_HEAD_DEADLINE_MS = 10 * 60_000;
|
|
35
|
+
export const CONTROL_RESERVE = 32;
|
|
36
|
+
export const REQUEST_CLASSES = ["CALL", "CONTROL", "LISTEN"];
|
|
37
|
+
/**
|
|
38
|
+
* Spec §6.7. The array is the source and the union is derived from it, so the set is enumerable
|
|
39
|
+
* at run time: the golden vectors publish a row per code and a unit test checks that table
|
|
40
|
+
* against this list, which is what stops the documented codes and the implemented ones drifting.
|
|
41
|
+
*/
|
|
42
|
+
export const ERROR_CODES = [
|
|
43
|
+
"connector_not_in_inventory",
|
|
44
|
+
"relay_not_active",
|
|
45
|
+
"relay_offline",
|
|
46
|
+
"relay_unavailable",
|
|
47
|
+
"relay_unknown",
|
|
48
|
+
"not_allowlisted",
|
|
49
|
+
"assertion_invalid",
|
|
50
|
+
"config_changed",
|
|
51
|
+
"stdio_capacity",
|
|
52
|
+
"upstream_unreachable",
|
|
53
|
+
"upstream_timeout",
|
|
54
|
+
"upstream_incomplete",
|
|
55
|
+
"outcome_unknown",
|
|
56
|
+
"response_invalid",
|
|
57
|
+
"body_too_large",
|
|
58
|
+
"session_lost",
|
|
59
|
+
"relay_in_use",
|
|
60
|
+
"request_unknown",
|
|
61
|
+
"protocol_unsupported",
|
|
62
|
+
];
|
|
63
|
+
//# sourceMappingURL=wire.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The BIP-39 English word list, 2048 words: the alphabet of the six-word pairing fingerprint
|
|
3
|
+
* (plan P6). Copied unchanged from
|
|
4
|
+
* https://github.com/bitcoin/bips/blob/master/bip-0039/english.txt, whose SHA-256 (LF line
|
|
5
|
+
* endings, trailing newline) is 2f5eed53a4727b4bf8880d8f3f199efc90e58503646d9ff8eff3a2ed3b24dbda;
|
|
6
|
+
* test/unit/pairing.test.ts checks this copy against it. The backend reads the same file as
|
|
7
|
+
* backend/src/main/resources/localbridge/wordlist-en.txt.
|
|
8
|
+
*
|
|
9
|
+
* BIP 39 is published under the MIT License ("License: MIT" in its header,
|
|
10
|
+
* https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki).
|
|
11
|
+
*
|
|
12
|
+
* Copyright (c) 2013 Marek Palatinus, Pavol Rusnak, Aaron Voisine, Sean Bowe (the BIP 39 authors)
|
|
13
|
+
*
|
|
14
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
|
15
|
+
* and associated documentation files (the "Software"), to deal in the Software without
|
|
16
|
+
* restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
|
17
|
+
* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
|
|
18
|
+
* Software is furnished to do so, subject to the following conditions:
|
|
19
|
+
*
|
|
20
|
+
* The above copyright notice and this permission notice shall be included in all copies or
|
|
21
|
+
* substantial portions of the Software.
|
|
22
|
+
*
|
|
23
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
|
24
|
+
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
25
|
+
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
26
|
+
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
28
|
+
*/
|
|
29
|
+
export declare const WORDLIST: readonly string[];
|
|
30
|
+
//# sourceMappingURL=wordlist.d.ts.map
|