p2party 0.8.0 → 0.14.2

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 (182) hide show
  1. package/LICENSE.md +202 -661
  2. package/README.md +736 -404
  3. package/THIRD_PARTY_NOTICES.md +458 -0
  4. package/docs/assets/p2party-cat.svg +1 -0
  5. package/docs/getting-started.md +203 -0
  6. package/docs/protocol-v4-security.md +158 -0
  7. package/docs/references.md +157 -0
  8. package/docs/session-api.md +457 -0
  9. package/docs/wire-format.md +103 -0
  10. package/examples/standalone-e2ee.ts +194 -0
  11. package/lib/api/signalingServerApi.d.ts +6 -7
  12. package/lib/api/webrtc/baseQuery.d.ts +1 -2
  13. package/lib/api/webrtc/disconnectFromAllRoomsQuery.d.ts +1 -2
  14. package/lib/api/webrtc/disconnectFromChannelLabelQuery.d.ts +1 -2
  15. package/lib/api/webrtc/disconnectFromPeerChannelLabelQuery.d.ts +1 -2
  16. package/lib/api/webrtc/disconnectFromPeerQuery.d.ts +3 -3
  17. package/lib/api/webrtc/disconnectFromRoomQuery.d.ts +1 -2
  18. package/lib/api/webrtc/disconnectQuery.d.ts +1 -2
  19. package/lib/api/webrtc/iceGeneration.d.ts +17 -0
  20. package/lib/api/webrtc/iceRepair.d.ts +10 -0
  21. package/lib/api/webrtc/index.d.ts +13 -13
  22. package/lib/api/webrtc/interfaces.d.ts +67 -11
  23. package/lib/api/webrtc/negotiationLock.d.ts +12 -0
  24. package/lib/api/webrtc/openChannelQuery.d.ts +1 -2
  25. package/lib/api/webrtc/pendingIceCandidates.d.ts +14 -0
  26. package/lib/api/webrtc/roomPeer.d.ts +11 -0
  27. package/lib/api/webrtc/sendMessageQuery.d.ts +7 -4
  28. package/lib/api/webrtc/setCandidateQuery.d.ts +1 -2
  29. package/lib/api/webrtc/setDescriptionQuery.d.ts +1 -2
  30. package/lib/cryptography/coverCell.d.ts +65 -0
  31. package/lib/cryptography/cpace.d.ts +41 -0
  32. package/lib/cryptography/ed25519.d.ts +0 -1
  33. package/lib/cryptography/hashStream.d.ts +15 -0
  34. package/lib/cryptography/hkdf.d.ts +3 -0
  35. package/lib/cryptography/identityCrossSig.d.ts +7 -0
  36. package/lib/cryptography/interfaces.d.ts +28 -13
  37. package/lib/cryptography/memory.d.ts +2 -4
  38. package/lib/cryptography/merkle.d.ts +0 -1
  39. package/lib/cryptography/mlkem.d.ts +117 -0
  40. package/lib/cryptography/mnemonic.d.ts +0 -1
  41. package/lib/cryptography/pqHealing.d.ts +268 -0
  42. package/lib/cryptography/pqHealingFrame.d.ts +39 -0
  43. package/lib/cryptography/pqMessageKey.d.ts +30 -0
  44. package/lib/cryptography/random.d.ts +22 -0
  45. package/lib/cryptography/ratchet.d.ts +146 -0
  46. package/lib/cryptography/testModule.d.ts +7 -0
  47. package/lib/cryptography/utils.d.ts +0 -1
  48. package/lib/cryptography/wasmLoader.d.ts +6 -1
  49. package/lib/cryptography/x25519.d.ts +15 -0
  50. package/lib/cryptography/x3dh.d.ts +9 -0
  51. package/lib/db/api.d.ts +54 -22
  52. package/lib/db/db.worker.d.ts +0 -1
  53. package/lib/db/ratchetWrap.d.ts +36 -0
  54. package/lib/db/src/getDB.d.ts +17 -4
  55. package/lib/db/types.d.ts +274 -32
  56. package/lib/db.worker.js +1 -2
  57. package/lib/handlers/chunkFrame.d.ts +13 -0
  58. package/lib/handlers/connectionSignal.d.ts +26 -0
  59. package/lib/handlers/coverEdge.d.ts +42 -0
  60. package/lib/handlers/coverRuntime.d.ts +91 -0
  61. package/lib/handlers/coverScheduler.d.ts +184 -0
  62. package/lib/handlers/coverTransfer.d.ts +46 -0
  63. package/lib/handlers/frameType.d.ts +10 -0
  64. package/lib/handlers/handleChallenge.d.ts +0 -1
  65. package/lib/handlers/handleConnectToPeer.d.ts +0 -1
  66. package/lib/handlers/handleHandshake.d.ts +74 -0
  67. package/lib/handlers/handleMessageQueueing.d.ts +53 -4
  68. package/lib/handlers/handleOpenChannel.d.ts +4 -2
  69. package/lib/handlers/handleQueuedIceCandidates.d.ts +0 -1
  70. package/lib/handlers/handleReadReceipt.d.ts +17 -4
  71. package/lib/handlers/handleReceiveMessage.d.ts +21 -3
  72. package/lib/handlers/handleSendMessage.d.ts +110 -2
  73. package/lib/handlers/handleWebSocketMessage.d.ts +1 -3
  74. package/lib/handlers/handshakeCore.d.ts +95 -0
  75. package/lib/handlers/messageChunkCrypto.d.ts +70 -0
  76. package/lib/handlers/peerRosterDelta.d.ts +12 -0
  77. package/lib/handlers/pqHealingOrchestrator.d.ts +46 -0
  78. package/lib/handlers/pqHealingRuntime.d.ts +82 -0
  79. package/lib/handlers/ratchetGate.d.ts +18 -0
  80. package/lib/handlers/ratchetPersist.d.ts +53 -0
  81. package/lib/handlers/receiptFrame.d.ts +18 -0
  82. package/lib/handlers/receiveMessageKeyLifetime.d.ts +24 -0
  83. package/lib/handlers/reconcile.d.ts +16 -0
  84. package/lib/handlers/transferAbort.d.ts +42 -0
  85. package/lib/index.d.ts +203 -46
  86. package/lib/index.js +1 -2
  87. package/lib/index.min.js +1 -2
  88. package/lib/index.mjs +1 -2
  89. package/lib/libcrypto.provenance.json +45 -0
  90. package/lib/libcrypto.wasm +0 -0
  91. package/lib/middleware/keyPairListenerMiddleware.d.ts +0 -1
  92. package/lib/middleware/roomListenerMiddleware.d.ts +0 -1
  93. package/lib/reducers/commonSlice.d.ts +0 -1
  94. package/lib/reducers/keyPairSlice.d.ts +0 -1
  95. package/lib/reducers/roomSlice.d.ts +65 -3
  96. package/lib/reducers/signalingServerSlice.d.ts +25 -4
  97. package/lib/roomInvite.d.ts +18 -0
  98. package/lib/roomPinAttempts.d.ts +12 -0
  99. package/lib/roomPinVault.d.ts +26 -0
  100. package/lib/roomPolicy.d.ts +60 -0
  101. package/lib/session.d.ts +110 -0
  102. package/lib/session.js +1 -0
  103. package/lib/session.mjs +1 -0
  104. package/lib/store.d.ts +40 -41
  105. package/lib/utils/channelLabel.d.ts +0 -1
  106. package/lib/utils/chunkBounds.d.ts +9 -0
  107. package/lib/utils/constants.d.ts +50 -6
  108. package/lib/utils/debug.d.ts +25 -0
  109. package/lib/utils/drainAndClose.d.ts +5 -0
  110. package/lib/utils/identityRole.d.ts +19 -0
  111. package/lib/utils/interfaces.d.ts +31 -43
  112. package/lib/utils/leafHash.d.ts +13 -0
  113. package/lib/utils/messageTypes.d.ts +0 -1
  114. package/lib/utils/metadata.d.ts +6 -1
  115. package/lib/utils/mutex.d.ts +24 -0
  116. package/lib/utils/protocolVersion.d.ts +5 -0
  117. package/lib/utils/receiptToken.d.ts +7 -0
  118. package/lib/utils/sendQueueKey.d.ts +2 -0
  119. package/lib/utils/signalingAuth.d.ts +8 -0
  120. package/lib/utils/signalingBounds.d.ts +9 -0
  121. package/lib/utils/splitToChunks.d.ts +7 -4
  122. package/lib/utils/uint8array.d.ts +1 -2
  123. package/lib/utils/waitForOpen.d.ts +3 -0
  124. package/lib/utils/zeroFree.d.ts +10 -0
  125. package/package.json +109 -49
  126. package/lib/api/signalingServerApi.d.ts.map +0 -1
  127. package/lib/api/webrtc/baseQuery.d.ts.map +0 -1
  128. package/lib/api/webrtc/disconnectFromAllRoomsQuery.d.ts.map +0 -1
  129. package/lib/api/webrtc/disconnectFromChannelLabelQuery.d.ts.map +0 -1
  130. package/lib/api/webrtc/disconnectFromPeerChannelLabelQuery.d.ts.map +0 -1
  131. package/lib/api/webrtc/disconnectFromPeerQuery.d.ts.map +0 -1
  132. package/lib/api/webrtc/disconnectFromRoomQuery.d.ts.map +0 -1
  133. package/lib/api/webrtc/disconnectQuery.d.ts.map +0 -1
  134. package/lib/api/webrtc/index.d.ts.map +0 -1
  135. package/lib/api/webrtc/interfaces.d.ts.map +0 -1
  136. package/lib/api/webrtc/openChannelQuery.d.ts.map +0 -1
  137. package/lib/api/webrtc/sendMessageQuery.d.ts.map +0 -1
  138. package/lib/api/webrtc/setCandidateQuery.d.ts.map +0 -1
  139. package/lib/api/webrtc/setDescriptionQuery.d.ts.map +0 -1
  140. package/lib/cryptography/chacha20poly1305.d.ts +0 -116
  141. package/lib/cryptography/chacha20poly1305.d.ts.map +0 -1
  142. package/lib/cryptography/ed25519.d.ts.map +0 -1
  143. package/lib/cryptography/interfaces.d.ts.map +0 -1
  144. package/lib/cryptography/memory.d.ts.map +0 -1
  145. package/lib/cryptography/merkle.d.ts.map +0 -1
  146. package/lib/cryptography/mnemonic.d.ts.map +0 -1
  147. package/lib/cryptography/utils.d.ts.map +0 -1
  148. package/lib/cryptography/wasmLoader.d.ts.map +0 -1
  149. package/lib/db/api.d.ts.map +0 -1
  150. package/lib/db/db.worker.d.ts.map +0 -1
  151. package/lib/db/src/getDB.d.ts.map +0 -1
  152. package/lib/db/types.d.ts.map +0 -1
  153. package/lib/db.worker.js.map +0 -1
  154. package/lib/handlers/handleChallenge.d.ts.map +0 -1
  155. package/lib/handlers/handleConnectToPeer.d.ts.map +0 -1
  156. package/lib/handlers/handleMessageQueueing.d.ts.map +0 -1
  157. package/lib/handlers/handleOpenChannel.d.ts.map +0 -1
  158. package/lib/handlers/handleQueuedIceCandidates.d.ts.map +0 -1
  159. package/lib/handlers/handleReadReceipt.d.ts.map +0 -1
  160. package/lib/handlers/handleReceiveMessage.d.ts.map +0 -1
  161. package/lib/handlers/handleSendMessage.d.ts.map +0 -1
  162. package/lib/handlers/handleWebSocketMessage.d.ts.map +0 -1
  163. package/lib/index.d.ts.map +0 -1
  164. package/lib/index.js.map +0 -1
  165. package/lib/index.min.js.map +0 -1
  166. package/lib/index.mjs.map +0 -1
  167. package/lib/middleware/keyPairListenerMiddleware.d.ts.map +0 -1
  168. package/lib/middleware/roomListenerMiddleware.d.ts.map +0 -1
  169. package/lib/reducers/commonSlice.d.ts.map +0 -1
  170. package/lib/reducers/keyPairSlice.d.ts.map +0 -1
  171. package/lib/reducers/roomSlice.d.ts.map +0 -1
  172. package/lib/reducers/signalingServerSlice.d.ts.map +0 -1
  173. package/lib/store.d.ts.map +0 -1
  174. package/lib/utils/allocators.d.ts +0 -22
  175. package/lib/utils/allocators.d.ts.map +0 -1
  176. package/lib/utils/channelLabel.d.ts.map +0 -1
  177. package/lib/utils/constants.d.ts.map +0 -1
  178. package/lib/utils/interfaces.d.ts.map +0 -1
  179. package/lib/utils/messageTypes.d.ts.map +0 -1
  180. package/lib/utils/metadata.d.ts.map +0 -1
  181. package/lib/utils/splitToChunks.d.ts.map +0 -1
  182. package/lib/utils/uint8array.d.ts.map +0 -1
@@ -1,5 +1,113 @@
1
1
  import type { IRTCDataChannel, IRTCPeerConnection } from "../api/webrtc/interfaces";
2
2
  import type { LibCrypto } from "../cryptography/libcrypto";
3
+ import type { PqMessageKeyContext } from "../cryptography/pqMessageKey";
4
+ import type { RatchetHeader } from "../cryptography/ratchet";
3
5
  import type { BaseQueryApi } from "@reduxjs/toolkit/query";
4
- export declare const handleSendMessage: (data: string | File, api: BaseQueryApi, label: string, roomId: string, peerConnections: IRTCPeerConnection[], dataChannels: IRTCDataChannel[], encryptionModule: LibCrypto, merkleModule: LibCrypto, minChunks?: number, chunkSize?: number, percentageFilledChunk?: number, metadataSchemaVersion?: number) => Promise<void>;
5
- //# sourceMappingURL=handleSendMessage.d.ts.map
6
+ export declare const SEND_RECEIPT_WINDOW: number;
7
+ export declare const SEND_WINDOW_STALL_MS = 10000;
8
+ export interface ReceiptWindowParams {
9
+ readonly windowSize: number;
10
+ readonly stallTimeoutMs: number;
11
+ readonly pollMs: number;
12
+ readonly isOpen: () => boolean;
13
+ readonly inFlight: () => number;
14
+ readonly signal?: AbortSignal;
15
+ }
16
+ /**
17
+ * Resolve once in-flight drops below the window, the channel leaves "open",
18
+ * or receipt progress stalls for stallTimeoutMs. Any receipt progress while
19
+ * still over the window resets the stall clock. Throws only on transfer abort.
20
+ */
21
+ export declare const waitForReceiptWindow: (params: ReceiptWindowParams) => Promise<void>;
22
+ interface TransferCipher {
23
+ epc: IRTCPeerConnection;
24
+ messageKey: Uint8Array;
25
+ header: RatchetHeader;
26
+ /**
27
+ * OWNED copy of the PQ message context captured in the same edge
28
+ * transaction as the ratchet step (v4). Null only on runtime-free
29
+ * bootstrap/test edges. Wiped with the message key.
30
+ */
31
+ pqContext?: PqMessageKeyContext | null;
32
+ }
33
+ /**
34
+ * Stop a per-message transport without depending on its eventual `close`
35
+ * event to release protocol accounting. This also covers cancellation while
36
+ * the SCTP stream is still connecting and has not entered `dataChannels`.
37
+ */
38
+ export declare const closeTransferChannel: (channel: IRTCDataChannel) => void;
39
+ /**
40
+ * Own the one normal terminal close for a message-scoped channel. Receipt
41
+ * handling only records authenticated completion; sendWithReconcile reaches
42
+ * this boundary after its send/reconcile work settles.
43
+ */
44
+ export declare const runWithTerminalChannelClose: <T>(currentChannel: () => IRTCDataChannel, operation: () => Promise<T>) => Promise<T>;
45
+ type WaitForRatchetGate = (roomId: string, peerId: string) => Promise<void>;
46
+ type DurableRatchetStep = (epc: IRTCPeerConnection, roomId: string, module: LibCrypto) => Promise<{
47
+ messageKey: Uint8Array;
48
+ header: RatchetHeader;
49
+ pqContext: PqMessageKeyContext | null;
50
+ }>;
51
+ /**
52
+ * Bind an in-flight message to the active cryptographic transport.
53
+ *
54
+ * Reopening only its DataChannel on the SAME RTCPeerConnection retains the
55
+ * ratchet session, so the per-message key/header remain valid. A replacement
56
+ * RTCPeerConnection, however, performs a fresh hybrid handshake and owns an
57
+ * unrelated ratchet. It must advance that ratchet once and reseal the missing
58
+ * plaintext chunks; replaying the old ciphertext/key/header cannot decrypt.
59
+ *
60
+ * Exported only so the transport-identity and key-erasure contract has a focused
61
+ * unit test. It is not part of the package's public root exports.
62
+ */
63
+ export declare const bindTransferCipherToConnection: (transfer: TransferCipher, nextEpc: IRTCPeerConnection, roomId: string, module: LibCrypto, waitForGate?: WaitForRatchetGate, ratchetStep?: DurableRatchetStep, signal?: AbortSignal) => Promise<TransferCipher>;
64
+ export declare const isAuthenticatedPeerCancel: (currentEpc: IRTCPeerConnection, peerConnections: IRTCPeerConnection[], roomId: string, peerId: string) => boolean;
65
+ export interface PeerSendTarget {
66
+ peerId: string;
67
+ epc?: IRTCPeerConnection;
68
+ }
69
+ export type PeerDeliveryOutcome = {
70
+ peerId: string;
71
+ status: "delivered";
72
+ } | {
73
+ peerId: string;
74
+ status: "failed";
75
+ phase: "setup" | "transfer";
76
+ reason: unknown;
77
+ } | {
78
+ peerId: string;
79
+ status: "skipped";
80
+ reason: "not-connected" | "unauthenticated" | "cancelled";
81
+ };
82
+ export interface PeerSendFanoutResult {
83
+ outcomes: PeerDeliveryOutcome[];
84
+ startedTransfers: number;
85
+ }
86
+ type OpenPeerTransferChannel = (target: Required<PeerSendTarget>) => Promise<IRTCDataChannel>;
87
+ type StartPeerTransfer = (target: Required<PeerSendTarget>, channel: IRTCDataChannel) => Promise<void>;
88
+ /**
89
+ * Set up every eligible room edge and settle every transfer that was started.
90
+ *
91
+ * Opening channels remains sequential so it cannot burst through the per-edge
92
+ * channel budget. Transfer promises run concurrently, however, and are given a
93
+ * rejection handler immediately. A later setup failure therefore becomes that
94
+ * peer's outcome instead of unwinding past already-running sends and freeing
95
+ * their shared `newChunks` staging records.
96
+ */
97
+ export declare const runPeerSendFanout: (targets: readonly PeerSendTarget[], openChannel: OpenPeerTransferChannel, startTransfer: StartPeerTransfer, signal?: AbortSignal, onTransferStarted?: () => void) => Promise<PeerSendFanoutResult>;
98
+ export interface SendMessageResult extends PeerSendFanoutResult {
99
+ transferId: string;
100
+ merkleRootHex: string;
101
+ }
102
+ export declare class MessageDeliveryError extends AggregateError {
103
+ readonly result: SendMessageResult;
104
+ constructor(result: SendMessageResult, message: string);
105
+ }
106
+ /**
107
+ * The sender's history is independent of remote delivery once splitToChunks
108
+ * has committed its complete local copy. Network/setup failures may annotate
109
+ * delivery status, but only an explicit local cancel removes that history.
110
+ */
111
+ export declare const shouldDeleteLocalMessageAfterFailure: (localMessageCommitted: boolean, wireWorkStarted: boolean, explicitlyCancelled: boolean) => boolean;
112
+ export declare const handleSendMessage: (data: string | File, api: BaseQueryApi, label: string, roomId: string, peerConnections: IRTCPeerConnection[], dataChannels: IRTCDataChannel[], encryptionModule: LibCrypto, merkleModule: LibCrypto, transferId: string, minChunks?: number, chunkSize?: number, percentageFilledChunk?: number, metadataSchemaVersion?: number) => Promise<SendMessageResult | undefined>;
113
+ export {};
@@ -1,5 +1,3 @@
1
1
  import type { BaseQueryApi } from "@reduxjs/toolkit/query";
2
- import type { WSPeerConnection } from "../utils/interfaces";
3
- declare const handleWebSocketMessage: (event: MessageEvent, ws: WebSocket, api: BaseQueryApi, peerConnections: WSPeerConnection[]) => Promise<void>;
2
+ declare const handleWebSocketMessage: (event: MessageEvent, ws: WebSocket, api: BaseQueryApi) => Promise<void>;
4
3
  export default handleWebSocketMessage;
5
- //# sourceMappingURL=handleWebSocketMessage.d.ts.map
@@ -0,0 +1,95 @@
1
+ import type { LibCrypto } from "../cryptography/libcrypto";
2
+ import type { RatchetState } from "../cryptography/ratchet";
3
+ import type { RoomPqMode } from "../roomPolicy";
4
+ export interface ChannelInputParams {
5
+ channelId: Uint8Array;
6
+ ikInitiator: Uint8Array;
7
+ ikResponder: Uint8Array;
8
+ fpInitiator: Uint8Array;
9
+ fpResponder: Uint8Array;
10
+ /** Defaults to the 0.10 ML-KEM-768 suite for source compatibility. */
11
+ pqMode?: RoomPqMode;
12
+ }
13
+ /**
14
+ * CPace channel-input transcript (spec §5): CI = channel-id ‖ IK_a ‖ IK_b ‖
15
+ * fp_a ‖ fp_b ‖ PQ_TAG, with a = initiator, b = responder. Both peers build a
16
+ * byte-identical CI because they agree on the initiator role (Task 4). Binding
17
+ * both identity keys + both DTLS fingerprints is what makes a swapped-cert
18
+ * MITM fail the key-confirmation MAC.
19
+ *
20
+ * Deliberately synchronous (unlike the project's async concatUint8Arrays
21
+ * helper in utils/uint8array.ts, which yields to the microtask queue for
22
+ * parity with other WASM-backed helpers): CI construction is pure
23
+ * byte-copying with no WASM/IndexedDB involved, and callers build it inline
24
+ * while assembling the CPace transcript.
25
+ */
26
+ export declare const buildChannelInput: (p: ChannelInputParams) => Uint8Array;
27
+ export type HandshakeStep = 1 | 2 | 3;
28
+ /**
29
+ * Fail-closed pre-auth framing check used before a payload enters the handshake
30
+ * inbox. This keeps attacker-controlled frames bounded before the crypto core
31
+ * consumes them; the core repeats the same checks when parsing.
32
+ */
33
+ export declare const isHandshakePayloadForStep: (payload: Uint8Array, step: HandshakeStep, pqMode?: RoomPqMode) => boolean;
34
+ export interface HandshakeTransport {
35
+ send(bytes: Uint8Array): void | Promise<void>;
36
+ recv(): Promise<Uint8Array>;
37
+ }
38
+ export interface HandshakeCoreParams {
39
+ mode: "pin" | "nopin";
40
+ /** Exact preselected suite; omission selects the documented 768 default. */
41
+ pqMode?: RoomPqMode;
42
+ pin: Uint8Array | null;
43
+ channelInput: Uint8Array;
44
+ amInitiator: boolean;
45
+ idSelfSec: Uint8Array;
46
+ selfIdentityX25519Pub: Uint8Array;
47
+ selfIdentityCrossSignature: Uint8Array;
48
+ peerIdentityEd25519Pub: Uint8Array;
49
+ }
50
+ export interface PqHealingBootstrap {
51
+ /** Secret root dedicated to sparse post-quantum healing. Caller-owned. */
52
+ rootKey: Uint8Array;
53
+ /** Non-secret transcript/edge binding shared by both authenticated peers. */
54
+ binding: Uint8Array;
55
+ /** Stable Ed25519 role ordering chooses the first OFFER turn. */
56
+ nextOfferer: "local" | "remote";
57
+ }
58
+ /**
59
+ * Three-flight key-confirmed handshake core (spec §5), decoupled from
60
+ * RTCDataChannel so it is
61
+ * unit-testable with two linked in-memory transports:
62
+ * R1 initiator sends HELLO {sid, EK, Y, ML-KEM pk}; responder encapsulates,
63
+ * replies with HELLO {sid, EK, Y, ML-KEM ct}; both derive the 32-byte
64
+ * hybrid root from interactive-3DH || ML-KEM (no-PIN) or
65
+ * CPace-ISK || interactive-3DH || ML-KEM (PIN).
66
+ * R2 responder initRatchet(false,null) → dhPubR and sends
67
+ * CONFIRM{dhPubR, mac_R}; initiator seeds against dhPubR and sends
68
+ * CONFIRM{dhPubI, mac_I}, where mac_I commits to the received mac_R.
69
+ * R3 after verifying mac_I and priming both ratchet chains, responder sends
70
+ * FINISH{mac_F}, which commits to both earlier confirmation MACs.
71
+ * Initiator returns only after verifying mac_F.
72
+ *
73
+ * The room-selected ML-KEM suite is mandatory: this function constructs that
74
+ * exact backend itself and fails before sending if its WASM exports are
75
+ * missing. There is deliberately no classical fallback or negotiation bit.
76
+ * The three chained MACs cover the full ordered HELLO transcript and dhPubR
77
+ * under distinct role domains; mac_I additionally covers dhPubI + mac_R, and
78
+ * mac_F covers both earlier proofs. A swapped cert/key, altered KEM field,
79
+ * ratchet-key tamper, or wrong PIN makes key confirmation fail (or is rejected
80
+ * as malformed).
81
+ * Persistence + gate-open happen ONLY in runHandshake, after this resolves, so
82
+ * a throw here leaves nothing persisted.
83
+ *
84
+ * NOTE on the R2 ordering: the initiator publishes its mac_I BEFORE separately
85
+ * checking mac_R so a wrong-root peer still receives a terminal proof instead
86
+ * of hanging forever. This is safe because mac_I itself authenticates the exact
87
+ * mac_R bytes the initiator received: changing mac_R makes the responder reject
88
+ * mac_I. A valid responder FINISH is therefore required before the initiator
89
+ * can return an established state.
90
+ */
91
+ export declare const performHandshakeCore: (transport: HandshakeTransport, params: HandshakeCoreParams, module: LibCrypto) => Promise<{
92
+ state: RatchetState;
93
+ secret: Uint8Array;
94
+ pqHealing: PqHealingBootstrap;
95
+ }>;
@@ -0,0 +1,70 @@
1
+ import type { RatchetState, RatchetHeader } from "../cryptography/ratchet";
2
+ import type { LibCrypto } from "../cryptography/libcrypto";
3
+ import type { PqMessageKeyContext } from "../cryptography/pqMessageKey";
4
+ /**
5
+ * Per-message key-cache key. Production passes the parsed epoch explicitly and
6
+ * gets an epoch-bound `(dhPub,N,epoch)` identity. Omitting the epoch preserves
7
+ * the legacy low-level `dhPub:N` identity for raw/bootstrap tests only; this is
8
+ * needed while those tests still round-trip keys through RatchetState.skipped,
9
+ * whose historical serializer understands exactly that two-field shape.
10
+ */
11
+ export declare const messageCacheKey: (dhPub: Uint8Array, N: number, pqEpoch?: bigint) => string;
12
+ /**
13
+ * Seal ONE chunk under an already-derived classical `messageKey` + `header`.
14
+ * With a PQ context, an owned copy of the classical key is consumed by the v4
15
+ * combiner and the resulting key is wiped after this one AEAD operation. The
16
+ * caller's classical key remains live across streamed chunks/retransmit rounds.
17
+ * The low-level context-free default emits bootstrap epoch zero and uses the
18
+ * raw classical key for backwards-compatible tests only.
19
+ *
20
+ * This is the streaming/reconcile-friendly primitive the live send path uses: it
21
+ * seals chunks one-at-a-time as they are read from IndexedDB, so a multi-GB
22
+ * message is never materialised in RAM. A retransmit re-seals the same plaintext
23
+ * under the SAME `messageKey` with a FRESH random nonce — cryptographically safe
24
+ * (distinct 96-bit random nonces under one key) and decryptable by the receiver's
25
+ * cached per-message key (a HIT on `(dhPub, N)`), so no frame-cache is needed.
26
+ */
27
+ export declare const sealChunk: (messageKey: Uint8Array, header: RatchetHeader, chunk: Uint8Array, merkleRoot: Uint8Array, module: LibCrypto, pqContext?: PqMessageKeyContext) => Uint8Array;
28
+ export interface DecryptedChunk {
29
+ /** The DECRYPTED_LEN plaintext `metadata ‖ receiptLeaf ‖ chunk` written by the C
30
+ * receive, or `null` when the chunk was dropped (AEAD or Merkle failure). */
31
+ decrypted: Uint8Array | null;
32
+ /** True iff C returned 0 — AEAD **and** Merkle both passed. When false the caller
33
+ * drops the chunk (but still persists the ratchet if `stateAdvanced`). */
34
+ ok: boolean;
35
+ /** True iff this chunk stepped the ratchet (first-arriving chunk of a message
36
+ * whose AEAD authenticated). The caller persists `state` when true — even if
37
+ * `ok` is false, since the DH step is real once the AEAD authenticates. */
38
+ stateAdvanced: boolean;
39
+ }
40
+ /**
41
+ * Resolve an authenticated, currently acceptable PQ epoch. Returning null
42
+ * rejects unknown/stale/future epochs before a Double-Ratchet clone is touched.
43
+ */
44
+ export type PqMessageKeyContextResolver = (epoch: bigint) => PqMessageKeyContext | null;
45
+ /**
46
+ * RECEIVE one chunk frame: derive the per-message key off the ratchet (in TS —
47
+ * the ratchet state is a TS object), then do ALL the crypto in ONE C call
48
+ * (`receiveWithKey` → `_receive_message_with_key`: decrypt + leaf-hash + Merkle +
49
+ * receipt, in place).
50
+ *
51
+ * The production `cache` is caller-owned and keyed by
52
+ * `messageCacheKey(dhPub,N,pqEpoch)`. It stores already-combined active receive
53
+ * keys separately from classical skipped keys. On a HIT the key is reused
54
+ * without touching the ratchet. On a MISS, the resolver must authorize the
55
+ * epoch before the classical key is derived on a clone and combined.
56
+ *
57
+ * Clone-rollback (MANDATORY — `ratchetDecrypt` mutates BEFORE the AEAD
58
+ * authenticates, so a replayed/old-chain header could otherwise fire a spurious
59
+ * DH-step and desync the session):
60
+ * 1. `clone = deserializeRatchet(serializeRatchet(state))`.
61
+ * 2. `messageKey = ratchetDecrypt(clone, header)` — mutates the CLONE only.
62
+ * 3. C decrypts+verifies. COMMIT (adopt the clone, cache the key) iff the AEAD
63
+ * authenticated (`code !== -2`); on `-2` DISCARD the clone — the live `state`
64
+ * is byte-for-byte untouched. `ok` (store-vs-drop) then follows `code === 0`.
65
+ *
66
+ * Cache lifecycle: the caller evicts a message's key (via `messageCacheKey`) when
67
+ * the message completes (all leaves present) or on a TTL, so a peer can't pin keys
68
+ * with never-completing messages.
69
+ */
70
+ export declare const decryptMessageChunk: (state: RatchetState, frame: Uint8Array, cache: Map<string, Uint8Array>, merkleRoot: Uint8Array, module: LibCrypto, pqContextResolver?: PqMessageKeyContextResolver) => DecryptedChunk;
@@ -0,0 +1,12 @@
1
+ import type { Peer } from "../reducers/roomSlice";
2
+ import type { RoomPeer } from "../utils/interfaces";
3
+ export interface SelfPeerIdentity {
4
+ peerId: string;
5
+ publicKey: string;
6
+ }
7
+ /**
8
+ * Server `peers` messages are accepted-peer deltas, not room snapshots.
9
+ * Preserve prior members and upsert only canonical, non-conflicting additions.
10
+ * Peer removal belongs to explicit transport teardown/disconnect handling.
11
+ */
12
+ export declare const mergePeerRosterDelta: (current: readonly Peer[], delta: readonly RoomPeer[], self: SelfPeerIdentity) => Peer[];
@@ -0,0 +1,46 @@
1
+ import type { IRTCPeerConnection } from "../api/webrtc/interfaces";
2
+ import type { RatchetState } from "../cryptography/ratchet";
3
+ /** Persist the (unchanged) live ratchet plus one candidate edge checkpoint. */
4
+ export type PersistEdgeCheckpoint = (epc: IRTCPeerConnection, state: RatchetState, roomId: string, serializeCandidate: () => Uint8Array) => Promise<void>;
5
+ export interface PqOrchestratorHooks {
6
+ /** Dispatch one exact sealed control cell; false when the lane is not open. */
7
+ readonly sendControlFrame: (frame: Uint8Array) => boolean;
8
+ /** Fail/reconnect the authenticated edge (retry exhaustion, fork). */
9
+ readonly failEdge: (reason: Error) => void;
10
+ /** Injectable clock for deterministic tests. */
11
+ readonly now?: () => number;
12
+ /** Injectable persistence for fault-injection tests. */
13
+ readonly persistEdge?: PersistEdgeCheckpoint;
14
+ /** Tick cadence for the due/retry timer; 0 disables the interval (tests). */
15
+ readonly tickMs?: number;
16
+ }
17
+ /**
18
+ * True while new application sends must not derive a message key on this edge:
19
+ * either the runtime's machine is mid-exchange, or an inbound control frame is
20
+ * being processed and the local gate has already closed.
21
+ */
22
+ export declare const isPqApplicationTrafficBlocked: (epc: IRTCPeerConnection) => boolean;
23
+ /**
24
+ * Install the live orchestrator after a successful handshake has installed
25
+ * `epc.pqHealingState`. Idempotent per connection: a replacement handshake on
26
+ * the same connection object reuses the interval with the fresh runtime.
27
+ */
28
+ export declare const installPqHealingOrchestrator: (epc: IRTCPeerConnection, roomId: string, hooks: PqOrchestratorHooks) => void;
29
+ export declare const destroyPqHealingOrchestrator: (epc: IRTCPeerConnection) => void;
30
+ /**
31
+ * One due/retry pass. Retransmits the exact persisted control frame when its
32
+ * 5-second deadline passed (durably counting the attempt first), fails the
33
+ * edge after the 8-attempt budget, and starts a due local exchange only when
34
+ * the stable role owns the turn and the edge is at a quiescent boundary.
35
+ * Exported for deterministic tests; the installed interval calls it.
36
+ */
37
+ export declare const tickPqHealing: (epc: IRTCPeerConnection) => Promise<void>;
38
+ /**
39
+ * Route one authenticated-transport FRAME_TYPE_PQ_CONTROL cell. Closes the
40
+ * local application gate, drains queued inbound frames to the quiescent
41
+ * boundary, then runs the exact durable OFFER/ADVANCE/ACK ordering. An exact
42
+ * duplicate re-emits the exact persisted response without a write. Any
43
+ * authentication failure is a fork/replay outside the protocol and fails the
44
+ * authenticated edge.
45
+ */
46
+ export declare const handleInboundPqControlFrame: (epc: IRTCPeerConnection, roomId: string, frame: Uint8Array) => Promise<void>;
@@ -0,0 +1,82 @@
1
+ import { type PqHealingPhase, type PqHealingTurn } from "../cryptography/pqHealing";
2
+ import { type RoomPqMode } from "../roomPolicy";
3
+ import { type RatchetRootSuite } from "../utils/constants";
4
+ import type { LibCrypto } from "../cryptography/libcrypto";
5
+ import type { PqMessageKeyContext } from "../cryptography/pqMessageKey";
6
+ export declare const PQ_HEAL_AFTER_MESSAGES = 64;
7
+ export declare const PQ_HEAL_AFTER_MS: number;
8
+ export declare const PQ_HEAL_RETRY_MS = 5000;
9
+ export declare const PQ_HEAL_MAX_RETRIES = 8;
10
+ export type PqHealingOutboxKind = "offer" | "advance";
11
+ export interface PqHealingOutbox {
12
+ readonly kind: PqHealingOutboxKind;
13
+ readonly frame: Uint8Array;
14
+ readonly attempts: number;
15
+ readonly nextRetryAt: number;
16
+ }
17
+ export interface PqHealingControlResult {
18
+ /**
19
+ * Exact already-sealed cell to dispatch after the candidate checkpoint is
20
+ * durable. It may be an OFFER, ADVANCE, ACK, or cached duplicate response.
21
+ */
22
+ readonly dispatch: Uint8Array | null;
23
+ /**
24
+ * False only for an exact duplicate whose cached response can be resent
25
+ * without another persistence write.
26
+ */
27
+ readonly changed: boolean;
28
+ }
29
+ export interface SparsePqHealingOptions {
30
+ readonly module: LibCrypto;
31
+ readonly pqMode: RoomPqMode;
32
+ readonly rootSuite: RatchetRootSuite;
33
+ readonly binding: Uint8Array;
34
+ readonly rootKey: Uint8Array;
35
+ readonly nextOfferer: PqHealingTurn;
36
+ readonly amInitiator: boolean;
37
+ readonly now?: number;
38
+ }
39
+ export interface RestoreSparsePqHealingOptions {
40
+ readonly module: LibCrypto;
41
+ readonly pqMode: RoomPqMode;
42
+ readonly rootSuite: RatchetRootSuite;
43
+ readonly binding: Uint8Array;
44
+ readonly amInitiator: boolean;
45
+ }
46
+ export declare class SparsePqHealingState {
47
+ #private;
48
+ constructor(options: SparsePqHealingOptions);
49
+ get pqMode(): RoomPqMode;
50
+ get rootSuite(): RatchetRootSuite;
51
+ get epoch(): bigint;
52
+ get phase(): PqHealingPhase;
53
+ get trafficBlocked(): boolean;
54
+ get nextOfferer(): PqHealingTurn;
55
+ get activeReceiveKeys(): Map<string, Uint8Array>;
56
+ get messagesSinceHealing(): number;
57
+ get lastHealedAt(): number;
58
+ currentMessageContext(): PqMessageKeyContext;
59
+ resolveMessageContext(epoch: bigint): PqMessageKeyContext | null;
60
+ noteApplicationMessage(): void;
61
+ healingDue(now?: number): boolean;
62
+ copyPendingFrame(): Uint8Array | null;
63
+ get pendingRetryAt(): number | null;
64
+ get pendingAttempts(): number;
65
+ markPendingDispatched(now?: number): void;
66
+ prepareHealingOffer(now?: number): Promise<Uint8Array>;
67
+ acceptControlFrame(frame: Uint8Array, now?: number): Promise<PqHealingControlResult>;
68
+ clone(): SparsePqHealingState;
69
+ adopt(next: SparsePqHealingState): void;
70
+ serialize(activeReceiveKeys?: ReadonlyMap<string, Uint8Array>): Uint8Array;
71
+ /**
72
+ * Read the public edge binding out of a checkpoint without instantiating the
73
+ * runtime. A store-free consumer that keeps the whole checkpoint under
74
+ * authenticated encryption (rather than a separate binding) uses this to
75
+ * supply the expected binding to `restore`. The binding layout is fixed:
76
+ * magic(8) | version(1) | suite(1) | direction(1) | reserved(1) |
77
+ * lastHealedAt(8) | messagesSinceHealing(8) | binding(32).
78
+ */
79
+ static readCheckpointBinding(bytes: Uint8Array): Uint8Array;
80
+ static restore(bytes: Uint8Array, options: RestoreSparsePqHealingOptions): SparsePqHealingState;
81
+ destroy(): void;
82
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Opaque ownership token for one concrete room/peer transport attempt.
3
+ *
4
+ * A reconnect replaces the gate at the same map key. Async completion from the
5
+ * old transport must present its old lease and is then ignored instead of
6
+ * opening/rejecting the replacement transport's promise.
7
+ */
8
+ export interface RatchetGateLease {
9
+ readonly token: symbol;
10
+ }
11
+ export declare const getRatchetGate: (roomId: string, peerId: string) => Promise<void>;
12
+ /** Claim the gate already created for the current transport attempt. */
13
+ export declare const claimRatchetGate: (roomId: string, peerId: string) => RatchetGateLease;
14
+ export declare const isCurrentRatchetGateLease: (roomId: string, peerId: string, lease: RatchetGateLease) => boolean;
15
+ export declare const isRatchetGateOpen: (roomId: string, peerId: string, lease?: RatchetGateLease) => boolean;
16
+ export declare const openRatchetGate: (roomId: string, peerId: string, lease: RatchetGateLease) => boolean;
17
+ export declare const rejectRatchetGate: (roomId: string, peerId: string, err: unknown, lease?: RatchetGateLease) => boolean;
18
+ export declare const resetRatchetGate: (roomId: string, peerId: string, reason?: unknown) => RatchetGateLease;
@@ -0,0 +1,53 @@
1
+ import type { LibCrypto } from "../cryptography/libcrypto";
2
+ import type { IRTCPeerConnection } from "../api/webrtc/interfaces";
3
+ import type { PqMessageKeyContext } from "../cryptography/pqMessageKey";
4
+ import type { RatchetHeader, RatchetState } from "../cryptography/ratchet";
5
+ import type { DecryptedChunk } from "./messageChunkCrypto";
6
+ /**
7
+ * Claim the stable persistence edge before a replacement handshake writes its
8
+ * seed. The synchronous claim invalidates queued mutations from the old PC;
9
+ * the edge-wide write lock ensures any already-running old write finishes
10
+ * before the replacement seed is written last.
11
+ */
12
+ export declare const claimRatchetPersistence: (epc: IRTCPeerConnection, roomId: string) => void;
13
+ export declare const persistRatchetState: (state: RatchetState, roomId: string, peerPublicKey: string, peerId: string, edgeCryptoState?: Uint8Array | null) => Promise<void>;
14
+ export declare const persistClaimedRatchetState: (epc: IRTCPeerConnection, state: RatchetState, roomId: string, edgeCryptoStateOverride?: () => Uint8Array) => Promise<void>;
15
+ export type PersistInitialRatchetState = (state: RatchetState, roomId: string, peerPublicKey: string, peerId: string, edgeCryptoState?: Uint8Array | null) => Promise<void>;
16
+ export type RollbackInitialRatchetState = (roomId: string, peerPublicKey: string) => Promise<void>;
17
+ /**
18
+ * Atomically order the initial durable seed and its synchronous gate/RAM
19
+ * activation under the stable-edge lock. If close/replacement settles the gate
20
+ * while the worker write is in flight, delete that just-written seed before a
21
+ * replacement is allowed to write. This prevents a never-established root from
22
+ * being restored later.
23
+ */
24
+ export declare const persistAndActivateClaimedRatchetState: (epc: IRTCPeerConnection, state: RatchetState, roomId: string, activate: () => void, persist?: PersistInitialRatchetState, rollback?: RollbackInitialRatchetState) => Promise<void>;
25
+ export type PersistRatchetState = typeof persistRatchetState;
26
+ export declare const withEdgeCryptoMutationLock: <T>(epc: IRTCPeerConnection, mutation: () => Promise<T>) => Promise<T>;
27
+ /**
28
+ * Advance the sending chain exactly once, durably, before its key/header can be
29
+ * used to build a frame. The returned message key remains caller-owned, as is
30
+ * the returned OWNED copy of the current PQ message context (null only on the
31
+ * bootstrap/test path with no installed PQ runtime). Capturing the context and
32
+ * counting the application message happen inside the same edge transaction as
33
+ * the ratchet step, so a concurrent PQ healing transition cannot interleave.
34
+ */
35
+ export declare const ratchetEncryptDurably: (epc: IRTCPeerConnection, roomId: string, module: LibCrypto, persist?: PersistRatchetState) => Promise<{
36
+ messageKey: Uint8Array;
37
+ header: RatchetHeader;
38
+ pqContext: PqMessageKeyContext | null;
39
+ }>;
40
+ /**
41
+ * Decrypt one inbound chunk with a staged state/cache. An authenticated ratchet
42
+ * advance becomes visible only after its snapshot is durable; persistence
43
+ * failure rejects without advancing live state, caching a key, or exposing the
44
+ * decrypted bytes. Cache hits do not advance or persist the ratchet.
45
+ */
46
+ export declare const decryptMessageChunkDurably: (epc: IRTCPeerConnection, roomId: string, frame: Uint8Array, cache: Map<string, Uint8Array>, merkleRoot: Uint8Array, module: LibCrypto, persist?: PersistRatchetState) => Promise<DecryptedChunk>;
47
+ /**
48
+ * Durably retire the active receive key after the atomic chunk manifest reports
49
+ * completion, then erase the RAM cache copy. If persistence fails, both copies
50
+ * remain usable for an idempotent retransmit/retry rather than diverging.
51
+ */
52
+ export declare const forgetReceiveMessageKeyDurably: (epc: IRTCPeerConnection, roomId: string, cache: Map<string, Uint8Array>, cacheKey: string, persist?: PersistRatchetState) => Promise<void>;
53
+ export declare const persistRatchetSession: (epc: IRTCPeerConnection, roomId: string) => Promise<void>;
@@ -0,0 +1,18 @@
1
+ interface ReceiptSendChannel {
2
+ readonly readyState: string;
3
+ readonly bufferedAmount: number;
4
+ send(data: ArrayBuffer): void;
5
+ }
6
+ /** Encode a chunk token or terminal message hash as one canonical wire frame. */
7
+ export declare const encodeReceiptFrame: (token: Uint8Array) => Uint8Array;
8
+ /** Return the zero-copy token only for the exact tagged receipt geometry. */
9
+ export declare const decodeReceiptFrame: (frame: Uint8Array) => Uint8Array | undefined;
10
+ export declare const sendReceiptFrame: (channel: Pick<ReceiptSendChannel, "readyState" | "send">, token: Uint8Array) => boolean;
11
+ export declare const RECEIPT_REPLAY_BATCH_SIZE = 32;
12
+ export declare const RECEIPT_REPLAY_PAUSE_MS = 10;
13
+ /**
14
+ * Replay a reconnect have-set without filling SCTP or monopolising the event
15
+ * loop. The caller may then send the terminal receipt on the same channel.
16
+ */
17
+ export declare const sendReceiptFramesPaced: (channel: ReceiptSendChannel, tokens: Iterable<Uint8Array>) => Promise<boolean>;
18
+ export {};
@@ -0,0 +1,24 @@
1
+ import { forgetReceiveMessageKeyDurably } from "./ratchetPersist";
2
+ import type { IRTCPeerConnection } from "../api/webrtc/interfaces";
3
+ import type { MessageData } from "../db/types";
4
+ type ForgetReceiveMessageKey = typeof forgetReceiveMessageKeyDurably;
5
+ type ReceiveProgress = Pick<MessageData, "savedSize" | "totalSize">;
6
+ /**
7
+ * Bind the authenticated transfer root to the receive-ratchet key used by all
8
+ * of its cells. Completion of the real bytes does not end this lifetime: valid
9
+ * padded/cover cells may still be draining from an unordered data channel.
10
+ */
11
+ export declare const bindReceiveMessageKey: (epc: IRTCPeerConnection, merkleRootHex: string, cacheKey: string) => void;
12
+ /**
13
+ * Durably retire the key mapped to one transfer. The root binding is removed
14
+ * only after persistence succeeds, so failure leaves an idempotent retry path.
15
+ */
16
+ export declare const forgetMappedReceiveMessageKey: (epc: IRTCPeerConnection, roomId: string, merkleRootHex: string, forget?: ForgetReceiveMessageKey) => Promise<boolean>;
17
+ /**
18
+ * A normal channel close retires a receive key only after its queue drained and
19
+ * the durable manifest confirms all real bytes. Incomplete transfers retain
20
+ * their key so a failed transport can resume; explicit cancel uses the
21
+ * unconditional helper above.
22
+ */
23
+ export declare const forgetCompletedReceiveMessageKey: (epc: IRTCPeerConnection, roomId: string, merkleRootHex: string, progress: ReceiveProgress | undefined, forget?: ForgetReceiveMessageKey) => Promise<boolean>;
24
+ export {};
@@ -0,0 +1,16 @@
1
+ export declare const markChunkAcked: (roomId: string, peerId: string, transferId: string, chunkIndex: number) => boolean;
2
+ export declare const getAckedChunks: (roomId: string, peerId: string, transferId: string) => Set<number>;
3
+ export declare const getAckedChunkCount: (roomId: string, peerId: string, transferId: string) => number;
4
+ export declare const markTransferComplete: (roomId: string, peerId: string, transferId: string) => boolean;
5
+ export interface PeerTransferOutcome {
6
+ readonly roomId: string;
7
+ readonly peerId: string;
8
+ readonly transferId: string;
9
+ readonly ackedChunks: ReadonlySet<number>;
10
+ readonly complete: boolean;
11
+ }
12
+ /** Accurate, defensive per-peer delivery state for UI/diagnostics consumers. */
13
+ export declare const getPeerTransferOutcome: (roomId: string, peerId: string, transferId: string) => PeerTransferOutcome;
14
+ export declare const isTransferComplete: (roomId: string, peerId: string, transferId: string) => boolean;
15
+ export declare const clearTransfer: (roomId: string, peerId: string, transferId: string) => void;
16
+ export declare const waitForCompletion: (roomId: string, peerId: string, transferId: string, timeoutMs: number, signal?: AbortSignal) => Promise<boolean>;
@@ -0,0 +1,42 @@
1
+ export interface TransferAbortHandle {
2
+ readonly transferId: string;
3
+ readonly signal: AbortSignal;
4
+ bindHash(hashHex: string): void;
5
+ bindMerkleRoot(merkleRootHex: string): void;
6
+ finish(): void;
7
+ }
8
+ /**
9
+ * Registers one logical outbound message. Its identifiers are bound as soon as
10
+ * streaming hashing/Merkle construction learns them, so cancellation remains
11
+ * message-scoped instead of broadcasting a window-global event.
12
+ */
13
+ export declare const createTransferId: () => string;
14
+ export declare const beginTransfer: (roomId: string, transferId?: string) => TransferAbortHandle;
15
+ /**
16
+ * Internal handoff from the public API to the async send pipeline. The public
17
+ * boundary registers first so `handle.cancel()` works even while WASM is still
18
+ * loading; the handler later claims that exact live controller.
19
+ */
20
+ export declare const claimTransfer: (roomId: string, transferId: string) => TransferAbortHandle;
21
+ export interface TransferSelector {
22
+ transferId?: string;
23
+ hashHex?: string;
24
+ merkleRootHex?: string;
25
+ }
26
+ export interface IncomingTransferProgress {
27
+ fromPeerId: string;
28
+ savedSize: number;
29
+ totalSize: number;
30
+ }
31
+ /**
32
+ * In immediate mode, closing only a message-scoped DataChannel while its
33
+ * authenticated peer connection remains alive is the remote CANCEL signal.
34
+ * Delete only an incomplete receive owned by that peer; completed messages and
35
+ * the sender's local self-copy must survive the ordinary terminal close.
36
+ */
37
+ export declare const isRemoteCancelClose: (progress: IncomingTransferProgress | undefined, remotePeerId: string, authenticatedTransportStillAlive: boolean) => boolean;
38
+ export declare const abortTransfer: (roomId: string, selector: TransferSelector) => number;
39
+ export declare const abortRoomTransfers: (roomId: string) => number;
40
+ export declare const abortAllTransfers: () => number;
41
+ export declare const throwIfTransferAborted: (signal?: AbortSignal) => void;
42
+ export declare const waitWithTransferAbort: <T>(pending: Promise<T>, signal?: AbortSignal) => Promise<T>;