@the9ines/bolt-core 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,81 @@
1
+ /**
2
+ * BTR session/transfer/chain state — §16.5 key material lifecycle.
3
+ *
4
+ * BtrEngine: session-level ratchet state.
5
+ * BtrTransferContext: per-transfer chain state for seal/open.
6
+ *
7
+ * Must match Rust bolt-btr/src/state.rs semantics exactly.
8
+ */
9
+ /**
10
+ * BTR engine — manages session-level ratchet state.
11
+ *
12
+ * Owns the session_root_key, ratchet generation counter, and replay guard.
13
+ * Create via `new BtrEngine(ephemeralSharedSecret)` after handshake completes.
14
+ */
15
+ export declare class BtrEngine {
16
+ private sessionRootKey;
17
+ private _ratchetGeneration;
18
+ private replayGuard;
19
+ constructor(ephemeralSharedSecret: Uint8Array);
20
+ /** Current ratchet generation (monotonically increasing per session). */
21
+ get ratchetGeneration(): number;
22
+ /** Current session root key (for testing/vector generation only). */
23
+ getSessionRootKey(): Uint8Array;
24
+ /**
25
+ * Prepare to send a FILE_OFFER — generate local ratchet keypair and
26
+ * perform DH ratchet step with remote peer's ratchet public key.
27
+ *
28
+ * Returns [BtrTransferContext, localRatchetPublicKey].
29
+ */
30
+ beginTransferSend(transferId: Uint8Array, remoteRatchetPub: Uint8Array): [BtrTransferContext, Uint8Array];
31
+ /**
32
+ * Accept a transfer — perform DH ratchet step with the sender's
33
+ * ratchet public key from their FILE_OFFER envelope.
34
+ *
35
+ * Returns [BtrTransferContext, localRatchetPublicKey].
36
+ */
37
+ beginTransferReceive(transferId: Uint8Array, remoteRatchetPub: Uint8Array): [BtrTransferContext, Uint8Array];
38
+ /** Check a received chunk's replay/ordering status. */
39
+ checkReplay(transferId: Uint8Array, generation: number, chainIndex: number): void;
40
+ /** End the current transfer's replay tracking. */
41
+ endTransfer(): void;
42
+ /** Cleanup on disconnect — zero ALL BTR state. */
43
+ cleanupDisconnect(): void;
44
+ }
45
+ /**
46
+ * BTR transfer context — manages per-transfer chain state.
47
+ *
48
+ * Created by BtrEngine.beginTransferSend/Receive.
49
+ * Used for encrypting/decrypting chunks within a single transfer.
50
+ */
51
+ export declare class BtrTransferContext {
52
+ private _transferId;
53
+ private _generation;
54
+ private _chainKey;
55
+ private _chainIndex;
56
+ constructor(transferId: Uint8Array, generation: number, chainKey: Uint8Array);
57
+ get transferId(): Uint8Array;
58
+ get generation(): number;
59
+ get chainIndex(): number;
60
+ /** Current chain key (for testing only). */
61
+ getChainKey(): Uint8Array;
62
+ /**
63
+ * Encrypt a chunk at the current chain position.
64
+ *
65
+ * Advances the chain: derives message_key and next_chain_key,
66
+ * encrypts plaintext via NaCl secretbox.
67
+ *
68
+ * Returns [chainIndex, sealedBytes].
69
+ */
70
+ sealChunk(plaintext: Uint8Array): [number, Uint8Array];
71
+ /**
72
+ * Decrypt a chunk at the expected chain position.
73
+ *
74
+ * Same chain advance as sealChunk — both peers derive identical keys.
75
+ */
76
+ openChunk(expectedChainIndex: number, sealed: Uint8Array): Uint8Array;
77
+ /** Cleanup on transfer complete (FILE_FINISH). */
78
+ cleanupComplete(): void;
79
+ /** Cleanup on transfer cancel (CANCEL). */
80
+ cleanupCancel(): void;
81
+ }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * BTR session/transfer/chain state — §16.5 key material lifecycle.
3
+ *
4
+ * BtrEngine: session-level ratchet state.
5
+ * BtrTransferContext: per-transfer chain state for seal/open.
6
+ *
7
+ * Must match Rust bolt-btr/src/state.rs semantics exactly.
8
+ */
9
+ import { deriveSessionRoot, deriveTransferRoot, chainAdvance } from './key-schedule.js';
10
+ import { deriveRatchetedSessionRoot, generateRatchetKeypair, scalarMult } from './ratchet.js';
11
+ import { btrSeal, btrOpen } from './encrypt.js';
12
+ import { ratchetChainError } from './errors.js';
13
+ import { ReplayGuard } from './replay.js';
14
+ /**
15
+ * BTR engine — manages session-level ratchet state.
16
+ *
17
+ * Owns the session_root_key, ratchet generation counter, and replay guard.
18
+ * Create via `new BtrEngine(ephemeralSharedSecret)` after handshake completes.
19
+ */
20
+ export class BtrEngine {
21
+ constructor(ephemeralSharedSecret) {
22
+ this.sessionRootKey = deriveSessionRoot(ephemeralSharedSecret);
23
+ this._ratchetGeneration = 0;
24
+ this.replayGuard = new ReplayGuard();
25
+ }
26
+ /** Current ratchet generation (monotonically increasing per session). */
27
+ get ratchetGeneration() {
28
+ return this._ratchetGeneration;
29
+ }
30
+ /** Current session root key (for testing/vector generation only). */
31
+ getSessionRootKey() {
32
+ return this.sessionRootKey;
33
+ }
34
+ /**
35
+ * Prepare to send a FILE_OFFER — generate local ratchet keypair and
36
+ * perform DH ratchet step with remote peer's ratchet public key.
37
+ *
38
+ * Returns [BtrTransferContext, localRatchetPublicKey].
39
+ */
40
+ beginTransferSend(transferId, remoteRatchetPub) {
41
+ const localKp = generateRatchetKeypair();
42
+ const localPub = localKp.publicKey;
43
+ // DH ratchet step
44
+ const dhOutput = scalarMult(localKp.secretKey, remoteRatchetPub);
45
+ const newSrk = deriveRatchetedSessionRoot(this.sessionRootKey, dhOutput);
46
+ // Update session state
47
+ this.sessionRootKey = newSrk;
48
+ this._ratchetGeneration += 1;
49
+ // Derive transfer root
50
+ const transferRoot = deriveTransferRoot(this.sessionRootKey, transferId);
51
+ // Set up replay guard for this transfer
52
+ this.replayGuard.beginTransfer(transferId, this._ratchetGeneration);
53
+ const ctx = new BtrTransferContext(new Uint8Array(transferId), this._ratchetGeneration, transferRoot);
54
+ return [ctx, localPub];
55
+ }
56
+ /**
57
+ * Accept a transfer — perform DH ratchet step with the sender's
58
+ * ratchet public key from their FILE_OFFER envelope.
59
+ *
60
+ * Returns [BtrTransferContext, localRatchetPublicKey].
61
+ */
62
+ beginTransferReceive(transferId, remoteRatchetPub) {
63
+ return this.beginTransferSend(transferId, remoteRatchetPub);
64
+ }
65
+ /** Check a received chunk's replay/ordering status. */
66
+ checkReplay(transferId, generation, chainIndex) {
67
+ this.replayGuard.check(transferId, generation, chainIndex);
68
+ }
69
+ /** End the current transfer's replay tracking. */
70
+ endTransfer() {
71
+ this.replayGuard.endTransfer();
72
+ }
73
+ /** Cleanup on disconnect — zero ALL BTR state. */
74
+ cleanupDisconnect() {
75
+ this.sessionRootKey.fill(0);
76
+ this._ratchetGeneration = 0;
77
+ this.replayGuard.reset();
78
+ }
79
+ }
80
+ /**
81
+ * BTR transfer context — manages per-transfer chain state.
82
+ *
83
+ * Created by BtrEngine.beginTransferSend/Receive.
84
+ * Used for encrypting/decrypting chunks within a single transfer.
85
+ */
86
+ export class BtrTransferContext {
87
+ constructor(transferId, generation, chainKey) {
88
+ this._transferId = transferId;
89
+ this._generation = generation;
90
+ this._chainKey = chainKey;
91
+ this._chainIndex = 0;
92
+ }
93
+ get transferId() {
94
+ return this._transferId;
95
+ }
96
+ get generation() {
97
+ return this._generation;
98
+ }
99
+ get chainIndex() {
100
+ return this._chainIndex;
101
+ }
102
+ /** Current chain key (for testing only). */
103
+ getChainKey() {
104
+ return this._chainKey;
105
+ }
106
+ /**
107
+ * Encrypt a chunk at the current chain position.
108
+ *
109
+ * Advances the chain: derives message_key and next_chain_key,
110
+ * encrypts plaintext via NaCl secretbox.
111
+ *
112
+ * Returns [chainIndex, sealedBytes].
113
+ */
114
+ sealChunk(plaintext) {
115
+ const idx = this._chainIndex;
116
+ const { messageKey, nextChainKey } = chainAdvance(this._chainKey);
117
+ const sealed = btrSeal(messageKey, plaintext);
118
+ this._chainKey = nextChainKey;
119
+ this._chainIndex += 1;
120
+ return [idx, sealed];
121
+ }
122
+ /**
123
+ * Decrypt a chunk at the expected chain position.
124
+ *
125
+ * Same chain advance as sealChunk — both peers derive identical keys.
126
+ */
127
+ openChunk(expectedChainIndex, sealed) {
128
+ if (expectedChainIndex !== this._chainIndex) {
129
+ throw ratchetChainError(`chain_index mismatch: expected ${this._chainIndex}, got ${expectedChainIndex}`);
130
+ }
131
+ const { messageKey, nextChainKey } = chainAdvance(this._chainKey);
132
+ const plaintext = btrOpen(messageKey, sealed);
133
+ this._chainKey = nextChainKey;
134
+ this._chainIndex += 1;
135
+ return plaintext;
136
+ }
137
+ /** Cleanup on transfer complete (FILE_FINISH). */
138
+ cleanupComplete() {
139
+ this._chainKey.fill(0);
140
+ this._transferId.fill(0);
141
+ }
142
+ /** Cleanup on transfer cancel (CANCEL). */
143
+ cleanupCancel() {
144
+ this.cleanupComplete();
145
+ }
146
+ }
package/dist/crypto.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * Generate a fresh ephemeral X25519 keypair for a single connection.
3
3
  * Discard after session ends.
4
+ *
5
+ * RB3: Uses Rust/WASM when available, falls back to tweetnacl.
4
6
  */
5
7
  export declare function generateEphemeralKeyPair(): {
6
8
  publicKey: Uint8Array;
@@ -10,12 +12,8 @@ export declare function generateEphemeralKeyPair(): {
10
12
  * Seal a plaintext payload using NaCl box (XSalsa20-Poly1305).
11
13
  *
12
14
  * Wire format: base64(nonce || ciphertext)
13
- * This matches the exact format used by all current product repos.
14
15
  *
15
- * @param plaintext - Raw bytes to encrypt
16
- * @param remotePublicKey - Receiver's ephemeral public key (32 bytes)
17
- * @param senderSecretKey - Sender's ephemeral secret key (32 bytes)
18
- * @returns base64-encoded string of nonce + ciphertext
16
+ * RB3: Uses Rust/WASM when available, falls back to tweetnacl.
19
17
  */
20
18
  export declare function sealBoxPayload(plaintext: Uint8Array, remotePublicKey: Uint8Array, senderSecretKey: Uint8Array): string;
21
19
  /**
@@ -23,9 +21,6 @@ export declare function sealBoxPayload(plaintext: Uint8Array, remotePublicKey: U
23
21
  *
24
22
  * Expects wire format: base64(nonce || ciphertext)
25
23
  *
26
- * @param sealed - base64-encoded string from sealBoxPayload
27
- * @param senderPublicKey - Sender's ephemeral public key (32 bytes)
28
- * @param receiverSecretKey - Receiver's ephemeral secret key (32 bytes)
29
- * @returns Decrypted plaintext bytes
24
+ * RB3: Uses Rust/WASM when available, falls back to tweetnacl.
30
25
  */
31
26
  export declare function openBoxPayload(sealed: string, senderPublicKey: Uint8Array, receiverSecretKey: Uint8Array): Uint8Array;
package/dist/crypto.js CHANGED
@@ -2,25 +2,30 @@ import tweetnacl from 'tweetnacl';
2
2
  const { box, randomBytes } = tweetnacl;
3
3
  import { toBase64, fromBase64 } from './encoding.js';
4
4
  import { EncryptionError } from './errors.js';
5
+ import { getWasmCrypto } from './wasm-crypto.js';
5
6
  /**
6
7
  * Generate a fresh ephemeral X25519 keypair for a single connection.
7
8
  * Discard after session ends.
9
+ *
10
+ * RB3: Uses Rust/WASM when available, falls back to tweetnacl.
8
11
  */
9
12
  export function generateEphemeralKeyPair() {
13
+ const wasm = getWasmCrypto();
14
+ if (wasm)
15
+ return wasm.generateEphemeralKeyPair();
10
16
  return box.keyPair();
11
17
  }
12
18
  /**
13
19
  * Seal a plaintext payload using NaCl box (XSalsa20-Poly1305).
14
20
  *
15
21
  * Wire format: base64(nonce || ciphertext)
16
- * This matches the exact format used by all current product repos.
17
22
  *
18
- * @param plaintext - Raw bytes to encrypt
19
- * @param remotePublicKey - Receiver's ephemeral public key (32 bytes)
20
- * @param senderSecretKey - Sender's ephemeral secret key (32 bytes)
21
- * @returns base64-encoded string of nonce + ciphertext
23
+ * RB3: Uses Rust/WASM when available, falls back to tweetnacl.
22
24
  */
23
25
  export function sealBoxPayload(plaintext, remotePublicKey, senderSecretKey) {
26
+ const wasm = getWasmCrypto();
27
+ if (wasm)
28
+ return wasm.sealBoxPayload(plaintext, remotePublicKey, senderSecretKey);
24
29
  const nonce = randomBytes(box.nonceLength);
25
30
  const encrypted = box(plaintext, nonce, remotePublicKey, senderSecretKey);
26
31
  if (!encrypted)
@@ -35,12 +40,12 @@ export function sealBoxPayload(plaintext, remotePublicKey, senderSecretKey) {
35
40
  *
36
41
  * Expects wire format: base64(nonce || ciphertext)
37
42
  *
38
- * @param sealed - base64-encoded string from sealBoxPayload
39
- * @param senderPublicKey - Sender's ephemeral public key (32 bytes)
40
- * @param receiverSecretKey - Receiver's ephemeral secret key (32 bytes)
41
- * @returns Decrypted plaintext bytes
43
+ * RB3: Uses Rust/WASM when available, falls back to tweetnacl.
42
44
  */
43
45
  export function openBoxPayload(sealed, senderPublicKey, receiverSecretKey) {
46
+ const wasm = getWasmCrypto();
47
+ if (wasm)
48
+ return wasm.openBoxPayload(sealed, senderPublicKey, receiverSecretKey);
44
49
  const data = fromBase64(sealed);
45
50
  if (data.length < box.nonceLength) {
46
51
  throw new EncryptionError('Sealed payload too short');
package/dist/errors.d.ts CHANGED
@@ -15,11 +15,11 @@ export declare class IntegrityError extends BoltError {
15
15
  constructor(message?: string);
16
16
  }
17
17
  /**
18
- * Canonical wire error code registry — 22 codes (11 PROTOCOL + 11 ENFORCEMENT).
18
+ * Canonical wire error code registry — 26 codes (11 PROTOCOL + 11 ENFORCEMENT + 4 BTR).
19
19
  * Every error frame sent on the wire MUST use a code from this array.
20
20
  * Implementations MUST reject inbound error frames carrying codes not listed here.
21
21
  */
22
- export declare const WIRE_ERROR_CODES: readonly ["VERSION_MISMATCH", "ENCRYPTION_FAILED", "INTEGRITY_FAILED", "REPLAY_DETECTED", "TRANSFER_FAILED", "LIMIT_EXCEEDED", "CONNECTION_LOST", "PEER_NOT_FOUND", "ALREADY_CONNECTED", "INVALID_STATE", "KEY_MISMATCH", "DUPLICATE_HELLO", "ENVELOPE_REQUIRED", "ENVELOPE_UNNEGOTIATED", "ENVELOPE_DECRYPT_FAIL", "ENVELOPE_INVALID", "HELLO_PARSE_ERROR", "HELLO_DECRYPT_FAIL", "HELLO_SCHEMA_ERROR", "INVALID_MESSAGE", "UNKNOWN_MESSAGE_TYPE", "PROTOCOL_VIOLATION"];
22
+ export declare const WIRE_ERROR_CODES: readonly ["VERSION_MISMATCH", "ENCRYPTION_FAILED", "INTEGRITY_FAILED", "REPLAY_DETECTED", "TRANSFER_FAILED", "LIMIT_EXCEEDED", "CONNECTION_LOST", "PEER_NOT_FOUND", "ALREADY_CONNECTED", "INVALID_STATE", "KEY_MISMATCH", "DUPLICATE_HELLO", "ENVELOPE_REQUIRED", "ENVELOPE_UNNEGOTIATED", "ENVELOPE_DECRYPT_FAIL", "ENVELOPE_INVALID", "HELLO_PARSE_ERROR", "HELLO_DECRYPT_FAIL", "HELLO_SCHEMA_ERROR", "INVALID_MESSAGE", "UNKNOWN_MESSAGE_TYPE", "PROTOCOL_VIOLATION", "RATCHET_STATE_ERROR", "RATCHET_CHAIN_ERROR", "RATCHET_DECRYPT_FAIL", "RATCHET_DOWNGRADE_REJECTED"];
23
23
  /** A valid wire error code string from PROTOCOL.md §10. */
24
24
  export type WireErrorCode = (typeof WIRE_ERROR_CODES)[number];
25
25
  /** Type guard: returns true if `x` is a canonical wire error code. */
package/dist/errors.js CHANGED
@@ -31,7 +31,7 @@ export class IntegrityError extends BoltError {
31
31
  }
32
32
  // ── Wire Error Code Registry (PROTOCOL.md §10, v0.1.3-spec) ──────────
33
33
  /**
34
- * Canonical wire error code registry — 22 codes (11 PROTOCOL + 11 ENFORCEMENT).
34
+ * Canonical wire error code registry — 26 codes (11 PROTOCOL + 11 ENFORCEMENT + 4 BTR).
35
35
  * Every error frame sent on the wire MUST use a code from this array.
36
36
  * Implementations MUST reject inbound error frames carrying codes not listed here.
37
37
  */
@@ -60,6 +60,11 @@ export const WIRE_ERROR_CODES = [
60
60
  'INVALID_MESSAGE',
61
61
  'UNKNOWN_MESSAGE_TYPE',
62
62
  'PROTOCOL_VIOLATION',
63
+ // BTR class (4) — §16.7
64
+ 'RATCHET_STATE_ERROR',
65
+ 'RATCHET_CHAIN_ERROR',
66
+ 'RATCHET_DECRYPT_FAIL',
67
+ 'RATCHET_DOWNGRADE_REJECTED',
63
68
  ];
64
69
  /** Type guard: returns true if `x` is a canonical wire error code. */
65
70
  export function isValidWireErrorCode(x) {
@@ -7,9 +7,7 @@ export interface IdentityKeyPair {
7
7
  /**
8
8
  * Generate a persistent identity keypair (X25519).
9
9
  *
10
- * Identity keys are long-lived and stored by the transport layer.
11
- * They MUST NOT be sent through the signaling server — identity
12
- * material travels only inside encrypted DataChannel messages (HELLO).
10
+ * RB3: Uses Rust/WASM when available, falls back to tweetnacl.
13
11
  */
14
12
  export declare function generateIdentityKeyPair(): IdentityKeyPair;
15
13
  /**
package/dist/identity.js CHANGED
@@ -1,14 +1,16 @@
1
1
  import tweetnacl from 'tweetnacl';
2
2
  const { box } = tweetnacl;
3
3
  import { BoltError } from './errors.js';
4
+ import { getWasmCrypto } from './wasm-crypto.js';
4
5
  /**
5
6
  * Generate a persistent identity keypair (X25519).
6
7
  *
7
- * Identity keys are long-lived and stored by the transport layer.
8
- * They MUST NOT be sent through the signaling server — identity
9
- * material travels only inside encrypted DataChannel messages (HELLO).
8
+ * RB3: Uses Rust/WASM when available, falls back to tweetnacl.
10
9
  */
11
10
  export function generateIdentityKeyPair() {
11
+ const wasm = getWasmCrypto();
12
+ if (wasm)
13
+ return wasm.generateIdentityKeyPair();
12
14
  return box.keyPair();
13
15
  }
14
16
  /**
package/dist/index.d.ts CHANGED
@@ -9,3 +9,6 @@ export type { IdentityKeyPair } from './identity.js';
9
9
  export { BoltError, EncryptionError, ConnectionError, TransferError, IntegrityError } from './errors.js';
10
10
  export { WIRE_ERROR_CODES, isValidWireErrorCode } from './errors.js';
11
11
  export type { WireErrorCode } from './errors.js';
12
+ export { initWasmCrypto, initWasmCryptoFromModule, getWasmCrypto, getWasmModule, createWasmBtrEngine, createWasmSendSession } from './wasm-crypto.js';
13
+ export type { WasmCryptoAdapter, WasmBtrEngineHandle, WasmBtrTransferCtxHandle, WasmSendSessionHandle } from './wasm-crypto.js';
14
+ export * from './btr/index.js';
package/dist/index.js CHANGED
@@ -16,3 +16,7 @@ export { generateIdentityKeyPair, KeyMismatchError } from './identity.js';
16
16
  export { BoltError, EncryptionError, ConnectionError, TransferError, IntegrityError } from './errors.js';
17
17
  // Wire error code registry (PROTOCOL.md §10)
18
18
  export { WIRE_ERROR_CODES, isValidWireErrorCode } from './errors.js';
19
+ // WASM protocol adapter (RUSTIFY-BROWSER-CORE-1 RB3+RB4)
20
+ export { initWasmCrypto, initWasmCryptoFromModule, getWasmCrypto, getWasmModule, createWasmBtrEngine, createWasmSendSession } from './wasm-crypto.js';
21
+ // Bolt Transfer Ratchet (BTR) — §16
22
+ export * from './btr/index.js';
package/dist/sas.d.ts CHANGED
@@ -10,4 +10,10 @@
10
10
  * @param ephemeralB - Raw 32-byte ephemeral public key of peer B
11
11
  * @returns 6-character uppercase hex string (24 bits of entropy)
12
12
  */
13
+ /**
14
+ * Compute a 6-character SAS (Short Authentication String) per PROTOCOL.md.
15
+ *
16
+ * RB3: Uses Rust/WASM (sync) when available, falls back to TS Web Crypto (async).
17
+ * Return type remains Promise<string> for backward compatibility.
18
+ */
13
19
  export declare function computeSas(identityA: Uint8Array, identityB: Uint8Array, ephemeralA: Uint8Array, ephemeralB: Uint8Array): Promise<string>;
package/dist/sas.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { sha256, bufferToHex } from './hash.js';
2
2
  import { PUBLIC_KEY_LENGTH, SAS_LENGTH } from './constants.js';
3
+ import { getWasmCrypto } from './wasm-crypto.js';
3
4
  // CANONICAL: computeSas() is the ONLY SAS implementation in the Bolt ecosystem.
4
5
  // SAS verification is not yet surfaced in products. No SAS logic may exist in
5
6
  // transport or product packages. See scripts/verify-no-shadow-sas.sh.
@@ -35,7 +36,19 @@ function sort32(a, b) {
35
36
  * @param ephemeralB - Raw 32-byte ephemeral public key of peer B
36
37
  * @returns 6-character uppercase hex string (24 bits of entropy)
37
38
  */
39
+ /**
40
+ * Compute a 6-character SAS (Short Authentication String) per PROTOCOL.md.
41
+ *
42
+ * RB3: Uses Rust/WASM (sync) when available, falls back to TS Web Crypto (async).
43
+ * Return type remains Promise<string> for backward compatibility.
44
+ */
38
45
  export async function computeSas(identityA, identityB, ephemeralA, ephemeralB) {
46
+ // RB3: WASM path (sync — Rust SHA-256, no Web Crypto)
47
+ const wasm = getWasmCrypto();
48
+ if (wasm) {
49
+ return wasm.computeSas(identityA, identityB, ephemeralA, ephemeralB);
50
+ }
51
+ // TS fallback (async — Web Crypto digest)
39
52
  if (identityA.length !== PUBLIC_KEY_LENGTH || identityB.length !== PUBLIC_KEY_LENGTH) {
40
53
  throw new Error(`Identity keys must be ${PUBLIC_KEY_LENGTH} bytes`);
41
54
  }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * WASM-backed protocol adapter (RUSTIFY-BROWSER-CORE-1 RB3+RB4).
3
+ *
4
+ * RB3: crypto/session/SAS functions backed by Rust/WASM.
5
+ * RB4: BTR state (BtrEngine, BtrTransferCtx) + transfer state (SendSession)
6
+ * backed by Rust/WASM opaque handles.
7
+ *
8
+ * TS tweetnacl/BTR implementations remain as fallback (PM-RB-03: condition-gated).
9
+ */
10
+ export interface WasmCryptoAdapter {
11
+ generateEphemeralKeyPair(): {
12
+ publicKey: Uint8Array;
13
+ secretKey: Uint8Array;
14
+ };
15
+ generateIdentityKeyPair(): {
16
+ publicKey: Uint8Array;
17
+ secretKey: Uint8Array;
18
+ };
19
+ sealBoxPayload(plaintext: Uint8Array, remotePublicKey: Uint8Array, senderSecretKey: Uint8Array): string;
20
+ openBoxPayload(sealed: string, senderPublicKey: Uint8Array, receiverSecretKey: Uint8Array): Uint8Array;
21
+ computeSas(identityA: Uint8Array, identityB: Uint8Array, ephemeralA: Uint8Array, ephemeralB: Uint8Array): string;
22
+ generateSecurePeerCode(): string;
23
+ isValidPeerCode(code: string): boolean;
24
+ sha256Hex(data: Uint8Array): string;
25
+ }
26
+ /**
27
+ * Get the WASM crypto adapter, or null if not initialized or init failed.
28
+ * Callers should fall back to TS crypto if this returns null.
29
+ */
30
+ export declare function getWasmCrypto(): WasmCryptoAdapter | null;
31
+ /**
32
+ * Initialize WASM crypto from a pre-loaded WASM module.
33
+ *
34
+ * BR2: Accepts an already-loaded+initialized WASM module (provided by
35
+ * transport-web's initProtocolWasm()). This avoids bare module specifier
36
+ * issues — the loader lives in transport-web where the artifact is embedded.
37
+ *
38
+ * PM-RB-03: TS fallback remains operational if this is never called.
39
+ *
40
+ * @param wasmModule - The loaded bolt-protocol-wasm module (with exported functions)
41
+ */
42
+ export declare function initWasmCryptoFromModule(wasmModule: any): boolean;
43
+ /**
44
+ * Initialize WASM crypto. Legacy entry point — attempts dynamic import.
45
+ * Prefer initProtocolWasm() from @the9ines/bolt-transport-web instead.
46
+ */
47
+ export declare function initWasmCrypto(): Promise<boolean>;
48
+ /**
49
+ * Get the raw WASM module for constructing BTR/transfer handles.
50
+ * Returns null if WASM not initialized.
51
+ */
52
+ export declare function getWasmModule(): any;
53
+ /**
54
+ * Opaque BTR engine handle. Rust owns all key material.
55
+ * Wraps WasmBtrEngine from bolt-protocol-wasm.
56
+ *
57
+ * Usage:
58
+ * const engine = createWasmBtrEngine(sharedSecret);
59
+ * const ctx = engine.beginTransferSend(transferId, remoteRatchetPub);
60
+ * const { chainIndex, sealed } = ctx.sealChunk(plaintext);
61
+ * engine.free(); // zeroize when done
62
+ */
63
+ export interface WasmBtrEngineHandle {
64
+ beginTransferSend(transferId: Uint8Array, remoteRatchetPub: Uint8Array): WasmBtrTransferCtxHandle;
65
+ beginTransferReceive(transferId: Uint8Array, remoteRatchetPub: Uint8Array): WasmBtrTransferCtxHandle;
66
+ ratchetGeneration(): number;
67
+ endTransfer(): void;
68
+ cleanupDisconnect(): void;
69
+ free(): void;
70
+ }
71
+ export interface WasmBtrTransferCtxHandle {
72
+ sealChunk(plaintext: Uint8Array): {
73
+ chainIndex: number;
74
+ sealed: Uint8Array;
75
+ };
76
+ openChunk(expectedIndex: number, sealed: Uint8Array): Uint8Array;
77
+ chainIndex(): number;
78
+ generation(): number;
79
+ transferId(): Uint8Array;
80
+ localRatchetPub(): Uint8Array;
81
+ cleanupComplete(): void;
82
+ cleanupCancel(): void;
83
+ free(): void;
84
+ }
85
+ export interface WasmSendSessionHandle {
86
+ beginSend(transferId: string, payload: Uint8Array, filename: string, fileHash?: string): {
87
+ transferId: string;
88
+ filename: string;
89
+ size: number;
90
+ totalChunks: number;
91
+ chunkSize: number;
92
+ fileHash?: string;
93
+ };
94
+ onAccept(transferId: string): void;
95
+ onCancel(transferId: string): void;
96
+ onPause(transferId: string): void;
97
+ onResume(transferId: string): void;
98
+ nextChunk(): {
99
+ transferId: string;
100
+ chunkIndex: number;
101
+ totalChunks: number;
102
+ data: Uint8Array;
103
+ } | null;
104
+ finish(): string;
105
+ state(): string;
106
+ isSendActive(): boolean;
107
+ free(): void;
108
+ }
109
+ /**
110
+ * Create a WASM-backed BTR engine. Returns null if WASM not available.
111
+ * Caller must call .free() when done to zeroize key material.
112
+ */
113
+ export declare function createWasmBtrEngine(sharedSecret: Uint8Array): WasmBtrEngineHandle | null;
114
+ /**
115
+ * Create a WASM-backed send session. Returns null if WASM not available.
116
+ * Rust owns transfer-state transitions. TS proposes events; Rust validates.
117
+ */
118
+ export declare function createWasmSendSession(): WasmSendSessionHandle | null;