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.
- package/LICENSE.md +202 -661
- package/README.md +736 -404
- package/THIRD_PARTY_NOTICES.md +458 -0
- package/docs/assets/p2party-cat.svg +1 -0
- package/docs/getting-started.md +203 -0
- package/docs/protocol-v4-security.md +158 -0
- package/docs/references.md +157 -0
- package/docs/session-api.md +457 -0
- package/docs/wire-format.md +103 -0
- package/examples/standalone-e2ee.ts +194 -0
- package/lib/api/signalingServerApi.d.ts +6 -7
- package/lib/api/webrtc/baseQuery.d.ts +1 -2
- package/lib/api/webrtc/disconnectFromAllRoomsQuery.d.ts +1 -2
- package/lib/api/webrtc/disconnectFromChannelLabelQuery.d.ts +1 -2
- package/lib/api/webrtc/disconnectFromPeerChannelLabelQuery.d.ts +1 -2
- package/lib/api/webrtc/disconnectFromPeerQuery.d.ts +3 -3
- package/lib/api/webrtc/disconnectFromRoomQuery.d.ts +1 -2
- package/lib/api/webrtc/disconnectQuery.d.ts +1 -2
- package/lib/api/webrtc/iceGeneration.d.ts +17 -0
- package/lib/api/webrtc/iceRepair.d.ts +10 -0
- package/lib/api/webrtc/index.d.ts +13 -13
- package/lib/api/webrtc/interfaces.d.ts +67 -11
- package/lib/api/webrtc/negotiationLock.d.ts +12 -0
- package/lib/api/webrtc/openChannelQuery.d.ts +1 -2
- package/lib/api/webrtc/pendingIceCandidates.d.ts +14 -0
- package/lib/api/webrtc/roomPeer.d.ts +11 -0
- package/lib/api/webrtc/sendMessageQuery.d.ts +7 -4
- package/lib/api/webrtc/setCandidateQuery.d.ts +1 -2
- package/lib/api/webrtc/setDescriptionQuery.d.ts +1 -2
- package/lib/cryptography/coverCell.d.ts +65 -0
- package/lib/cryptography/cpace.d.ts +41 -0
- package/lib/cryptography/ed25519.d.ts +0 -1
- package/lib/cryptography/hashStream.d.ts +15 -0
- package/lib/cryptography/hkdf.d.ts +3 -0
- package/lib/cryptography/identityCrossSig.d.ts +7 -0
- package/lib/cryptography/interfaces.d.ts +28 -13
- package/lib/cryptography/memory.d.ts +2 -4
- package/lib/cryptography/merkle.d.ts +0 -1
- package/lib/cryptography/mlkem.d.ts +117 -0
- package/lib/cryptography/mnemonic.d.ts +0 -1
- package/lib/cryptography/pqHealing.d.ts +268 -0
- package/lib/cryptography/pqHealingFrame.d.ts +39 -0
- package/lib/cryptography/pqMessageKey.d.ts +30 -0
- package/lib/cryptography/random.d.ts +22 -0
- package/lib/cryptography/ratchet.d.ts +146 -0
- package/lib/cryptography/testModule.d.ts +7 -0
- package/lib/cryptography/utils.d.ts +0 -1
- package/lib/cryptography/wasmLoader.d.ts +6 -1
- package/lib/cryptography/x25519.d.ts +15 -0
- package/lib/cryptography/x3dh.d.ts +9 -0
- package/lib/db/api.d.ts +54 -22
- package/lib/db/db.worker.d.ts +0 -1
- package/lib/db/ratchetWrap.d.ts +36 -0
- package/lib/db/src/getDB.d.ts +17 -4
- package/lib/db/types.d.ts +274 -32
- package/lib/db.worker.js +1 -2
- package/lib/handlers/chunkFrame.d.ts +13 -0
- package/lib/handlers/connectionSignal.d.ts +26 -0
- package/lib/handlers/coverEdge.d.ts +42 -0
- package/lib/handlers/coverRuntime.d.ts +91 -0
- package/lib/handlers/coverScheduler.d.ts +184 -0
- package/lib/handlers/coverTransfer.d.ts +46 -0
- package/lib/handlers/frameType.d.ts +10 -0
- package/lib/handlers/handleChallenge.d.ts +0 -1
- package/lib/handlers/handleConnectToPeer.d.ts +0 -1
- package/lib/handlers/handleHandshake.d.ts +74 -0
- package/lib/handlers/handleMessageQueueing.d.ts +53 -4
- package/lib/handlers/handleOpenChannel.d.ts +4 -2
- package/lib/handlers/handleQueuedIceCandidates.d.ts +0 -1
- package/lib/handlers/handleReadReceipt.d.ts +17 -4
- package/lib/handlers/handleReceiveMessage.d.ts +21 -3
- package/lib/handlers/handleSendMessage.d.ts +110 -2
- package/lib/handlers/handleWebSocketMessage.d.ts +1 -3
- package/lib/handlers/handshakeCore.d.ts +95 -0
- package/lib/handlers/messageChunkCrypto.d.ts +70 -0
- package/lib/handlers/peerRosterDelta.d.ts +12 -0
- package/lib/handlers/pqHealingOrchestrator.d.ts +46 -0
- package/lib/handlers/pqHealingRuntime.d.ts +82 -0
- package/lib/handlers/ratchetGate.d.ts +18 -0
- package/lib/handlers/ratchetPersist.d.ts +53 -0
- package/lib/handlers/receiptFrame.d.ts +18 -0
- package/lib/handlers/receiveMessageKeyLifetime.d.ts +24 -0
- package/lib/handlers/reconcile.d.ts +16 -0
- package/lib/handlers/transferAbort.d.ts +42 -0
- package/lib/index.d.ts +203 -46
- package/lib/index.js +1 -2
- package/lib/index.min.js +1 -2
- package/lib/index.mjs +1 -2
- package/lib/libcrypto.provenance.json +45 -0
- package/lib/libcrypto.wasm +0 -0
- package/lib/middleware/keyPairListenerMiddleware.d.ts +0 -1
- package/lib/middleware/roomListenerMiddleware.d.ts +0 -1
- package/lib/reducers/commonSlice.d.ts +0 -1
- package/lib/reducers/keyPairSlice.d.ts +0 -1
- package/lib/reducers/roomSlice.d.ts +65 -3
- package/lib/reducers/signalingServerSlice.d.ts +25 -4
- package/lib/roomInvite.d.ts +18 -0
- package/lib/roomPinAttempts.d.ts +12 -0
- package/lib/roomPinVault.d.ts +26 -0
- package/lib/roomPolicy.d.ts +60 -0
- package/lib/session.d.ts +110 -0
- package/lib/session.js +1 -0
- package/lib/session.mjs +1 -0
- package/lib/store.d.ts +40 -41
- package/lib/utils/channelLabel.d.ts +0 -1
- package/lib/utils/chunkBounds.d.ts +9 -0
- package/lib/utils/constants.d.ts +50 -6
- package/lib/utils/debug.d.ts +25 -0
- package/lib/utils/drainAndClose.d.ts +5 -0
- package/lib/utils/identityRole.d.ts +19 -0
- package/lib/utils/interfaces.d.ts +31 -43
- package/lib/utils/leafHash.d.ts +13 -0
- package/lib/utils/messageTypes.d.ts +0 -1
- package/lib/utils/metadata.d.ts +6 -1
- package/lib/utils/mutex.d.ts +24 -0
- package/lib/utils/protocolVersion.d.ts +5 -0
- package/lib/utils/receiptToken.d.ts +7 -0
- package/lib/utils/sendQueueKey.d.ts +2 -0
- package/lib/utils/signalingAuth.d.ts +8 -0
- package/lib/utils/signalingBounds.d.ts +9 -0
- package/lib/utils/splitToChunks.d.ts +7 -4
- package/lib/utils/uint8array.d.ts +1 -2
- package/lib/utils/waitForOpen.d.ts +3 -0
- package/lib/utils/zeroFree.d.ts +10 -0
- package/package.json +109 -49
- package/lib/api/signalingServerApi.d.ts.map +0 -1
- package/lib/api/webrtc/baseQuery.d.ts.map +0 -1
- package/lib/api/webrtc/disconnectFromAllRoomsQuery.d.ts.map +0 -1
- package/lib/api/webrtc/disconnectFromChannelLabelQuery.d.ts.map +0 -1
- package/lib/api/webrtc/disconnectFromPeerChannelLabelQuery.d.ts.map +0 -1
- package/lib/api/webrtc/disconnectFromPeerQuery.d.ts.map +0 -1
- package/lib/api/webrtc/disconnectFromRoomQuery.d.ts.map +0 -1
- package/lib/api/webrtc/disconnectQuery.d.ts.map +0 -1
- package/lib/api/webrtc/index.d.ts.map +0 -1
- package/lib/api/webrtc/interfaces.d.ts.map +0 -1
- package/lib/api/webrtc/openChannelQuery.d.ts.map +0 -1
- package/lib/api/webrtc/sendMessageQuery.d.ts.map +0 -1
- package/lib/api/webrtc/setCandidateQuery.d.ts.map +0 -1
- package/lib/api/webrtc/setDescriptionQuery.d.ts.map +0 -1
- package/lib/cryptography/chacha20poly1305.d.ts +0 -116
- package/lib/cryptography/chacha20poly1305.d.ts.map +0 -1
- package/lib/cryptography/ed25519.d.ts.map +0 -1
- package/lib/cryptography/interfaces.d.ts.map +0 -1
- package/lib/cryptography/memory.d.ts.map +0 -1
- package/lib/cryptography/merkle.d.ts.map +0 -1
- package/lib/cryptography/mnemonic.d.ts.map +0 -1
- package/lib/cryptography/utils.d.ts.map +0 -1
- package/lib/cryptography/wasmLoader.d.ts.map +0 -1
- package/lib/db/api.d.ts.map +0 -1
- package/lib/db/db.worker.d.ts.map +0 -1
- package/lib/db/src/getDB.d.ts.map +0 -1
- package/lib/db/types.d.ts.map +0 -1
- package/lib/db.worker.js.map +0 -1
- package/lib/handlers/handleChallenge.d.ts.map +0 -1
- package/lib/handlers/handleConnectToPeer.d.ts.map +0 -1
- package/lib/handlers/handleMessageQueueing.d.ts.map +0 -1
- package/lib/handlers/handleOpenChannel.d.ts.map +0 -1
- package/lib/handlers/handleQueuedIceCandidates.d.ts.map +0 -1
- package/lib/handlers/handleReadReceipt.d.ts.map +0 -1
- package/lib/handlers/handleReceiveMessage.d.ts.map +0 -1
- package/lib/handlers/handleSendMessage.d.ts.map +0 -1
- package/lib/handlers/handleWebSocketMessage.d.ts.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/index.min.js.map +0 -1
- package/lib/index.mjs.map +0 -1
- package/lib/middleware/keyPairListenerMiddleware.d.ts.map +0 -1
- package/lib/middleware/roomListenerMiddleware.d.ts.map +0 -1
- package/lib/reducers/commonSlice.d.ts.map +0 -1
- package/lib/reducers/keyPairSlice.d.ts.map +0 -1
- package/lib/reducers/roomSlice.d.ts.map +0 -1
- package/lib/reducers/signalingServerSlice.d.ts.map +0 -1
- package/lib/store.d.ts.map +0 -1
- package/lib/utils/allocators.d.ts +0 -22
- package/lib/utils/allocators.d.ts.map +0 -1
- package/lib/utils/channelLabel.d.ts.map +0 -1
- package/lib/utils/constants.d.ts.map +0 -1
- package/lib/utils/interfaces.d.ts.map +0 -1
- package/lib/utils/messageTypes.d.ts.map +0 -1
- package/lib/utils/metadata.d.ts.map +0 -1
- package/lib/utils/splitToChunks.d.ts.map +0 -1
- 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
|
|
5
|
-
|
|
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
|
-
|
|
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>;
|