@kangentic/protocol 0.1.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 +5 -0
- package/README.md +27 -0
- package/dist/capabilities/verbs.d.ts +18 -0
- package/dist/crypto/noise/cipher-state.d.ts +9 -0
- package/dist/crypto/noise/handshake-state.d.ts +64 -0
- package/dist/crypto/noise/kk.d.ts +11 -0
- package/dist/crypto/noise/patterns.d.ts +48 -0
- package/dist/crypto/noise/symmetric-state.d.ts +28 -0
- package/dist/crypto/pairing-handshake.d.ts +20 -0
- package/dist/crypto/primitives.d.ts +53 -0
- package/dist/crypto/sas.d.ts +7 -0
- package/dist/crypto/secretstream.d.ts +39 -0
- package/dist/events/event.d.ts +29 -0
- package/dist/index.cjs +4571 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.mjs +4504 -0
- package/dist/pairing/qr.d.ts +19 -0
- package/dist/roster/roster.d.ts +33 -0
- package/dist/transport/transport.d.ts +22 -0
- package/dist/version.d.ts +12 -0
- package/dist/wire/base64url.d.ts +2 -0
- package/dist/wire/framing.d.ts +4 -0
- package/dist/wire/messages.d.ts +45 -0
- package/dist/wire/session-frame.d.ts +23 -0
- package/package.json +51 -0
package/LICENSE
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# @kangentic/protocol
|
|
2
|
+
|
|
3
|
+
Shared wire protocol for the Kangentic mobile bridge: frame encoding, the Noise-based pairing
|
|
4
|
+
and session handshakes, secretstream-style AEAD framing, the signed device roster, capability
|
|
5
|
+
verbs, and the transcript/board/activity event contract types the mobile app consumes.
|
|
6
|
+
|
|
7
|
+
This package is the single source of truth for the desktop (`src/main/mobile-bridge/`) and
|
|
8
|
+
`kangentic-mobile` implementations of the protocol. It is pure TypeScript on top of
|
|
9
|
+
[`@noble/curves`](https://github.com/paulmillr/noble-curves),
|
|
10
|
+
[`@noble/hashes`](https://github.com/paulmillr/noble-hashes), and
|
|
11
|
+
[`@noble/ciphers`](https://github.com/paulmillr/noble-ciphers) so the exact same handshake code
|
|
12
|
+
runs on Node (Electron main) and React Native (via the same pure-JS primitives), with no native
|
|
13
|
+
module coupling.
|
|
14
|
+
|
|
15
|
+
See `docs/mobile-bridge.md` in the main repository for the pairing ceremony, roster/revocation
|
|
16
|
+
model, and the honest relay-metadata statement.
|
|
17
|
+
|
|
18
|
+
## Security
|
|
19
|
+
|
|
20
|
+
- `Noise_KK_25519_ChaChaPoly_BLAKE2s` for ongoing sessions (mutual authentication by construction,
|
|
21
|
+
no trust-on-first-use).
|
|
22
|
+
- A token-bound Noise PSK handshake for the one-time pairing ceremony, confirmed by a Matrix-style
|
|
23
|
+
short authentication string (SAS) on both screens.
|
|
24
|
+
- No shell, file, or arbitrary-command verb exists in the protocol.
|
|
25
|
+
|
|
26
|
+
This package has not undergone a third-party security audit. Report suspected vulnerabilities
|
|
27
|
+
per the repository's security policy rather than filing a public issue.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The complete set of capability verbs a paired device may be granted.
|
|
3
|
+
* There is deliberately NO shell, file-read, or arbitrary-command verb --
|
|
4
|
+
* absent from the protocol entirely, not filtered at runtime (the SSH
|
|
5
|
+
* forced-command lesson: Chrome Remote Desktop and VS Code tunnels are the
|
|
6
|
+
* counter-examples, identity-gated but capability-unscoped). Adding a new
|
|
7
|
+
* verb here is a protocol change; it does not, by itself, grant anything --
|
|
8
|
+
* the desktop's capability router (Phase 2) still has to implement and
|
|
9
|
+
* wire a handler, and a device's roster entry still has to include it in
|
|
10
|
+
* its CapabilitySet.
|
|
11
|
+
*/
|
|
12
|
+
export declare const CAPABILITY_VERBS: readonly ["read-stream", "read-board", "read-diff", "send-user-message", "move-task", "answer-permission-prompt"];
|
|
13
|
+
export type CapabilityVerb = (typeof CAPABILITY_VERBS)[number];
|
|
14
|
+
export declare function isCapabilityVerb(value: string): value is CapabilityVerb;
|
|
15
|
+
/** Deny-by-default: a verb absent from the set is not authorized. */
|
|
16
|
+
export type CapabilitySet = ReadonlySet<CapabilityVerb>;
|
|
17
|
+
export declare function capabilitySetFromArray(verbs: readonly string[]): CapabilitySet;
|
|
18
|
+
export declare function capabilitySetToArray(capabilities: CapabilitySet): CapabilityVerb[];
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare class CipherState {
|
|
2
|
+
private key;
|
|
3
|
+
private nonce;
|
|
4
|
+
initializeKey(key: Uint8Array | null): void;
|
|
5
|
+
hasKey(): boolean;
|
|
6
|
+
encryptWithAd(associatedData: Uint8Array, plaintext: Uint8Array): Uint8Array;
|
|
7
|
+
/** Nonce is only advanced on a SUCCESSFUL decrypt; aeadDecrypt throws on tag failure. */
|
|
8
|
+
decryptWithAd(associatedData: Uint8Array, ciphertext: Uint8Array): Uint8Array;
|
|
9
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Noise Protocol Framework section 5.3: the HandshakeState object. A
|
|
3
|
+
* generic token interpreter driven by a NoisePattern, so both the ongoing
|
|
4
|
+
* session pattern (KK) and the pairing pattern (IKpsk0) share this exact
|
|
5
|
+
* code - only the pattern table and the supplied key material differ.
|
|
6
|
+
*/
|
|
7
|
+
import { type X25519KeyPair } from '../primitives';
|
|
8
|
+
import { CipherState } from './cipher-state';
|
|
9
|
+
import type { NoisePattern } from './patterns';
|
|
10
|
+
export interface HandshakeStateOptions {
|
|
11
|
+
pattern: NoisePattern;
|
|
12
|
+
initiator: boolean;
|
|
13
|
+
/** Bound into the transcript before any key material - kills downgrade attacks when it encodes the negotiated protocol version. */
|
|
14
|
+
prologue: Uint8Array;
|
|
15
|
+
/** Local static keypair. Required whenever the pattern's local role sends or has pre-shared an "s" token. */
|
|
16
|
+
s?: X25519KeyPair;
|
|
17
|
+
/** Local ephemeral keypair. Only ever set by a test harness; production code always lets writeMessage() generate it. */
|
|
18
|
+
e?: X25519KeyPair;
|
|
19
|
+
/** Remote static public key. Required whenever the pattern's local role receives an "s" pre-message (e.g. KK) or already knows it out-of-band (e.g. IK's responder key from a QR). */
|
|
20
|
+
rs?: Uint8Array;
|
|
21
|
+
re?: Uint8Array;
|
|
22
|
+
/** 32-byte pre-shared key. Required by patterns with a "psk" token. */
|
|
23
|
+
psk?: Uint8Array;
|
|
24
|
+
/** Test-only override for ephemeral keypair generation, to replay a fixed test vector. */
|
|
25
|
+
generateEphemeral?: () => X25519KeyPair;
|
|
26
|
+
}
|
|
27
|
+
export interface HandshakeWriteResult {
|
|
28
|
+
message: Uint8Array;
|
|
29
|
+
/** Present once the pattern's last message has been written. */
|
|
30
|
+
split?: [CipherState, CipherState];
|
|
31
|
+
}
|
|
32
|
+
export interface HandshakeReadResult {
|
|
33
|
+
payload: Uint8Array;
|
|
34
|
+
/** Present once the pattern's last message has been read. */
|
|
35
|
+
split?: [CipherState, CipherState];
|
|
36
|
+
}
|
|
37
|
+
export declare class HandshakeState {
|
|
38
|
+
private readonly symmetricState;
|
|
39
|
+
private readonly pattern;
|
|
40
|
+
private readonly initiator;
|
|
41
|
+
private readonly generateEphemeral;
|
|
42
|
+
private readonly psk?;
|
|
43
|
+
private s?;
|
|
44
|
+
private e?;
|
|
45
|
+
private rs?;
|
|
46
|
+
private re?;
|
|
47
|
+
private messageIndex;
|
|
48
|
+
constructor(options: HandshakeStateOptions);
|
|
49
|
+
private resolvePreMessageKey;
|
|
50
|
+
private requireLocalKeyPair;
|
|
51
|
+
private requireRemoteKey;
|
|
52
|
+
private requirePsk;
|
|
53
|
+
private dh;
|
|
54
|
+
private mixDhToken;
|
|
55
|
+
private nextTokens;
|
|
56
|
+
private maybeSplit;
|
|
57
|
+
writeMessage(payload: Uint8Array): HandshakeWriteResult;
|
|
58
|
+
readMessage(message: Uint8Array): HandshakeReadResult;
|
|
59
|
+
getHandshakeHash(): Uint8Array;
|
|
60
|
+
/** See SymmetricState.getChainingKey(): only meaningful once split() has occurred. */
|
|
61
|
+
getChainingKey(): Uint8Array;
|
|
62
|
+
/** The remote static public key, once learned (pre-shared or received via an "s" token). */
|
|
63
|
+
getRemoteStaticKey(): Uint8Array | undefined;
|
|
64
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { X25519KeyPair } from '../primitives';
|
|
2
|
+
import { HandshakeState } from './handshake-state';
|
|
3
|
+
export interface KKHandshakeOptions {
|
|
4
|
+
initiator: boolean;
|
|
5
|
+
/** Local device's static identity keypair. */
|
|
6
|
+
localStatic: X25519KeyPair;
|
|
7
|
+
/** Peer's static public key, already pinned in the signed device roster. */
|
|
8
|
+
remoteStatic: Uint8Array;
|
|
9
|
+
protocolVersion?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function createKKHandshake(options: KKHandshakeOptions): HandshakeState;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** Noise message-pattern tokens (section 5.3, plus "psk" from section 9). */
|
|
2
|
+
export type NoiseToken = 'e' | 's' | 'ee' | 'es' | 'se' | 'ss' | 'psk';
|
|
3
|
+
export interface NoisePattern {
|
|
4
|
+
/** Used to construct the protocol name, e.g. "KK" -> Noise_KK_25519_ChaChaPoly_BLAKE2s. */
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly initiatorPreMessage: readonly NoiseToken[];
|
|
7
|
+
readonly responderPreMessage: readonly NoiseToken[];
|
|
8
|
+
readonly messages: readonly (readonly NoiseToken[])[];
|
|
9
|
+
/**
|
|
10
|
+
* Noise section 9.2: in a PSK handshake every "e" token calls
|
|
11
|
+
* MixKey(e.public_key) in addition to the MixHash(e.public_key) a
|
|
12
|
+
* non-PSK handshake always does. This must be a property of the
|
|
13
|
+
* PATTERN (not inferred from "was a psk option passed"), since it
|
|
14
|
+
* changes how the "e" token itself is processed, independent of
|
|
15
|
+
* whether the pattern's own "psk" token has been reached yet.
|
|
16
|
+
*/
|
|
17
|
+
readonly usesPsk: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Both parties' static keys are known in advance (mutual authentication by
|
|
21
|
+
* construction, no trust-on-first-use). Used for ongoing bridge sessions
|
|
22
|
+
* once a device is in the signed roster.
|
|
23
|
+
*
|
|
24
|
+
* KK:
|
|
25
|
+
* -> s
|
|
26
|
+
* <- s
|
|
27
|
+
* ...
|
|
28
|
+
* -> e, es, ss
|
|
29
|
+
* <- e, ee, se
|
|
30
|
+
*/
|
|
31
|
+
export declare const KK_PATTERN: NoisePattern;
|
|
32
|
+
/**
|
|
33
|
+
* The initiator (phone) knows the responder's (desktop's) static key from
|
|
34
|
+
* the QR in advance; the responder learns the initiator's static key from
|
|
35
|
+
* the "s" token in message 1, which is exactly how the desktop learns the
|
|
36
|
+
* phone's identity key during pairing. The "psk0" modifier mixes the
|
|
37
|
+
* pairing token in before any Diffie-Hellman step, so message 1 only
|
|
38
|
+
* decrypts (and the "s"/payload fields inside it only authenticate) for a
|
|
39
|
+
* peer that has the same token - a wrong guess fails at DecryptAndHash
|
|
40
|
+
* with no oracle beyond "handshake failed".
|
|
41
|
+
*
|
|
42
|
+
* IKpsk0:
|
|
43
|
+
* <- s
|
|
44
|
+
* ...
|
|
45
|
+
* -> psk, e, es, s, ss
|
|
46
|
+
* <- e, ee, se
|
|
47
|
+
*/
|
|
48
|
+
export declare const IKPSK0_PATTERN: NoisePattern;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { CipherState } from './cipher-state';
|
|
2
|
+
export declare class SymmetricState {
|
|
3
|
+
private readonly cipherState;
|
|
4
|
+
private chainingKey;
|
|
5
|
+
private hash;
|
|
6
|
+
constructor(protocolName: Uint8Array);
|
|
7
|
+
mixKey(inputKeyMaterial: Uint8Array): void;
|
|
8
|
+
mixHash(data: Uint8Array): void;
|
|
9
|
+
/** Processes a "psk" token: MixKeyAndHash(psk) per Noise section 9. */
|
|
10
|
+
mixKeyAndHash(inputKeyMaterial: Uint8Array): void;
|
|
11
|
+
getHandshakeHash(): Uint8Array;
|
|
12
|
+
/**
|
|
13
|
+
* The raw chaining key, meaningful only after the handshake's message
|
|
14
|
+
* patterns are exhausted (alongside split()). Exposed so a caller can
|
|
15
|
+
* derive ADDITIONAL independent key material (e.g. secretstream framing
|
|
16
|
+
* keys) via its own domain-separated HKDF label - see
|
|
17
|
+
* crypto/secretstream.ts's deriveTransportKey. This is standard KDF
|
|
18
|
+
* practice (TLS 1.3's key schedule does the same): reusing one secret
|
|
19
|
+
* for multiple purposes via distinct labels is safe; Split()'s own
|
|
20
|
+
* derivation (empty info) and a labeled derivation never collide.
|
|
21
|
+
*/
|
|
22
|
+
getChainingKey(): Uint8Array;
|
|
23
|
+
encryptAndHash(plaintext: Uint8Array): Uint8Array;
|
|
24
|
+
decryptAndHash(ciphertext: Uint8Array): Uint8Array;
|
|
25
|
+
hasKey(): boolean;
|
|
26
|
+
/** Only valid once every message pattern has been processed. */
|
|
27
|
+
split(): [CipherState, CipherState];
|
|
28
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { X25519KeyPair } from './primitives';
|
|
2
|
+
import { HandshakeState } from './noise/handshake-state';
|
|
3
|
+
export interface PairingInitiatorOptions {
|
|
4
|
+
/** The phone's own static identity keypair. */
|
|
5
|
+
localStatic: X25519KeyPair;
|
|
6
|
+
/** The desktop's static public key, read from the QR payload. */
|
|
7
|
+
remoteStatic: Uint8Array;
|
|
8
|
+
/** The 32-byte single-use pairing token from the QR payload, used as the Noise PSK. */
|
|
9
|
+
pairingToken: Uint8Array;
|
|
10
|
+
protocolVersion?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function createPairingInitiatorHandshake(options: PairingInitiatorOptions): HandshakeState;
|
|
13
|
+
export interface PairingResponderOptions {
|
|
14
|
+
/** The desktop's own static identity keypair (the one published in the QR). */
|
|
15
|
+
localStatic: X25519KeyPair;
|
|
16
|
+
/** The same 32-byte single-use pairing token minted for this QR. */
|
|
17
|
+
pairingToken: Uint8Array;
|
|
18
|
+
protocolVersion?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function createPairingResponderHandshake(options: PairingResponderOptions): HandshakeState;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { hexToBytes, bytesToHex } from '@noble/hashes/utils.js';
|
|
2
|
+
export declare const X25519_KEY_LENGTH = 32;
|
|
3
|
+
export declare const ED25519_KEY_LENGTH = 32;
|
|
4
|
+
export declare const ED25519_SIGNATURE_LENGTH = 64;
|
|
5
|
+
export declare const HASH_LENGTH = 32;
|
|
6
|
+
export declare const AEAD_KEY_LENGTH = 32;
|
|
7
|
+
export declare const CHACHA_NONCE_LENGTH = 12;
|
|
8
|
+
export declare const XCHACHA_NONCE_LENGTH = 24;
|
|
9
|
+
export declare const AEAD_TAG_LENGTH = 16;
|
|
10
|
+
export declare function randomBytes(length: number): Uint8Array;
|
|
11
|
+
export { hexToBytes, bytesToHex };
|
|
12
|
+
export interface X25519KeyPair {
|
|
13
|
+
secretKey: Uint8Array;
|
|
14
|
+
publicKey: Uint8Array;
|
|
15
|
+
}
|
|
16
|
+
export declare function generateX25519KeyPair(): X25519KeyPair;
|
|
17
|
+
export declare function x25519PublicKeyFrom(secretKey: Uint8Array): Uint8Array;
|
|
18
|
+
/** Diffie-Hellman: DH(localSecretKey, remotePublicKey). Throws on a low-order/invalid peer key. */
|
|
19
|
+
export declare function x25519SharedSecret(secretKey: Uint8Array, publicKey: Uint8Array): Uint8Array;
|
|
20
|
+
export interface Ed25519KeyPair {
|
|
21
|
+
secretKey: Uint8Array;
|
|
22
|
+
publicKey: Uint8Array;
|
|
23
|
+
}
|
|
24
|
+
export declare function generateEd25519KeyPair(): Ed25519KeyPair;
|
|
25
|
+
export declare function ed25519Sign(message: Uint8Array, secretKey: Uint8Array): Uint8Array;
|
|
26
|
+
export declare function ed25519Verify(signature: Uint8Array, message: Uint8Array, publicKey: Uint8Array): boolean;
|
|
27
|
+
export declare function hashBlake2s(data: Uint8Array): Uint8Array;
|
|
28
|
+
/**
|
|
29
|
+
* Noise's HKDF(chaining_key, input_key_material, num_outputs): HKDF-Extract
|
|
30
|
+
* with salt=chaining_key, ikm=input_key_material, then HKDF-Expand with an
|
|
31
|
+
* empty info, chunked into num_outputs HASH_LENGTH-byte blocks. This is
|
|
32
|
+
* exactly RFC 5869 HKDF with a zero-length info string, so the noble
|
|
33
|
+
* convenience function (extract+expand combined) is used directly rather
|
|
34
|
+
* than hand-rolling the counter loop.
|
|
35
|
+
*/
|
|
36
|
+
export declare function noiseHkdf(chainingKey: Uint8Array, inputKeyMaterial: Uint8Array, numOutputs: 2 | 3): Uint8Array[];
|
|
37
|
+
/**
|
|
38
|
+
* Independent, domain-separated key derivation from a Noise chaining key,
|
|
39
|
+
* for material that is NOT part of the Noise handshake itself (e.g. the
|
|
40
|
+
* secretstream framing keys derived once a session's split() has run).
|
|
41
|
+
* Reusing one secret for multiple purposes via distinct labels is safe
|
|
42
|
+
* KDF practice as long as every purpose gets its own label; this is a
|
|
43
|
+
* plain HKDF-Expand-style derivation (salt=chainingKey, ikm=empty,
|
|
44
|
+
* info=label), independent of noiseHkdf's own empty-info convention.
|
|
45
|
+
*/
|
|
46
|
+
export declare function deriveLabeledKey(chainingKey: Uint8Array, label: string, length: number): Uint8Array;
|
|
47
|
+
export declare function aeadEncrypt(key: Uint8Array, counter: bigint, associatedData: Uint8Array, plaintext: Uint8Array): Uint8Array;
|
|
48
|
+
export declare function aeadDecrypt(key: Uint8Array, counter: bigint, associatedData: Uint8Array, ciphertext: Uint8Array): Uint8Array;
|
|
49
|
+
/** XChaCha20-Poly1305 with a 24-byte nonce - used by the secretstream framing, not the Noise handshake. */
|
|
50
|
+
export declare function xaeadEncrypt(key: Uint8Array, nonce: Uint8Array, associatedData: Uint8Array, plaintext: Uint8Array): Uint8Array;
|
|
51
|
+
export declare function xaeadDecrypt(key: Uint8Array, nonce: Uint8Array, associatedData: Uint8Array, ciphertext: Uint8Array): Uint8Array;
|
|
52
|
+
export declare function constantTimeEqual(a: Uint8Array, b: Uint8Array): boolean;
|
|
53
|
+
export declare function concatBytes(...arrays: Uint8Array[]): Uint8Array;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface ShortAuthenticationString {
|
|
2
|
+
/** A 6-digit code, e.g. "042917". */
|
|
3
|
+
digits: string;
|
|
4
|
+
/** 5 emoji from a fixed 64-entry table. */
|
|
5
|
+
emoji: string[];
|
|
6
|
+
}
|
|
7
|
+
export declare function deriveShortAuthenticationString(handshakeHash: Uint8Array): ShortAuthenticationString;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export declare const FrameTag: {
|
|
2
|
+
readonly Message: 0;
|
|
3
|
+
readonly Rekey: 1;
|
|
4
|
+
readonly Final: 2;
|
|
5
|
+
};
|
|
6
|
+
export type FrameTag = (typeof FrameTag)[keyof typeof FrameTag];
|
|
7
|
+
export declare class SecretstreamState {
|
|
8
|
+
private key;
|
|
9
|
+
private readonly nonceHeader;
|
|
10
|
+
private counter;
|
|
11
|
+
constructor(key: Uint8Array, nonceHeader: Uint8Array);
|
|
12
|
+
private nonceForCounter;
|
|
13
|
+
/** Encrypts one frame and advances this direction's counter. */
|
|
14
|
+
seal(plaintext: Uint8Array, tag?: FrameTag): Uint8Array;
|
|
15
|
+
/**
|
|
16
|
+
* Decrypts one frame and advances this direction's counter. Throws if
|
|
17
|
+
* the frame does not authenticate against the counter this instance
|
|
18
|
+
* currently expects - which is exactly what happens if a frame was
|
|
19
|
+
* replayed, reordered, or the stream was truncated and resumed wrong.
|
|
20
|
+
*/
|
|
21
|
+
open(frame: Uint8Array): {
|
|
22
|
+
tag: FrameTag;
|
|
23
|
+
plaintext: Uint8Array;
|
|
24
|
+
};
|
|
25
|
+
/** Rotates this direction's key without a full Noise re-handshake, matching libsodium's REKEY tag. */
|
|
26
|
+
rekey(): void;
|
|
27
|
+
}
|
|
28
|
+
export interface SecretstreamDirectionPair {
|
|
29
|
+
send: SecretstreamState;
|
|
30
|
+
receive: SecretstreamState;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Derives both directions' secretstream state from a completed Noise
|
|
34
|
+
* session's chaining key (HandshakeState.getChainingKey(), valid once
|
|
35
|
+
* split() has run). Both peers call this with the SAME chaining key and
|
|
36
|
+
* opposite `isInitiator` values, so `initiator.send` pairs with
|
|
37
|
+
* `responder.receive` and vice versa.
|
|
38
|
+
*/
|
|
39
|
+
export declare function deriveSecretstreamPair(chainingKey: Uint8Array, isInitiator: boolean): SecretstreamDirectionPair;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The event contract skeleton the phone consumes. Phase 1 establishes the
|
|
3
|
+
* three event FAMILIES (transcript, board, activity) and their envelope
|
|
4
|
+
* shape; Phase 2 (data feeds & interactive control) is what actually maps
|
|
5
|
+
* desktop internals - SessionManager's `data`/`activity` events,
|
|
6
|
+
* transcript-service output, repository/DiffService mutations - onto
|
|
7
|
+
* these envelopes. Keeping `payload` as JsonValue here rather than a
|
|
8
|
+
* fully-typed shape per event avoids guessing at Phase 2's real mapping
|
|
9
|
+
* before that integration exists.
|
|
10
|
+
*/
|
|
11
|
+
import type { JsonValue } from '../wire/messages';
|
|
12
|
+
export interface TranscriptEvent {
|
|
13
|
+
kind: 'transcript';
|
|
14
|
+
sessionId: string;
|
|
15
|
+
taskId: string;
|
|
16
|
+
payload: JsonValue;
|
|
17
|
+
}
|
|
18
|
+
export interface BoardEvent {
|
|
19
|
+
kind: 'board';
|
|
20
|
+
taskId: string;
|
|
21
|
+
payload: JsonValue;
|
|
22
|
+
}
|
|
23
|
+
export interface ActivityEvent {
|
|
24
|
+
kind: 'activity';
|
|
25
|
+
sessionId: string;
|
|
26
|
+
taskId: string;
|
|
27
|
+
payload: JsonValue;
|
|
28
|
+
}
|
|
29
|
+
export type BridgeEvent = TranscriptEvent | BoardEvent | ActivityEvent;
|