react-native-ble-mesh 1.1.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +288 -172
  2. package/docs/IOS-BACKGROUND-BLE.md +231 -0
  3. package/docs/OPTIMIZATION.md +70 -0
  4. package/docs/SPEC-v2.1.md +308 -0
  5. package/package.json +1 -1
  6. package/src/MeshNetwork.js +659 -465
  7. package/src/constants/index.js +1 -0
  8. package/src/crypto/AutoCrypto.js +79 -0
  9. package/src/crypto/CryptoProvider.js +99 -0
  10. package/src/crypto/index.js +15 -63
  11. package/src/crypto/providers/ExpoCryptoProvider.js +125 -0
  12. package/src/crypto/providers/QuickCryptoProvider.js +134 -0
  13. package/src/crypto/providers/TweetNaClProvider.js +124 -0
  14. package/src/crypto/providers/index.js +11 -0
  15. package/src/errors/MeshError.js +2 -1
  16. package/src/expo/withBLEMesh.js +102 -0
  17. package/src/hooks/useMesh.js +30 -9
  18. package/src/hooks/useMessages.js +2 -0
  19. package/src/index.js +23 -8
  20. package/src/mesh/dedup/DedupManager.js +36 -10
  21. package/src/mesh/fragment/Assembler.js +5 -0
  22. package/src/mesh/index.js +1 -1
  23. package/src/mesh/monitor/ConnectionQuality.js +408 -0
  24. package/src/mesh/monitor/NetworkMonitor.js +327 -316
  25. package/src/mesh/monitor/index.js +7 -3
  26. package/src/mesh/peer/PeerManager.js +6 -1
  27. package/src/mesh/router/MessageRouter.js +26 -15
  28. package/src/mesh/router/RouteTable.js +7 -1
  29. package/src/mesh/store/StoreAndForwardManager.js +295 -297
  30. package/src/mesh/store/index.js +1 -1
  31. package/src/service/BatteryOptimizer.js +282 -278
  32. package/src/service/EmergencyManager.js +224 -214
  33. package/src/service/HandshakeManager.js +167 -13
  34. package/src/service/MeshService.js +72 -6
  35. package/src/service/SessionManager.js +77 -2
  36. package/src/service/audio/AudioManager.js +8 -2
  37. package/src/service/file/FileAssembler.js +106 -0
  38. package/src/service/file/FileChunker.js +79 -0
  39. package/src/service/file/FileManager.js +307 -0
  40. package/src/service/file/FileMessage.js +122 -0
  41. package/src/service/file/index.js +15 -0
  42. package/src/service/text/broadcast/BroadcastManager.js +16 -0
  43. package/src/transport/BLETransport.js +131 -9
  44. package/src/transport/MockTransport.js +1 -1
  45. package/src/transport/MultiTransport.js +305 -0
  46. package/src/transport/WiFiDirectTransport.js +295 -0
  47. package/src/transport/adapters/NodeBLEAdapter.js +34 -0
  48. package/src/transport/adapters/RNBLEAdapter.js +56 -1
  49. package/src/transport/index.js +6 -0
  50. package/src/utils/compression.js +291 -291
  51. package/src/crypto/aead.js +0 -189
  52. package/src/crypto/chacha20.js +0 -181
  53. package/src/crypto/hkdf.js +0 -187
  54. package/src/crypto/hmac.js +0 -143
  55. package/src/crypto/keys/KeyManager.js +0 -271
  56. package/src/crypto/keys/KeyPair.js +0 -216
  57. package/src/crypto/keys/SecureStorage.js +0 -219
  58. package/src/crypto/keys/index.js +0 -32
  59. package/src/crypto/noise/handshake.js +0 -410
  60. package/src/crypto/noise/index.js +0 -27
  61. package/src/crypto/noise/session.js +0 -253
  62. package/src/crypto/noise/state.js +0 -268
  63. package/src/crypto/poly1305.js +0 -113
  64. package/src/crypto/sha256.js +0 -240
  65. package/src/crypto/x25519.js +0 -154
@@ -1,189 +0,0 @@
1
- /**
2
- * @fileoverview ChaCha20-Poly1305 AEAD implementation (RFC 8439)
3
- * @module crypto/aead
4
- */
5
-
6
- 'use strict';
7
-
8
- const { chacha20 } = require('./chacha20');
9
- const { poly1305 } = require('./poly1305');
10
-
11
- /**
12
- * Generates Poly1305 key using ChaCha20 with counter=0
13
- * @param {Uint8Array} key - 32-byte encryption key
14
- * @param {Uint8Array} nonce - 12-byte nonce
15
- * @returns {Uint8Array} 32-byte Poly1305 key
16
- */
17
- function generatePolyKey(key, nonce) {
18
- const zeros = new Uint8Array(64);
19
- const block = chacha20(key, nonce, 0, zeros);
20
- return block.subarray(0, 32);
21
- }
22
-
23
- /**
24
- * Pads data to 16-byte boundary with zeros
25
- * @param {number} length - Current data length
26
- * @returns {Uint8Array} Padding bytes (0-15 bytes)
27
- */
28
- function pad16(length) {
29
- const remainder = length % 16;
30
- if (remainder === 0) {
31
- return new Uint8Array(0);
32
- }
33
- return new Uint8Array(16 - remainder);
34
- }
35
-
36
- /**
37
- * Encodes a 64-bit little-endian integer
38
- * @param {number} value - Value to encode
39
- * @returns {Uint8Array} 8-byte little-endian representation
40
- */
41
- function encode64LE(value) {
42
- const bytes = new Uint8Array(8);
43
- const view = new DataView(bytes.buffer);
44
- // JavaScript numbers are 64-bit floats, safe for values up to 2^53-1
45
- view.setUint32(0, value >>> 0, true);
46
- view.setUint32(4, Math.floor(value / 0x100000000) >>> 0, true);
47
- return bytes;
48
- }
49
-
50
- /**
51
- * Constructs Poly1305 MAC input per RFC 8439
52
- * @param {Uint8Array} aad - Additional authenticated data
53
- * @param {Uint8Array} ciphertext - Ciphertext
54
- * @returns {Uint8Array} MAC input
55
- */
56
- function constructMacData(aad, ciphertext) {
57
- const aadPad = pad16(aad.length);
58
- const ctPad = pad16(ciphertext.length);
59
- const aadLen = encode64LE(aad.length);
60
- const ctLen = encode64LE(ciphertext.length);
61
-
62
- const totalLen = aad.length + aadPad.length + ciphertext.length +
63
- ctPad.length + 8 + 8;
64
- const data = new Uint8Array(totalLen);
65
-
66
- let offset = 0;
67
- data.set(aad, offset);
68
- offset += aad.length;
69
- data.set(aadPad, offset);
70
- offset += aadPad.length;
71
- data.set(ciphertext, offset);
72
- offset += ciphertext.length;
73
- data.set(ctPad, offset);
74
- offset += ctPad.length;
75
- data.set(aadLen, offset);
76
- offset += 8;
77
- data.set(ctLen, offset);
78
-
79
- return data;
80
- }
81
-
82
- /**
83
- * Constant-time comparison of two byte arrays
84
- * @param {Uint8Array} a - First array
85
- * @param {Uint8Array} b - Second array
86
- * @returns {boolean} True if arrays are equal
87
- */
88
- function constantTimeEqual(a, b) {
89
- if (a.length !== b.length) {
90
- return false;
91
- }
92
- let diff = 0;
93
- for (let i = 0; i < a.length; i++) {
94
- diff |= a[i] ^ b[i];
95
- }
96
- return diff === 0;
97
- }
98
-
99
- /**
100
- * Encrypts and authenticates data using ChaCha20-Poly1305 AEAD
101
- * @param {Uint8Array} key - 32-byte encryption key
102
- * @param {Uint8Array} nonce - 12-byte nonce (must be unique per key)
103
- * @param {Uint8Array} plaintext - Data to encrypt
104
- * @param {Uint8Array} [aad] - Additional authenticated data (optional)
105
- * @returns {Uint8Array} Ciphertext with appended 16-byte authentication tag
106
- * @throws {Error} If key is not 32 bytes or nonce is not 12 bytes
107
- */
108
- function encrypt(key, nonce, plaintext, aad = new Uint8Array(0)) {
109
- if (!(key instanceof Uint8Array) || key.length !== 32) {
110
- throw new Error('Key must be 32 bytes');
111
- }
112
- if (!(nonce instanceof Uint8Array) || nonce.length !== 12) {
113
- throw new Error('Nonce must be 12 bytes');
114
- }
115
- if (!(plaintext instanceof Uint8Array)) {
116
- throw new Error('Plaintext must be a Uint8Array');
117
- }
118
- if (!(aad instanceof Uint8Array)) {
119
- throw new Error('AAD must be a Uint8Array');
120
- }
121
-
122
- // Generate Poly1305 key
123
- const polyKey = generatePolyKey(key, nonce);
124
-
125
- // Encrypt plaintext with counter starting at 1
126
- const ciphertext = chacha20(key, nonce, 1, plaintext);
127
-
128
- // Compute MAC
129
- const macData = constructMacData(aad, ciphertext);
130
- const tag = poly1305(polyKey, macData);
131
-
132
- // Concatenate ciphertext and tag
133
- const result = new Uint8Array(ciphertext.length + 16);
134
- result.set(ciphertext);
135
- result.set(tag, ciphertext.length);
136
-
137
- return result;
138
- }
139
-
140
- /**
141
- * Decrypts and verifies data using ChaCha20-Poly1305 AEAD
142
- * @param {Uint8Array} key - 32-byte encryption key
143
- * @param {Uint8Array} nonce - 12-byte nonce
144
- * @param {Uint8Array} ciphertext - Ciphertext with 16-byte authentication tag
145
- * @param {Uint8Array} [aad] - Additional authenticated data (optional)
146
- * @returns {Uint8Array|null} Decrypted plaintext, or null if authentication fails
147
- * @throws {Error} If key is not 32 bytes, nonce is not 12 bytes, or ciphertext too short
148
- */
149
- function decrypt(key, nonce, ciphertext, aad = new Uint8Array(0)) {
150
- if (!(key instanceof Uint8Array) || key.length !== 32) {
151
- throw new Error('Key must be 32 bytes');
152
- }
153
- if (!(nonce instanceof Uint8Array) || nonce.length !== 12) {
154
- throw new Error('Nonce must be 12 bytes');
155
- }
156
- if (!(ciphertext instanceof Uint8Array)) {
157
- throw new Error('Ciphertext must be a Uint8Array');
158
- }
159
- if (ciphertext.length < 16) {
160
- throw new Error('Ciphertext must be at least 16 bytes (tag size)');
161
- }
162
- if (!(aad instanceof Uint8Array)) {
163
- throw new Error('AAD must be a Uint8Array');
164
- }
165
-
166
- // Split ciphertext and tag
167
- const ct = ciphertext.subarray(0, ciphertext.length - 16);
168
- const receivedTag = ciphertext.subarray(ciphertext.length - 16);
169
-
170
- // Generate Poly1305 key
171
- const polyKey = generatePolyKey(key, nonce);
172
-
173
- // Compute expected MAC
174
- const macData = constructMacData(aad, ct);
175
- const expectedTag = poly1305(polyKey, macData);
176
-
177
- // Verify tag using constant-time comparison
178
- if (!constantTimeEqual(receivedTag, expectedTag)) {
179
- return null;
180
- }
181
-
182
- // Decrypt ciphertext with counter starting at 1
183
- return chacha20(key, nonce, 1, ct);
184
- }
185
-
186
- module.exports = {
187
- encrypt,
188
- decrypt
189
- };
@@ -1,181 +0,0 @@
1
- /**
2
- * @fileoverview ChaCha20 stream cipher implementation (RFC 8439)
3
- * @module crypto/chacha20
4
- */
5
-
6
- 'use strict';
7
-
8
- /**
9
- * ChaCha20 constants: "expand 32-byte k" in ASCII
10
- * @constant {Uint32Array}
11
- */
12
- const CONSTANTS = new Uint32Array([0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]);
13
-
14
- /**
15
- * Performs left rotation on a 32-bit unsigned integer
16
- * @param {number} v - Value to rotate
17
- * @param {number} c - Number of bits to rotate
18
- * @returns {number} Rotated value
19
- */
20
- function rotl32(v, c) {
21
- return ((v << c) | (v >>> (32 - c))) >>> 0;
22
- }
23
-
24
- /**
25
- * ChaCha20 quarter round operation
26
- * Modifies state array in place
27
- * @param {Uint32Array} state - 16-element state array
28
- * @param {number} a - Index a
29
- * @param {number} b - Index b
30
- * @param {number} c - Index c
31
- * @param {number} d - Index d
32
- */
33
- function quarterRound(state, a, b, c, d) {
34
- state[a] = (state[a] + state[b]) >>> 0;
35
- state[d] = rotl32(state[d] ^ state[a], 16);
36
-
37
- state[c] = (state[c] + state[d]) >>> 0;
38
- state[b] = rotl32(state[b] ^ state[c], 12);
39
-
40
- state[a] = (state[a] + state[b]) >>> 0;
41
- state[d] = rotl32(state[d] ^ state[a], 8);
42
-
43
- state[c] = (state[c] + state[d]) >>> 0;
44
- state[b] = rotl32(state[b] ^ state[c], 7);
45
- }
46
-
47
- /**
48
- * Creates initial ChaCha20 state from key, counter, and nonce
49
- * @param {Uint8Array} key - 32-byte key
50
- * @param {number} counter - 32-bit block counter
51
- * @param {Uint8Array} nonce - 12-byte nonce
52
- * @returns {Uint32Array} Initial state (16 x 32-bit words)
53
- */
54
- function createState(key, counter, nonce) {
55
- const state = new Uint32Array(16);
56
- const keyView = new DataView(key.buffer, key.byteOffset, key.byteLength);
57
- const nonceView = new DataView(nonce.buffer, nonce.byteOffset, nonce.byteLength);
58
-
59
- // Constants
60
- state[0] = CONSTANTS[0];
61
- state[1] = CONSTANTS[1];
62
- state[2] = CONSTANTS[2];
63
- state[3] = CONSTANTS[3];
64
-
65
- // Key (little-endian)
66
- for (let i = 0; i < 8; i++) {
67
- state[4 + i] = keyView.getUint32(i * 4, true);
68
- }
69
-
70
- // Counter
71
- state[12] = counter >>> 0;
72
-
73
- // Nonce (little-endian)
74
- for (let i = 0; i < 3; i++) {
75
- state[13 + i] = nonceView.getUint32(i * 4, true);
76
- }
77
-
78
- return state;
79
- }
80
-
81
- /**
82
- * Computes a single ChaCha20 block
83
- * @param {Uint32Array} state - 16-element initial state
84
- * @returns {Uint32Array} 16-element output state
85
- */
86
- function chacha20Block(state) {
87
- if (!(state instanceof Uint32Array) || state.length !== 16) {
88
- throw new Error('State must be a 16-element Uint32Array');
89
- }
90
-
91
- const working = new Uint32Array(state);
92
-
93
- // 20 rounds = 10 double rounds
94
- for (let i = 0; i < 10; i++) {
95
- // Column rounds
96
- quarterRound(working, 0, 4, 8, 12);
97
- quarterRound(working, 1, 5, 9, 13);
98
- quarterRound(working, 2, 6, 10, 14);
99
- quarterRound(working, 3, 7, 11, 15);
100
- // Diagonal rounds
101
- quarterRound(working, 0, 5, 10, 15);
102
- quarterRound(working, 1, 6, 11, 12);
103
- quarterRound(working, 2, 7, 8, 13);
104
- quarterRound(working, 3, 4, 9, 14);
105
- }
106
-
107
- // Add original state to working state
108
- const output = new Uint32Array(16);
109
- for (let i = 0; i < 16; i++) {
110
- output[i] = (working[i] + state[i]) >>> 0;
111
- }
112
-
113
- return output;
114
- }
115
-
116
- /**
117
- * Serializes 32-bit words to bytes (little-endian)
118
- * @param {Uint32Array} words - Array of 32-bit words
119
- * @returns {Uint8Array} Byte array
120
- */
121
- function wordsToBytes(words) {
122
- const bytes = new Uint8Array(words.length * 4);
123
- const view = new DataView(bytes.buffer);
124
- for (let i = 0; i < words.length; i++) {
125
- view.setUint32(i * 4, words[i], true);
126
- }
127
- return bytes;
128
- }
129
-
130
- /**
131
- * ChaCha20 encryption/decryption (XOR with keystream)
132
- * @param {Uint8Array} key - 32-byte key
133
- * @param {Uint8Array} nonce - 12-byte nonce
134
- * @param {number} counter - Initial block counter
135
- * @param {Uint8Array} data - Data to encrypt/decrypt
136
- * @returns {Uint8Array} Encrypted/decrypted data
137
- * @throws {Error} If key is not 32 bytes or nonce is not 12 bytes
138
- */
139
- function chacha20(key, nonce, counter, data) {
140
- if (!(key instanceof Uint8Array) || key.length !== 32) {
141
- throw new Error('Key must be 32 bytes');
142
- }
143
- if (!(nonce instanceof Uint8Array) || nonce.length !== 12) {
144
- throw new Error('Nonce must be 12 bytes');
145
- }
146
- if (!(data instanceof Uint8Array)) {
147
- throw new Error('Data must be a Uint8Array');
148
- }
149
- if (typeof counter !== 'number' || counter < 0 || counter > 0xFFFFFFFF) {
150
- throw new Error('Counter must be a 32-bit unsigned integer');
151
- }
152
-
153
- const output = new Uint8Array(data.length);
154
- let offset = 0;
155
- let blockCounter = counter;
156
-
157
- while (offset < data.length) {
158
- const state = createState(key, blockCounter, nonce);
159
- const keystream = wordsToBytes(chacha20Block(state));
160
- const remaining = data.length - offset;
161
- const blockSize = Math.min(64, remaining);
162
-
163
- for (let i = 0; i < blockSize; i++) {
164
- output[offset + i] = data[offset + i] ^ keystream[i];
165
- }
166
-
167
- offset += blockSize;
168
- blockCounter = (blockCounter + 1) >>> 0;
169
-
170
- if (blockCounter === 0 && offset < data.length) {
171
- throw new Error('Counter overflow');
172
- }
173
- }
174
-
175
- return output;
176
- }
177
-
178
- module.exports = {
179
- chacha20Block,
180
- chacha20
181
- };
@@ -1,187 +0,0 @@
1
- 'use strict';
2
-
3
- /**
4
- * HKDF Key Derivation Function (RFC 5869)
5
- * Pure JavaScript implementation using HMAC-SHA256
6
- * @module crypto/hkdf
7
- */
8
-
9
- const { hmacSha256 } = require('./hmac');
10
-
11
- /**
12
- * Hash output length in bytes (SHA-256 = 32 bytes)
13
- * @constant {number}
14
- */
15
- const HASH_LENGTH = 32;
16
-
17
- /**
18
- * Maximum output length (255 * hash length)
19
- * @constant {number}
20
- */
21
- const MAX_OUTPUT_LENGTH = 255 * HASH_LENGTH;
22
-
23
- /**
24
- * Default salt (32 zero bytes for SHA-256)
25
- * @constant {Uint8Array}
26
- */
27
- const DEFAULT_SALT = new Uint8Array(HASH_LENGTH);
28
-
29
- /**
30
- * HKDF-Extract: Extract a pseudorandom key from input keying material
31
- *
32
- * PRK = HMAC-Hash(salt, IKM)
33
- *
34
- * @param {Uint8Array|null} salt - Optional salt value (non-secret random value)
35
- * If null/undefined/empty, uses zeros
36
- * @param {Uint8Array} ikm - Input keying material
37
- * @returns {Uint8Array} Pseudorandom key (32 bytes)
38
- * @throws {TypeError} If ikm is not a Uint8Array
39
- *
40
- * @example
41
- * const ikm = new Uint8Array([...sharedSecret]);
42
- * const salt = crypto.getRandomValues(new Uint8Array(32));
43
- * const prk = extract(salt, ikm);
44
- */
45
- function extract(salt, ikm) {
46
- if (!(ikm instanceof Uint8Array)) {
47
- throw new TypeError('IKM must be a Uint8Array');
48
- }
49
-
50
- // Use default salt if not provided or empty
51
- const actualSalt = (salt && salt.length > 0) ? salt : DEFAULT_SALT;
52
-
53
- return hmacSha256(actualSalt, ikm);
54
- }
55
-
56
- /**
57
- * HKDF-Expand: Expand a pseudorandom key to desired length
58
- *
59
- * T(0) = empty string
60
- * T(1) = HMAC-Hash(PRK, T(0) | info | 0x01)
61
- * T(2) = HMAC-Hash(PRK, T(1) | info | 0x02)
62
- * ...
63
- * OKM = first L bytes of T(1) | T(2) | ...
64
- *
65
- * @param {Uint8Array} prk - Pseudorandom key (from extract)
66
- * @param {Uint8Array} info - Context and application specific info
67
- * @param {number} length - Desired output length in bytes (max 8160)
68
- * @returns {Uint8Array} Output keying material of specified length
69
- * @throws {TypeError} If prk or info is not a Uint8Array
70
- * @throws {RangeError} If length exceeds maximum (255 * 32 = 8160 bytes)
71
- *
72
- * @example
73
- * const prk = extract(salt, ikm);
74
- * const info = new TextEncoder().encode('encryption-key');
75
- * const key = expand(prk, info, 32);
76
- */
77
- function expand(prk, info, length) {
78
- if (!(prk instanceof Uint8Array)) {
79
- throw new TypeError('PRK must be a Uint8Array');
80
- }
81
- if (!(info instanceof Uint8Array)) {
82
- throw new TypeError('Info must be a Uint8Array');
83
- }
84
- if (!Number.isInteger(length) || length < 0) {
85
- throw new TypeError('Length must be a non-negative integer');
86
- }
87
- if (length > MAX_OUTPUT_LENGTH) {
88
- throw new RangeError(`Length must not exceed ${MAX_OUTPUT_LENGTH} bytes`);
89
- }
90
- if (length === 0) {
91
- return new Uint8Array(0);
92
- }
93
-
94
- // Calculate number of iterations needed
95
- const n = Math.ceil(length / HASH_LENGTH);
96
- const okm = new Uint8Array(n * HASH_LENGTH);
97
-
98
- // T(0) = empty, T(i) = HMAC(PRK, T(i-1) | info | i)
99
- let previous = new Uint8Array(0);
100
-
101
- for (let i = 1; i <= n; i++) {
102
- // Construct input: T(i-1) | info | counter
103
- const inputLength = previous.length + info.length + 1;
104
- const input = new Uint8Array(inputLength);
105
-
106
- let offset = 0;
107
- if (previous.length > 0) {
108
- input.set(previous, offset);
109
- offset += previous.length;
110
- }
111
- input.set(info, offset);
112
- offset += info.length;
113
- input[offset] = i; // Counter byte (1-indexed)
114
-
115
- // T(i) = HMAC-Hash(PRK, input)
116
- previous = hmacSha256(prk, input);
117
-
118
- // Copy to output
119
- okm.set(previous, (i - 1) * HASH_LENGTH);
120
- }
121
-
122
- // Return only the requested number of bytes
123
- return okm.subarray(0, length);
124
- }
125
-
126
- /**
127
- * HKDF: Combined extract-then-expand operation
128
- *
129
- * Convenience function that performs both HKDF-Extract and HKDF-Expand
130
- *
131
- * @param {Uint8Array} ikm - Input keying material
132
- * @param {Uint8Array|null} salt - Optional salt (uses zeros if null/empty)
133
- * @param {Uint8Array} info - Context and application specific info
134
- * @param {number} length - Desired output length in bytes
135
- * @returns {Uint8Array} Derived key material of specified length
136
- * @throws {TypeError} If ikm or info is not a Uint8Array
137
- * @throws {RangeError} If length exceeds maximum
138
- *
139
- * @example
140
- * const sharedSecret = performDH(myPrivate, theirPublic);
141
- * const info = new TextEncoder().encode('noise-handshake-v1');
142
- * const key = derive(sharedSecret, null, info, 32);
143
- */
144
- function derive(ikm, salt, info, length) {
145
- const prk = extract(salt, ikm);
146
- return expand(prk, info, length);
147
- }
148
-
149
- /**
150
- * Derive multiple keys in one operation
151
- * Useful for deriving encryption and MAC keys together
152
- *
153
- * @param {Uint8Array} ikm - Input keying material
154
- * @param {Uint8Array|null} salt - Optional salt
155
- * @param {Uint8Array} info - Context info
156
- * @param {number[]} lengths - Array of key lengths to derive
157
- * @returns {Uint8Array[]} Array of derived keys
158
- *
159
- * @example
160
- * const [encKey, macKey] = deriveMultiple(secret, salt, info, [32, 32]);
161
- */
162
- function deriveMultiple(ikm, salt, info, lengths) {
163
- if (!Array.isArray(lengths) || lengths.length === 0) {
164
- throw new TypeError('Lengths must be a non-empty array');
165
- }
166
-
167
- const totalLength = lengths.reduce((sum, len) => sum + len, 0);
168
- const combined = derive(ikm, salt, info, totalLength);
169
-
170
- const keys = [];
171
- let offset = 0;
172
- for (const len of lengths) {
173
- keys.push(combined.slice(offset, offset + len));
174
- offset += len;
175
- }
176
-
177
- return keys;
178
- }
179
-
180
- module.exports = {
181
- extract,
182
- expand,
183
- derive,
184
- deriveMultiple,
185
- HASH_LENGTH,
186
- MAX_OUTPUT_LENGTH
187
- };
@@ -1,143 +0,0 @@
1
- 'use strict';
2
-
3
- /**
4
- * HMAC-SHA256 Implementation (RFC 2104)
5
- * Pure JavaScript implementation for BLE Mesh Network
6
- * @module crypto/hmac
7
- */
8
-
9
- const { hash } = require('./sha256');
10
-
11
- /**
12
- * HMAC block size in bytes (SHA-256 uses 64-byte blocks)
13
- * @constant {number}
14
- */
15
- const BLOCK_SIZE = 64;
16
-
17
- /**
18
- * Inner padding byte value
19
- * @constant {number}
20
- */
21
- const IPAD = 0x36;
22
-
23
- /**
24
- * Outer padding byte value
25
- * @constant {number}
26
- */
27
- const OPAD = 0x5c;
28
-
29
- /**
30
- * Compute HMAC-SHA256 of data with given key
31
- *
32
- * HMAC is computed as:
33
- * HMAC(K, m) = H((K' XOR opad) || H((K' XOR ipad) || m))
34
- *
35
- * Where K' is the key padded/hashed to block size
36
- *
37
- * @param {Uint8Array} key - Secret key (any length)
38
- * @param {Uint8Array} data - Message to authenticate
39
- * @returns {Uint8Array} 32-byte HMAC digest
40
- * @throws {TypeError} If key or data is not a Uint8Array
41
- *
42
- * @example
43
- * const key = new Uint8Array([0x0b, 0x0b, ...]);
44
- * const data = new TextEncoder().encode('Hi There');
45
- * const mac = hmacSha256(key, data);
46
- */
47
- function hmacSha256(key, data) {
48
- if (!(key instanceof Uint8Array)) {
49
- throw new TypeError('Key must be a Uint8Array');
50
- }
51
- if (!(data instanceof Uint8Array)) {
52
- throw new TypeError('Data must be a Uint8Array');
53
- }
54
-
55
- // Step 1: Prepare the key
56
- // If key is longer than block size, hash it first
57
- // If shorter, it will be padded with zeros
58
- let keyPrime;
59
- if (key.length > BLOCK_SIZE) {
60
- keyPrime = hash(key);
61
- } else {
62
- keyPrime = key;
63
- }
64
-
65
- // Step 2: Create padded key blocks
66
- const keyPadded = new Uint8Array(BLOCK_SIZE);
67
- keyPadded.set(keyPrime);
68
- // Remaining bytes are already 0 from Uint8Array initialization
69
-
70
- // Step 3: Create inner and outer padded keys
71
- const innerKey = new Uint8Array(BLOCK_SIZE);
72
- const outerKey = new Uint8Array(BLOCK_SIZE);
73
-
74
- for (let i = 0; i < BLOCK_SIZE; i++) {
75
- innerKey[i] = keyPadded[i] ^ IPAD;
76
- outerKey[i] = keyPadded[i] ^ OPAD;
77
- }
78
-
79
- // Step 4: Compute inner hash: H((K' XOR ipad) || message)
80
- const innerData = new Uint8Array(BLOCK_SIZE + data.length);
81
- innerData.set(innerKey);
82
- innerData.set(data, BLOCK_SIZE);
83
- const innerHash = hash(innerData);
84
-
85
- // Step 5: Compute outer hash: H((K' XOR opad) || innerHash)
86
- const outerData = new Uint8Array(BLOCK_SIZE + 32);
87
- outerData.set(outerKey);
88
- outerData.set(innerHash, BLOCK_SIZE);
89
-
90
- return hash(outerData);
91
- }
92
-
93
- /**
94
- * Verify an HMAC-SHA256 digest
95
- * Uses constant-time comparison to prevent timing attacks
96
- *
97
- * @param {Uint8Array} key - Secret key
98
- * @param {Uint8Array} data - Message that was authenticated
99
- * @param {Uint8Array} expectedMac - Expected HMAC digest to verify against
100
- * @returns {boolean} True if MAC is valid, false otherwise
101
- *
102
- * @example
103
- * const isValid = verifyHmac(key, data, receivedMac);
104
- * if (!isValid) {
105
- * throw new Error('Message authentication failed');
106
- * }
107
- */
108
- function verifyHmac(key, data, expectedMac) {
109
- if (!(expectedMac instanceof Uint8Array) || expectedMac.length !== 32) {
110
- return false;
111
- }
112
-
113
- const computedMac = hmacSha256(key, data);
114
- return constantTimeEqual(computedMac, expectedMac);
115
- }
116
-
117
- /**
118
- * Constant-time comparison of two byte arrays
119
- * Prevents timing attacks by always comparing all bytes
120
- *
121
- * @param {Uint8Array} a - First array
122
- * @param {Uint8Array} b - Second array
123
- * @returns {boolean} True if arrays are equal
124
- * @private
125
- */
126
- function constantTimeEqual(a, b) {
127
- if (a.length !== b.length) {
128
- return false;
129
- }
130
-
131
- let result = 0;
132
- for (let i = 0; i < a.length; i++) {
133
- result |= a[i] ^ b[i];
134
- }
135
-
136
- return result === 0;
137
- }
138
-
139
- module.exports = {
140
- hmacSha256,
141
- verifyHmac,
142
- BLOCK_SIZE
143
- };