@sideband/secure-relay 0.2.2 → 0.3.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.
Files changed (46) hide show
  1. package/README.md +6 -4
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/constants.d.ts +49 -0
  4. package/dist/constants.d.ts.map +1 -0
  5. package/dist/constants.js +51 -0
  6. package/dist/constants.js.map +1 -0
  7. package/dist/crypto.d.ts +70 -0
  8. package/dist/crypto.d.ts.map +1 -0
  9. package/dist/crypto.js +144 -0
  10. package/dist/crypto.js.map +1 -0
  11. package/dist/frame.d.ts +219 -0
  12. package/dist/frame.d.ts.map +1 -0
  13. package/dist/frame.js +554 -0
  14. package/dist/frame.js.map +1 -0
  15. package/dist/handshake.d.ts +39 -0
  16. package/dist/handshake.d.ts.map +1 -0
  17. package/dist/handshake.js +93 -0
  18. package/dist/handshake.js.map +1 -0
  19. package/dist/index.d.ts +46 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +12 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/replay.d.ts +32 -0
  24. package/dist/replay.d.ts.map +1 -0
  25. package/dist/replay.js +88 -0
  26. package/dist/replay.js.map +1 -0
  27. package/dist/session.d.ts +67 -0
  28. package/dist/session.d.ts.map +1 -0
  29. package/dist/session.js +122 -0
  30. package/dist/session.js.map +1 -0
  31. package/dist/types.d.ts +120 -0
  32. package/dist/types.d.ts.map +1 -0
  33. package/dist/types.js +81 -0
  34. package/dist/types.js.map +1 -0
  35. package/package.json +1 -1
  36. package/src/constants.ts +3 -3
  37. package/src/crypto.test.ts +5 -5
  38. package/src/frame.test.ts +113 -47
  39. package/src/frame.ts +119 -86
  40. package/src/handshake.test.ts +29 -41
  41. package/src/handshake.ts +25 -27
  42. package/src/index.ts +4 -10
  43. package/src/integration.test.ts +97 -138
  44. package/src/session.test.ts +12 -10
  45. package/src/types.ts +4 -14
  46. /package/{dist/LICENSE → LICENSE} +0 -0
@@ -0,0 +1,93 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * E2EE handshake protocol for Sideband Relay Protocol (SBRP).
4
+ *
5
+ * Implements the authenticated key exchange between client and daemon,
6
+ * with Ed25519 signatures for MITM protection.
7
+ */
8
+ import { computeSharedSecret, createSignaturePayload, createTranscriptHash, deriveSessionKeys, generateEphemeralKeyPair, signPayload, verifySignature, zeroize, } from "./crypto.js";
9
+ import { SbrpError, SbrpErrorCode } from "./types.js";
10
+ /**
11
+ * Create a handshake init message (client side).
12
+ *
13
+ * Generates an ephemeral X25519 keypair for this session.
14
+ */
15
+ export function createHandshakeInit() {
16
+ const ephemeralKeyPair = generateEphemeralKeyPair();
17
+ return {
18
+ message: {
19
+ type: "handshake.init",
20
+ initPublicKey: ephemeralKeyPair.publicKey,
21
+ },
22
+ ephemeralKeyPair,
23
+ };
24
+ }
25
+ /**
26
+ * Process handshake init and create accept message (daemon side).
27
+ *
28
+ * 1. Generate ephemeral X25519 keypair
29
+ * 2. Sign ephemeral public key with identity key (context-bound)
30
+ * 3. Derive session keys
31
+ *
32
+ * NOTE: Callers MUST enforce a 30-second handshake timeout per SBRP §1.4.
33
+ * This function does not track time; timeout enforcement is a transport concern.
34
+ */
35
+ export function processHandshakeInit(init, daemonId, identityKeyPair) {
36
+ const ephemeralKeyPair = generateEphemeralKeyPair();
37
+ // Sign ephemeral key with context binding
38
+ const signaturePayload = createSignaturePayload(daemonId, init.initPublicKey, ephemeralKeyPair.publicKey);
39
+ const signature = signPayload(signaturePayload, identityKeyPair.privateKey);
40
+ // Derive session keys
41
+ const sharedSecret = computeSharedSecret(ephemeralKeyPair.privateKey, init.initPublicKey);
42
+ const transcriptHash = createTranscriptHash(daemonId, init.initPublicKey, ephemeralKeyPair.publicKey, signature);
43
+ const sessionKeys = deriveSessionKeys(sharedSecret, transcriptHash);
44
+ // Best-effort zeroize secrets
45
+ zeroize(sharedSecret);
46
+ zeroize(ephemeralKeyPair.privateKey);
47
+ return {
48
+ message: {
49
+ type: "handshake.accept",
50
+ identityPublicKey: identityKeyPair.publicKey,
51
+ acceptPublicKey: ephemeralKeyPair.publicKey,
52
+ signature,
53
+ },
54
+ sessionKeys,
55
+ };
56
+ }
57
+ /**
58
+ * Process handshake accept message (client side).
59
+ *
60
+ * 1. Verify signature using PINNED identity key (TOFU)
61
+ * 2. Derive session keys using same transcript hash as daemon
62
+ *
63
+ * NOTE: Callers MUST enforce a 30-second handshake timeout per SBRP §1.4.
64
+ * This function does not track time; timeout enforcement is a transport concern.
65
+ *
66
+ * @param ephemeralKeyPair The privateKey is zeroized in-place after key derivation.
67
+ * @throws {SbrpError} IdentityKeyChanged if advertised key doesn't match pinned key
68
+ * @throws {SbrpError} HandshakeFailed if signature verification fails
69
+ */
70
+ export function processHandshakeAccept(accept, daemonId, pinnedIdentityPublicKey, ephemeralKeyPair) {
71
+ // Reject if advertised identity key doesn't match pinned key.
72
+ // Signature is verified against pinnedIdentityPublicKey, but an attacker
73
+ // could swap the advertised field to mislead higher layers.
74
+ if (accept.identityPublicKey.length !== pinnedIdentityPublicKey.length ||
75
+ !accept.identityPublicKey.every((b, i) => b === pinnedIdentityPublicKey[i])) {
76
+ throw new SbrpError(SbrpErrorCode.IdentityKeyChanged, "Advertised identity key does not match pinned key");
77
+ }
78
+ // Verify daemon signature using PINNED key (not relay-provided!)
79
+ const signaturePayload = createSignaturePayload(daemonId, ephemeralKeyPair.publicKey, accept.acceptPublicKey);
80
+ const valid = verifySignature(signaturePayload, accept.signature, pinnedIdentityPublicKey);
81
+ if (!valid) {
82
+ throw new SbrpError(SbrpErrorCode.HandshakeFailed, "Signature verification failed");
83
+ }
84
+ // Derive session keys using same transcript hash as daemon
85
+ const sharedSecret = computeSharedSecret(ephemeralKeyPair.privateKey, accept.acceptPublicKey);
86
+ const transcriptHash = createTranscriptHash(daemonId, ephemeralKeyPair.publicKey, accept.acceptPublicKey, accept.signature);
87
+ const sessionKeys = deriveSessionKeys(sharedSecret, transcriptHash);
88
+ // Best-effort zeroize secrets
89
+ zeroize(sharedSecret);
90
+ zeroize(ephemeralKeyPair.privateKey);
91
+ return sessionKeys;
92
+ }
93
+ //# sourceMappingURL=handshake.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handshake.js","sourceRoot":"","sources":["../src/handshake.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @sideband/secure-relay
3
+ *
4
+ * Sideband Relay Protocol (SBRP) implementation for E2EE communication
5
+ * between daemons and clients via a relay server.
6
+ *
7
+ * Features:
8
+ * - Ed25519 identity signatures for MITM protection
9
+ * - X25519 ephemeral key exchange for forward secrecy
10
+ * - ChaCha20-Poly1305 authenticated encryption
11
+ * - TOFU (Trust On First Use) identity pinning
12
+ * - Bitmap-based replay protection
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * // Daemon side: generate identity keypair on first run
17
+ * const identity = generateIdentityKeyPair();
18
+ *
19
+ * // Client side: create handshake init
20
+ * const { message: init, ephemeralKeyPair } = createHandshakeInit();
21
+ *
22
+ * // Daemon side: process init and create accept
23
+ * const { message: accept, sessionKeys } = processHandshakeInit(init, daemonId, identity);
24
+ * const clientSession = createClientSession(clientId, sessionKeys);
25
+ *
26
+ * // Client side: process accept (with TOFU-pinned identity)
27
+ * const clientKeys = processHandshakeAccept(accept, daemonId, pinnedKey, ephemeralKeyPair);
28
+ * const daemonSession = createDaemonSession(clientKeys);
29
+ *
30
+ * // Encrypt/decrypt messages
31
+ * const encrypted = encryptClientToDaemon(daemonSession, plaintext);
32
+ * const decrypted = decryptClientToDaemon(clientSession, encrypted);
33
+ * ```
34
+ */
35
+ export type { ClientId, DaemonId, EncryptedMessage, EphemeralKeyPair, HandshakeAccept, HandshakeInit, IdentityKeyPair, SessionId, SessionKeys, } from "./types.js";
36
+ export { asClientId, asDaemonId, Direction, SbrpError, SbrpErrorCode, SignalCode, SignalReason, } from "./types.js";
37
+ export { AUTH_TAG_LENGTH, DEFAULT_REPLAY_WINDOW_SIZE, DIRECTION_CLIENT_TO_DAEMON, DIRECTION_DAEMON_TO_CLIENT, ED25519_PRIVATE_KEY_LENGTH, ED25519_PUBLIC_KEY_LENGTH, ED25519_SIGNATURE_LENGTH, FRAME_HEADER_SIZE, HANDSHAKE_ACCEPT_PAYLOAD_SIZE, HANDSHAKE_INIT_PAYLOAD_SIZE, MAX_PAYLOAD_SIZE, MAX_PING_PAYLOAD_SIZE, MIN_CONTROL_PAYLOAD_SIZE, MIN_ENCRYPTED_PAYLOAD_SIZE, NONCE_LENGTH, SBRP_HANDSHAKE_CONTEXT, SBRP_SESSION_KEYS_INFO, SBRP_TRANSCRIPT_CONTEXT, SESSION_KEYS_LENGTH, SIGNAL_PAYLOAD_SIZE, SYMMETRIC_KEY_LENGTH, X25519_PRIVATE_KEY_LENGTH, X25519_PUBLIC_KEY_LENGTH, } from "./constants.js";
38
+ export { computeFingerprint, computeSharedSecret, constructNonce, createSignaturePayload, createTranscriptHash, decrypt, deriveSessionKeys, encrypt, extractSequence, generateEphemeralKeyPair, generateIdentityKeyPair, randomBytes, signPayload, verifySignature, zeroize, } from "./crypto.js";
39
+ export { createHandshakeInit, processHandshakeAccept, processHandshakeInit, } from "./handshake.js";
40
+ export type { ReplayWindow } from "./replay.js";
41
+ export { checkAndUpdateReplay, createReplayWindow, isValidSequence, resetReplayWindow, } from "./replay.js";
42
+ export type { ClientSession, DaemonSession } from "./session.js";
43
+ export { clearClientSession, clearDaemonSession, createClientSession, createDaemonSession, decryptClientToDaemon, decryptDaemonToClient, encryptClientToDaemon, encryptDaemonToClient, } from "./session.js";
44
+ export type { ControlPayload, Frame, FrameHeader, SignalPayload, } from "./frame.js";
45
+ export { decodeControl, decodeData, decodeFrame, decodeHandshakeAccept, decodeHandshakeInit, decodeSignal, encodeControl, encodeData, encodeFrame, encodeHandshakeAccept, encodeHandshakeInit, encodePing, encodePong, encodeSignal, FrameDecoder, FrameType, fromWireControlCode, isTerminalCode, readFrameHeader, toWireControlCode, WireControlCode, } from "./frame.js";
46
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAGH,YAAY,EACV,QAAQ,EACR,QAAQ,EACR,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,eAAe,EACf,SAAS,EACT,WAAW,GACZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,UAAU,EACV,UAAU,EACV,SAAS,EACT,SAAS,EACT,aAAa,EACb,UAAU,EACV,YAAY,GACb,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,eAAe,EACf,0BAA0B,EAC1B,0BAA0B,EAC1B,0BAA0B,EAC1B,0BAA0B,EAC1B,yBAAyB,EACzB,wBAAwB,EACxB,iBAAiB,EACjB,6BAA6B,EAC7B,2BAA2B,EAC3B,gBAAgB,EAChB,qBAAqB,EACrB,wBAAwB,EACxB,0BAA0B,EAC1B,YAAY,EACZ,sBAAsB,EACtB,sBAAsB,EACtB,uBAAuB,EACvB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,yBAAyB,EACzB,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,EACd,sBAAsB,EACtB,oBAAoB,EACpB,OAAO,EACP,iBAAiB,EACjB,OAAO,EACP,eAAe,EACf,wBAAwB,EACxB,uBAAuB,EACvB,WAAW,EACX,WAAW,EACX,eAAe,EACf,OAAO,GACR,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,gBAAgB,CAAC;AAGxB,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EACL,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,EACf,iBAAiB,GAClB,MAAM,aAAa,CAAC;AAGrB,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAEjE,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,cAAc,CAAC;AAGtB,YAAY,EACV,cAAc,EACd,KAAK,EACL,WAAW,EACX,aAAa,GACd,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,aAAa,EACb,UAAU,EACV,WAAW,EACX,qBAAqB,EACrB,mBAAmB,EACnB,YAAY,EACZ,aAAa,EACb,UAAU,EACV,WAAW,EACX,qBAAqB,EACrB,mBAAmB,EACnB,UAAU,EACV,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,mBAAmB,EACnB,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,eAAe,GAChB,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ export { asClientId, asDaemonId, Direction, SbrpError, SbrpErrorCode, SignalCode, SignalReason, } from "./types.js";
3
+ // Constants
4
+ export { AUTH_TAG_LENGTH, DEFAULT_REPLAY_WINDOW_SIZE, DIRECTION_CLIENT_TO_DAEMON, DIRECTION_DAEMON_TO_CLIENT, ED25519_PRIVATE_KEY_LENGTH, ED25519_PUBLIC_KEY_LENGTH, ED25519_SIGNATURE_LENGTH, FRAME_HEADER_SIZE, HANDSHAKE_ACCEPT_PAYLOAD_SIZE, HANDSHAKE_INIT_PAYLOAD_SIZE, MAX_PAYLOAD_SIZE, MAX_PING_PAYLOAD_SIZE, MIN_CONTROL_PAYLOAD_SIZE, MIN_ENCRYPTED_PAYLOAD_SIZE, NONCE_LENGTH, SBRP_HANDSHAKE_CONTEXT, SBRP_SESSION_KEYS_INFO, SBRP_TRANSCRIPT_CONTEXT, SESSION_KEYS_LENGTH, SIGNAL_PAYLOAD_SIZE, SYMMETRIC_KEY_LENGTH, X25519_PRIVATE_KEY_LENGTH, X25519_PUBLIC_KEY_LENGTH, } from "./constants.js";
5
+ // Crypto primitives
6
+ export { computeFingerprint, computeSharedSecret, constructNonce, createSignaturePayload, createTranscriptHash, decrypt, deriveSessionKeys, encrypt, extractSequence, generateEphemeralKeyPair, generateIdentityKeyPair, randomBytes, signPayload, verifySignature, zeroize, } from "./crypto.js";
7
+ // Handshake
8
+ export { createHandshakeInit, processHandshakeAccept, processHandshakeInit, } from "./handshake.js";
9
+ export { checkAndUpdateReplay, createReplayWindow, isValidSequence, resetReplayWindow, } from "./replay.js";
10
+ export { clearClientSession, clearDaemonSession, createClientSession, createDaemonSession, decryptClientToDaemon, decryptDaemonToClient, encryptClientToDaemon, encryptDaemonToClient, } from "./session.js";
11
+ export { decodeControl, decodeData, decodeFrame, decodeHandshakeAccept, decodeHandshakeInit, decodeSignal, encodeControl, encodeData, encodeFrame, encodeHandshakeAccept, encodeHandshakeInit, encodePing, encodePong, encodeSignal, FrameDecoder, FrameType, fromWireControlCode, isTerminalCode, readFrameHeader, toWireControlCode, WireControlCode, } from "./frame.js";
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAkDtC,OAAO,EACL,UAAU,EACV,UAAU,EACV,SAAS,EACT,SAAS,EACT,aAAa,EACb,UAAU,EACV,YAAY,GACb,MAAM,YAAY,CAAC;AAEpB,YAAY;AACZ,OAAO,EACL,eAAe,EACf,0BAA0B,EAC1B,0BAA0B,EAC1B,0BAA0B,EAC1B,0BAA0B,EAC1B,yBAAyB,EACzB,wBAAwB,EACxB,iBAAiB,EACjB,6BAA6B,EAC7B,2BAA2B,EAC3B,gBAAgB,EAChB,qBAAqB,EACrB,wBAAwB,EACxB,0BAA0B,EAC1B,YAAY,EACZ,sBAAsB,EACtB,sBAAsB,EACtB,uBAAuB,EACvB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,yBAAyB,EACzB,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AAExB,oBAAoB;AACpB,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,EACd,sBAAsB,EACtB,oBAAoB,EACpB,OAAO,EACP,iBAAiB,EACjB,OAAO,EACP,eAAe,EACf,wBAAwB,EACxB,uBAAuB,EACvB,WAAW,EACX,WAAW,EACX,eAAe,EACf,OAAO,GACR,MAAM,aAAa,CAAC;AAErB,YAAY;AACZ,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,gBAAgB,CAAC;AAKxB,OAAO,EACL,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,EACf,iBAAiB,GAClB,MAAM,aAAa,CAAC;AAKrB,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,cAAc,CAAC;AAUtB,OAAO,EACL,aAAa,EACb,UAAU,EACV,WAAW,EACX,qBAAqB,EACrB,mBAAmB,EACnB,YAAY,EACZ,aAAa,EACb,UAAU,EACV,WAAW,EACX,qBAAqB,EACrB,mBAAmB,EACnB,UAAU,EACV,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,mBAAmB,EACnB,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,eAAe,GAChB,MAAM,YAAY,CAAC"}
@@ -0,0 +1,32 @@
1
+ /** Replay window state */
2
+ export interface ReplayWindow {
3
+ /** Highest accepted sequence number */
4
+ maxSeen: bigint;
5
+ /** Bitmap: bit i set = sequence (maxSeen - i) was seen */
6
+ bitmap: bigint;
7
+ /** Window size in bits */
8
+ windowSize: bigint;
9
+ }
10
+ /**
11
+ * Create a new replay window.
12
+ *
13
+ * @param windowSize - Window size in bits (default: 64)
14
+ */
15
+ export declare function createReplayWindow(windowSize?: bigint): ReplayWindow;
16
+ /**
17
+ * Check if a sequence number is valid (not a replay) and update the window.
18
+ *
19
+ * @returns true if the sequence is valid and accepted, false if it's a replay
20
+ */
21
+ export declare function checkAndUpdateReplay(seq: bigint, window: ReplayWindow): boolean;
22
+ /**
23
+ * Check if a sequence number would be valid without updating the window.
24
+ *
25
+ * Useful for pre-validation before decryption.
26
+ */
27
+ export declare function isValidSequence(seq: bigint, window: ReplayWindow): boolean;
28
+ /**
29
+ * Reset the replay window to initial state.
30
+ */
31
+ export declare function resetReplayWindow(window: ReplayWindow): void;
32
+ //# sourceMappingURL=replay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay.d.ts","sourceRoot":"","sources":["../src/replay.ts"],"names":[],"mappings":"AAYA,0BAA0B;AAC1B,MAAM,WAAW,YAAY;IAC3B,uCAAuC;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAC;IACf,0BAA0B;IAC1B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,GAAE,MAAmC,GAC9C,YAAY,CAMd;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,YAAY,GACnB,OAAO,CAqCT;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAgB1E;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAG5D"}
package/dist/replay.js ADDED
@@ -0,0 +1,88 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * Bitmap-based replay protection for Sideband Relay Protocol (SBRP).
4
+ *
5
+ * Uses a sliding window to track seen sequence numbers and prevent
6
+ * replay attacks while avoiding memory exhaustion from attacker-controlled
7
+ * sequence numbers.
8
+ */
9
+ import { DEFAULT_REPLAY_WINDOW_SIZE } from "./constants.js";
10
+ /**
11
+ * Create a new replay window.
12
+ *
13
+ * @param windowSize - Window size in bits (default: 64)
14
+ */
15
+ export function createReplayWindow(windowSize = DEFAULT_REPLAY_WINDOW_SIZE) {
16
+ return {
17
+ maxSeen: -1n, // No messages seen yet
18
+ bitmap: 0n,
19
+ windowSize,
20
+ };
21
+ }
22
+ /**
23
+ * Check if a sequence number is valid (not a replay) and update the window.
24
+ *
25
+ * @returns true if the sequence is valid and accepted, false if it's a replay
26
+ */
27
+ export function checkAndUpdateReplay(seq, window) {
28
+ // First message ever
29
+ if (window.maxSeen === -1n) {
30
+ window.maxSeen = seq;
31
+ window.bitmap = 1n;
32
+ return true;
33
+ }
34
+ if (seq > window.maxSeen) {
35
+ // New high sequence - shift window
36
+ const shift = seq - window.maxSeen;
37
+ if (shift >= window.windowSize) {
38
+ // Sequence is far ahead, reset bitmap
39
+ window.bitmap = 1n;
40
+ }
41
+ else {
42
+ window.bitmap = (window.bitmap << shift) | 1n;
43
+ }
44
+ window.maxSeen = seq;
45
+ return true;
46
+ }
47
+ // Sequence is within or before the window
48
+ const diff = window.maxSeen - seq;
49
+ if (diff >= window.windowSize) {
50
+ // Too old, outside window
51
+ return false;
52
+ }
53
+ const mask = 1n << diff;
54
+ if (window.bitmap & mask) {
55
+ // Already seen (replay)
56
+ return false;
57
+ }
58
+ // Mark as seen
59
+ window.bitmap |= mask;
60
+ return true;
61
+ }
62
+ /**
63
+ * Check if a sequence number would be valid without updating the window.
64
+ *
65
+ * Useful for pre-validation before decryption.
66
+ */
67
+ export function isValidSequence(seq, window) {
68
+ if (window.maxSeen === -1n) {
69
+ return true;
70
+ }
71
+ if (seq > window.maxSeen) {
72
+ return true;
73
+ }
74
+ const diff = window.maxSeen - seq;
75
+ if (diff >= window.windowSize) {
76
+ return false;
77
+ }
78
+ const mask = 1n << diff;
79
+ return (window.bitmap & mask) === 0n;
80
+ }
81
+ /**
82
+ * Reset the replay window to initial state.
83
+ */
84
+ export function resetReplayWindow(window) {
85
+ window.maxSeen = -1n;
86
+ window.bitmap = 0n;
87
+ }
88
+ //# sourceMappingURL=replay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay.js","sourceRoot":"","sources":["../src/replay.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;;;GAMG;AAEH,OAAO,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AAY5D;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAChC,aAAqB,0BAA0B;IAE/C,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,EAAE,uBAAuB;QACrC,MAAM,EAAE,EAAE;QACV,UAAU;KACX,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAClC,GAAW,EACX,MAAoB;IAEpB,qBAAqB;IACrB,IAAI,MAAM,CAAC,OAAO,KAAK,CAAC,EAAE,EAAE,CAAC;QAC3B,MAAM,CAAC,OAAO,GAAG,GAAG,CAAC;QACrB,MAAM,CAAC,MAAM,GAAG,EAAE,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,GAAG,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;QACzB,mCAAmC;QACnC,MAAM,KAAK,GAAG,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC;QACnC,IAAI,KAAK,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;YAC/B,sCAAsC;YACtC,MAAM,CAAC,MAAM,GAAG,EAAE,CAAC;QACrB,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,MAAM,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,KAAK,CAAC,GAAG,EAAE,CAAC;QAChD,CAAC;QACD,MAAM,CAAC,OAAO,GAAG,GAAG,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,0CAA0C;IAC1C,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,GAAG,GAAG,CAAC;IAClC,IAAI,IAAI,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QAC9B,0BAA0B;QAC1B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,IAAI,GAAG,EAAE,IAAI,IAAI,CAAC;IACxB,IAAI,MAAM,CAAC,MAAM,GAAG,IAAI,EAAE,CAAC;QACzB,wBAAwB;QACxB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,eAAe;IACf,MAAM,CAAC,MAAM,IAAI,IAAI,CAAC;IACtB,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW,EAAE,MAAoB;IAC/D,IAAI,MAAM,CAAC,OAAO,KAAK,CAAC,EAAE,EAAE,CAAC;QAC3B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,GAAG,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;QACzB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,GAAG,GAAG,CAAC;IAClC,IAAI,IAAI,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,IAAI,GAAG,EAAE,IAAI,IAAI,CAAC;IACxB,OAAO,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;AACvC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAoB;IACpD,MAAM,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC;IACrB,MAAM,CAAC,MAAM,GAAG,EAAE,CAAC;AACrB,CAAC"}
@@ -0,0 +1,67 @@
1
+ import { type ReplayWindow } from "./replay.js";
2
+ import type { ClientId, EncryptedMessage, SessionKeys } from "./types.js";
3
+ /** Crypto state for one direction of communication (traffic key, counters, replay) */
4
+ interface ChannelState {
5
+ trafficKey: Uint8Array;
6
+ sendSeq: bigint;
7
+ recvWindow: ReplayWindow;
8
+ }
9
+ /** Client session (daemon-side state for each connected client) */
10
+ export interface ClientSession {
11
+ clientId: ClientId;
12
+ clientToDaemon: ChannelState;
13
+ daemonToClient: ChannelState;
14
+ }
15
+ /** Daemon session (client-side state for communicating with daemon) */
16
+ export interface DaemonSession {
17
+ clientToDaemon: ChannelState;
18
+ daemonToClient: ChannelState;
19
+ }
20
+ /**
21
+ * Create a client session (daemon side).
22
+ *
23
+ * Used by daemon to manage state for each connected client.
24
+ */
25
+ export declare function createClientSession(clientId: ClientId, sessionKeys: SessionKeys): ClientSession;
26
+ /**
27
+ * Create a daemon session (client side).
28
+ *
29
+ * Used by client to communicate with daemon.
30
+ */
31
+ export declare function createDaemonSession(sessionKeys: SessionKeys): DaemonSession;
32
+ /**
33
+ * Encrypt a message from client to daemon.
34
+ */
35
+ export declare function encryptClientToDaemon(session: DaemonSession, plaintext: Uint8Array): EncryptedMessage;
36
+ /**
37
+ * Encrypt a message from daemon to client.
38
+ */
39
+ export declare function encryptDaemonToClient(session: ClientSession, plaintext: Uint8Array): EncryptedMessage;
40
+ /**
41
+ * Decrypt a message received by daemon from client.
42
+ *
43
+ * @throws {SbrpError} with code SequenceError if replay detected
44
+ * @throws {SbrpError} with code DecryptFailed if decryption fails
45
+ */
46
+ export declare function decryptClientToDaemon(session: ClientSession, message: EncryptedMessage): Uint8Array;
47
+ /**
48
+ * Decrypt a message received by client from daemon.
49
+ *
50
+ * @throws {SbrpError} with code SequenceError if replay detected
51
+ * @throws {SbrpError} with code DecryptFailed if decryption fails
52
+ */
53
+ export declare function decryptDaemonToClient(session: DaemonSession, message: EncryptedMessage): Uint8Array;
54
+ /**
55
+ * Clear all key material from a client session.
56
+ *
57
+ * Best-effort zeroization (JS/GC limitations apply).
58
+ */
59
+ export declare function clearClientSession(session: ClientSession): void;
60
+ /**
61
+ * Clear all key material from a daemon session.
62
+ *
63
+ * Best-effort zeroization (JS/GC limitations apply).
64
+ */
65
+ export declare function clearDaemonSession(session: DaemonSession): void;
66
+ export {};
67
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAUA,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAG1E,sFAAsF;AACtF,UAAU,YAAY;IACpB,UAAU,EAAE,UAAU,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,YAAY,CAAC;CAC1B;AAED,mEAAmE;AACnE,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,QAAQ,CAAC;IACnB,cAAc,EAAE,YAAY,CAAC;IAC7B,cAAc,EAAE,YAAY,CAAC;CAC9B;AAED,uEAAuE;AACvE,MAAM,WAAW,aAAa;IAC5B,cAAc,EAAE,YAAY,CAAC;IAC7B,cAAc,EAAE,YAAY,CAAC;CAC9B;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,EAClB,WAAW,EAAE,WAAW,GACvB,aAAa,CAcf;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,WAAW,GAAG,aAAa,CAa3E;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,UAAU,GACpB,gBAAgB,CAUlB;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,UAAU,GACpB,gBAAgB,CAUlB;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,aAAa,EACtB,OAAO,EAAE,gBAAgB,GACxB,UAAU,CAkBZ;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,aAAa,EACtB,OAAO,EAAE,gBAAgB,GACxB,UAAU,CAkBZ;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAG/D;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAG/D"}
@@ -0,0 +1,122 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * Session management for Sideband Relay Protocol (SBRP).
4
+ *
5
+ * Handles encrypted message sending/receiving with proper key selection,
6
+ * sequence number tracking, and replay protection.
7
+ */
8
+ import { decrypt, encrypt, extractSequence, zeroize } from "./crypto.js";
9
+ import { checkAndUpdateReplay, createReplayWindow, } from "./replay.js";
10
+ import { Direction, SbrpError, SbrpErrorCode } from "./types.js";
11
+ /**
12
+ * Create a client session (daemon side).
13
+ *
14
+ * Used by daemon to manage state for each connected client.
15
+ */
16
+ export function createClientSession(clientId, sessionKeys) {
17
+ return {
18
+ clientId,
19
+ clientToDaemon: {
20
+ trafficKey: sessionKeys.clientToDaemon,
21
+ sendSeq: 0n,
22
+ recvWindow: createReplayWindow(),
23
+ },
24
+ daemonToClient: {
25
+ trafficKey: sessionKeys.daemonToClient,
26
+ sendSeq: 0n,
27
+ recvWindow: createReplayWindow(),
28
+ },
29
+ };
30
+ }
31
+ /**
32
+ * Create a daemon session (client side).
33
+ *
34
+ * Used by client to communicate with daemon.
35
+ */
36
+ export function createDaemonSession(sessionKeys) {
37
+ return {
38
+ clientToDaemon: {
39
+ trafficKey: sessionKeys.clientToDaemon,
40
+ sendSeq: 0n,
41
+ recvWindow: createReplayWindow(),
42
+ },
43
+ daemonToClient: {
44
+ trafficKey: sessionKeys.daemonToClient,
45
+ sendSeq: 0n,
46
+ recvWindow: createReplayWindow(),
47
+ },
48
+ };
49
+ }
50
+ /**
51
+ * Encrypt a message from client to daemon.
52
+ */
53
+ export function encryptClientToDaemon(session, plaintext) {
54
+ const seq = session.clientToDaemon.sendSeq++;
55
+ const data = encrypt(session.clientToDaemon.trafficKey, Direction.ClientToDaemon, seq, plaintext);
56
+ return { type: "encrypted", seq, data };
57
+ }
58
+ /**
59
+ * Encrypt a message from daemon to client.
60
+ */
61
+ export function encryptDaemonToClient(session, plaintext) {
62
+ const seq = session.daemonToClient.sendSeq++;
63
+ const data = encrypt(session.daemonToClient.trafficKey, Direction.DaemonToClient, seq, plaintext);
64
+ return { type: "encrypted", seq, data };
65
+ }
66
+ /**
67
+ * Decrypt a message received by daemon from client.
68
+ *
69
+ * @throws {SbrpError} with code SequenceError if replay detected
70
+ * @throws {SbrpError} with code DecryptFailed if decryption fails
71
+ */
72
+ export function decryptClientToDaemon(session, message) {
73
+ // Check replay protection
74
+ const seq = extractSequence(message.data);
75
+ if (!checkAndUpdateReplay(seq, session.clientToDaemon.recvWindow)) {
76
+ throw new SbrpError(SbrpErrorCode.SequenceError, "Sequence number outside valid window or replay detected");
77
+ }
78
+ try {
79
+ return decrypt(session.clientToDaemon.trafficKey, message.data);
80
+ }
81
+ catch {
82
+ throw new SbrpError(SbrpErrorCode.DecryptFailed, "Message decryption failed");
83
+ }
84
+ }
85
+ /**
86
+ * Decrypt a message received by client from daemon.
87
+ *
88
+ * @throws {SbrpError} with code SequenceError if replay detected
89
+ * @throws {SbrpError} with code DecryptFailed if decryption fails
90
+ */
91
+ export function decryptDaemonToClient(session, message) {
92
+ // Check replay protection
93
+ const seq = extractSequence(message.data);
94
+ if (!checkAndUpdateReplay(seq, session.daemonToClient.recvWindow)) {
95
+ throw new SbrpError(SbrpErrorCode.SequenceError, "Sequence number outside valid window or replay detected");
96
+ }
97
+ try {
98
+ return decrypt(session.daemonToClient.trafficKey, message.data);
99
+ }
100
+ catch {
101
+ throw new SbrpError(SbrpErrorCode.DecryptFailed, "Message decryption failed");
102
+ }
103
+ }
104
+ /**
105
+ * Clear all key material from a client session.
106
+ *
107
+ * Best-effort zeroization (JS/GC limitations apply).
108
+ */
109
+ export function clearClientSession(session) {
110
+ zeroize(session.clientToDaemon.trafficKey);
111
+ zeroize(session.daemonToClient.trafficKey);
112
+ }
113
+ /**
114
+ * Clear all key material from a daemon session.
115
+ *
116
+ * Best-effort zeroization (JS/GC limitations apply).
117
+ */
118
+ export function clearDaemonSession(session) {
119
+ zeroize(session.clientToDaemon.trafficKey);
120
+ zeroize(session.daemonToClient.trafficKey);
121
+ }
122
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Type definitions for Sideband Relay Protocol (SBRP).
3
+ */
4
+ /** Branded type for daemon identifiers */
5
+ export type DaemonId = string & {
6
+ readonly __brand: "DaemonId";
7
+ };
8
+ /** Branded type for client session identifiers (relay-assigned) */
9
+ export type ClientId = string & {
10
+ readonly __brand: "ClientId";
11
+ };
12
+ /**
13
+ * Session identifier (uint64).
14
+ * Assigned by control plane, included in JWT `sid` claim as decimal string.
15
+ * Used in frame headers for routing.
16
+ */
17
+ export type SessionId = bigint;
18
+ /** Ed25519 identity keypair for daemon authentication */
19
+ export interface IdentityKeyPair {
20
+ publicKey: Uint8Array;
21
+ privateKey: Uint8Array;
22
+ }
23
+ /** X25519 ephemeral keypair for key exchange */
24
+ export interface EphemeralKeyPair {
25
+ publicKey: Uint8Array;
26
+ privateKey: Uint8Array;
27
+ }
28
+ /** Session keys derived from handshake (directional symmetric keys) */
29
+ export interface SessionKeys {
30
+ /** Key for encrypting client→daemon messages */
31
+ clientToDaemon: Uint8Array;
32
+ /** Key for encrypting daemon→client messages */
33
+ daemonToClient: Uint8Array;
34
+ }
35
+ /** Handshake init message (client → daemon) */
36
+ export interface HandshakeInit {
37
+ type: "handshake.init";
38
+ initPublicKey: Uint8Array;
39
+ }
40
+ /** Handshake accept message (daemon → client) */
41
+ export interface HandshakeAccept {
42
+ type: "handshake.accept";
43
+ identityPublicKey: Uint8Array;
44
+ acceptPublicKey: Uint8Array;
45
+ signature: Uint8Array;
46
+ }
47
+ /** Encrypted message envelope */
48
+ export interface EncryptedMessage {
49
+ type: "encrypted";
50
+ seq: bigint;
51
+ data: Uint8Array;
52
+ }
53
+ /** Direction of message flow (used in nonce construction) */
54
+ export declare const Direction: {
55
+ readonly ClientToDaemon: 1;
56
+ readonly DaemonToClient: 2;
57
+ };
58
+ export type Direction = (typeof Direction)[keyof typeof Direction];
59
+ /**
60
+ * SBRP error codes (string constants for SDK use).
61
+ *
62
+ * Wire codes use numeric values in Control frames (see frame.ts).
63
+ * These string constants provide type-safe SDK-level error handling.
64
+ */
65
+ export declare const SbrpErrorCode: {
66
+ readonly Unauthorized: "unauthorized";
67
+ readonly Forbidden: "forbidden";
68
+ readonly DaemonNotFound: "daemon_not_found";
69
+ readonly DaemonOffline: "daemon_offline";
70
+ readonly SessionNotFound: "session_not_found";
71
+ readonly SessionExpired: "session_expired";
72
+ readonly MalformedFrame: "malformed_frame";
73
+ readonly PayloadTooLarge: "payload_too_large";
74
+ readonly InvalidFrameType: "invalid_frame_type";
75
+ readonly InvalidSessionId: "invalid_session_id";
76
+ readonly DisallowedSender: "disallowed_sender";
77
+ readonly InternalError: "internal_error";
78
+ readonly RateLimited: "rate_limited";
79
+ readonly Backpressure: "backpressure";
80
+ readonly SessionPaused: "session_paused";
81
+ readonly SessionResumed: "session_resumed";
82
+ readonly SessionEnded: "session_ended";
83
+ readonly SessionPending: "session_pending";
84
+ readonly IdentityKeyChanged: "identity_key_changed";
85
+ readonly HandshakeFailed: "handshake_failed";
86
+ readonly HandshakeTimeout: "handshake_timeout";
87
+ readonly DecryptFailed: "decrypt_failed";
88
+ readonly SequenceError: "sequence_error";
89
+ };
90
+ export type SbrpErrorCode = (typeof SbrpErrorCode)[keyof typeof SbrpErrorCode];
91
+ /** Signal codes for Signal frame (0x04) */
92
+ export declare const SignalCode: {
93
+ readonly Ready: 0;
94
+ readonly Close: 1;
95
+ };
96
+ export type SignalCode = (typeof SignalCode)[keyof typeof SignalCode];
97
+ /** Reason codes for Signal frames (§13.4) */
98
+ export declare const SignalReason: {
99
+ /** No specific reason (default for ready signal) */
100
+ readonly None: 0;
101
+ /** Process restart, memory cleared */
102
+ readonly StateLost: 1;
103
+ /** Graceful daemon shutdown */
104
+ readonly Shutdown: 2;
105
+ /** Internal policy denial */
106
+ readonly Policy: 3;
107
+ /** Internal daemon error */
108
+ readonly Error: 4;
109
+ };
110
+ export type SignalReason = (typeof SignalReason)[keyof typeof SignalReason];
111
+ /** SBRP-specific error */
112
+ export declare class SbrpError extends Error {
113
+ readonly code: SbrpErrorCode;
114
+ constructor(code: SbrpErrorCode, message: string);
115
+ }
116
+ /** Brand a string as DaemonId (no validation) */
117
+ export declare function asDaemonId(value: string): DaemonId;
118
+ /** Brand a string as ClientId (no validation) */
119
+ export declare function asClientId(value: string): ClientId;
120
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA;;GAEG;AAEH,0CAA0C;AAC1C,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAA;CAAE,CAAC;AAEjE,mEAAmE;AACnE,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAA;CAAE,CAAC;AAEjE;;;;GAIG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAE/B,yDAAyD;AACzD,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,UAAU,CAAC;IACtB,UAAU,EAAE,UAAU,CAAC;CACxB;AAED,gDAAgD;AAChD,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,UAAU,CAAC;IACtB,UAAU,EAAE,UAAU,CAAC;CACxB;AAED,uEAAuE;AACvE,MAAM,WAAW,WAAW;IAC1B,gDAAgD;IAChD,cAAc,EAAE,UAAU,CAAC;IAC3B,gDAAgD;IAChD,cAAc,EAAE,UAAU,CAAC;CAC5B;AAED,+CAA+C;AAC/C,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,gBAAgB,CAAC;IACvB,aAAa,EAAE,UAAU,CAAC;CAC3B;AAED,iDAAiD;AACjD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,kBAAkB,CAAC;IACzB,iBAAiB,EAAE,UAAU,CAAC;IAC9B,eAAe,EAAE,UAAU,CAAC;IAC5B,SAAS,EAAE,UAAU,CAAC;CACvB;AAED,iCAAiC;AACjC,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,WAAW,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,UAAU,CAAC;CAClB;AAED,6DAA6D;AAC7D,eAAO,MAAM,SAAS;;;CAGZ,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,OAAO,SAAS,CAAC,CAAC;AAEnE;;;;;GAKG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;CAuChB,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,OAAO,aAAa,CAAC,CAAC;AAE/E,2CAA2C;AAC3C,eAAO,MAAM,UAAU;;;CAGb,CAAC;AAEX,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,OAAO,UAAU,CAAC,CAAC;AAEtE,6CAA6C;AAC7C,eAAO,MAAM,YAAY;IACvB,oDAAoD;;IAEpD,sCAAsC;;IAEtC,+BAA+B;;IAE/B,6BAA6B;;IAE7B,4BAA4B;;CAEpB,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,OAAO,YAAY,CAAC,CAAC;AAE5E,0BAA0B;AAC1B,qBAAa,SAAU,SAAQ,KAAK;aAEhB,IAAI,EAAE,aAAa;gBAAnB,IAAI,EAAE,aAAa,EACnC,OAAO,EAAE,MAAM;CAKlB;AAED,iDAAiD;AACjD,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAElD;AAED,iDAAiD;AACjD,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAElD"}