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.
- package/README.md +288 -172
- package/docs/IOS-BACKGROUND-BLE.md +231 -0
- package/docs/OPTIMIZATION.md +70 -0
- package/docs/SPEC-v2.1.md +308 -0
- package/package.json +1 -1
- package/src/MeshNetwork.js +659 -465
- package/src/constants/index.js +1 -0
- package/src/crypto/AutoCrypto.js +79 -0
- package/src/crypto/CryptoProvider.js +99 -0
- package/src/crypto/index.js +15 -63
- package/src/crypto/providers/ExpoCryptoProvider.js +125 -0
- package/src/crypto/providers/QuickCryptoProvider.js +134 -0
- package/src/crypto/providers/TweetNaClProvider.js +124 -0
- package/src/crypto/providers/index.js +11 -0
- package/src/errors/MeshError.js +2 -1
- package/src/expo/withBLEMesh.js +102 -0
- package/src/hooks/useMesh.js +30 -9
- package/src/hooks/useMessages.js +2 -0
- package/src/index.js +23 -8
- package/src/mesh/dedup/DedupManager.js +36 -10
- package/src/mesh/fragment/Assembler.js +5 -0
- package/src/mesh/index.js +1 -1
- package/src/mesh/monitor/ConnectionQuality.js +408 -0
- package/src/mesh/monitor/NetworkMonitor.js +327 -316
- package/src/mesh/monitor/index.js +7 -3
- package/src/mesh/peer/PeerManager.js +6 -1
- package/src/mesh/router/MessageRouter.js +26 -15
- package/src/mesh/router/RouteTable.js +7 -1
- package/src/mesh/store/StoreAndForwardManager.js +295 -297
- package/src/mesh/store/index.js +1 -1
- package/src/service/BatteryOptimizer.js +282 -278
- package/src/service/EmergencyManager.js +224 -214
- package/src/service/HandshakeManager.js +167 -13
- package/src/service/MeshService.js +72 -6
- package/src/service/SessionManager.js +77 -2
- package/src/service/audio/AudioManager.js +8 -2
- package/src/service/file/FileAssembler.js +106 -0
- package/src/service/file/FileChunker.js +79 -0
- package/src/service/file/FileManager.js +307 -0
- package/src/service/file/FileMessage.js +122 -0
- package/src/service/file/index.js +15 -0
- package/src/service/text/broadcast/BroadcastManager.js +16 -0
- package/src/transport/BLETransport.js +131 -9
- package/src/transport/MockTransport.js +1 -1
- package/src/transport/MultiTransport.js +305 -0
- package/src/transport/WiFiDirectTransport.js +295 -0
- package/src/transport/adapters/NodeBLEAdapter.js +34 -0
- package/src/transport/adapters/RNBLEAdapter.js +56 -1
- package/src/transport/index.js +6 -0
- package/src/utils/compression.js +291 -291
- package/src/crypto/aead.js +0 -189
- package/src/crypto/chacha20.js +0 -181
- package/src/crypto/hkdf.js +0 -187
- package/src/crypto/hmac.js +0 -143
- package/src/crypto/keys/KeyManager.js +0 -271
- package/src/crypto/keys/KeyPair.js +0 -216
- package/src/crypto/keys/SecureStorage.js +0 -219
- package/src/crypto/keys/index.js +0 -32
- package/src/crypto/noise/handshake.js +0 -410
- package/src/crypto/noise/index.js +0 -27
- package/src/crypto/noise/session.js +0 -253
- package/src/crypto/noise/state.js +0 -268
- package/src/crypto/poly1305.js +0 -113
- package/src/crypto/sha256.js +0 -240
- package/src/crypto/x25519.js +0 -154
package/src/constants/index.js
CHANGED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Auto-detect best available crypto provider
|
|
5
|
+
* @module crypto/AutoCrypto
|
|
6
|
+
*
|
|
7
|
+
* Priority order:
|
|
8
|
+
* 1. react-native-quick-crypto (native speed)
|
|
9
|
+
* 2. expo-crypto + tweetnacl (Expo projects)
|
|
10
|
+
* 3. tweetnacl (universal fallback)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const QuickCryptoProvider = require('./providers/QuickCryptoProvider');
|
|
14
|
+
const ExpoCryptoProvider = require('./providers/ExpoCryptoProvider');
|
|
15
|
+
const TweetNaClProvider = require('./providers/TweetNaClProvider');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Detects and returns the best available crypto provider.
|
|
19
|
+
* @returns {import('./CryptoProvider')} Best available provider
|
|
20
|
+
* @throws {Error} If no crypto provider is available
|
|
21
|
+
*/
|
|
22
|
+
function detectProvider() {
|
|
23
|
+
// 1. Native speed (react-native-quick-crypto)
|
|
24
|
+
if (QuickCryptoProvider.isAvailable()) {
|
|
25
|
+
return new QuickCryptoProvider();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 2. Expo (expo-crypto + tweetnacl)
|
|
29
|
+
if (ExpoCryptoProvider.isAvailable()) {
|
|
30
|
+
return new ExpoCryptoProvider();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 3. Universal (tweetnacl)
|
|
34
|
+
if (TweetNaClProvider.isAvailable()) {
|
|
35
|
+
return new TweetNaClProvider();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
throw new Error(
|
|
39
|
+
'No crypto provider available. Install one of:\n' +
|
|
40
|
+
' npm install tweetnacl (works everywhere)\n' +
|
|
41
|
+
' npm install react-native-quick-crypto (native speed)\n' +
|
|
42
|
+
' npx expo install expo-crypto && npm install tweetnacl (Expo)'
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Creates a crypto provider from a config value.
|
|
48
|
+
* @param {string|Object|null} config - 'auto', provider name, or provider instance
|
|
49
|
+
* @returns {import('./CryptoProvider')}
|
|
50
|
+
*/
|
|
51
|
+
function createProvider(config) {
|
|
52
|
+
if (!config || config === 'auto') {
|
|
53
|
+
return detectProvider();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (typeof config === 'object' && typeof config.generateKeyPair === 'function') {
|
|
57
|
+
return config; // Already a provider instance
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (typeof config === 'string') {
|
|
61
|
+
switch (config) {
|
|
62
|
+
case 'tweetnacl':
|
|
63
|
+
return new TweetNaClProvider();
|
|
64
|
+
case 'quick-crypto':
|
|
65
|
+
return new QuickCryptoProvider();
|
|
66
|
+
case 'expo-crypto':
|
|
67
|
+
return new ExpoCryptoProvider();
|
|
68
|
+
default:
|
|
69
|
+
throw new Error(`Unknown crypto provider: ${config}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
throw new Error('Invalid crypto config: expected "auto", provider name, or provider instance');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = {
|
|
77
|
+
detectProvider,
|
|
78
|
+
createProvider
|
|
79
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Abstract CryptoProvider interface
|
|
5
|
+
* @module crypto/CryptoProvider
|
|
6
|
+
*
|
|
7
|
+
* Pluggable crypto backend. Consumers choose their provider:
|
|
8
|
+
* - TweetNaClProvider (tweetnacl) — works everywhere
|
|
9
|
+
* - QuickCryptoProvider (react-native-quick-crypto) — native speed
|
|
10
|
+
* - ExpoCryptoProvider (expo-crypto) — for Expo projects
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Abstract crypto provider interface.
|
|
15
|
+
* All crypto operations go through this interface, allowing
|
|
16
|
+
* consumers to swap implementations without changing application code.
|
|
17
|
+
*
|
|
18
|
+
* @abstract
|
|
19
|
+
* @class CryptoProvider
|
|
20
|
+
*/
|
|
21
|
+
class CryptoProvider {
|
|
22
|
+
/**
|
|
23
|
+
* Provider name for identification
|
|
24
|
+
* @returns {string}
|
|
25
|
+
*/
|
|
26
|
+
get name() {
|
|
27
|
+
return 'abstract';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Generates a new X25519 key pair
|
|
32
|
+
* @returns {{ publicKey: Uint8Array, secretKey: Uint8Array }}
|
|
33
|
+
*/
|
|
34
|
+
generateKeyPair() {
|
|
35
|
+
throw new Error('CryptoProvider.generateKeyPair() must be implemented');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Computes X25519 shared secret
|
|
40
|
+
* @param {Uint8Array} secretKey - Our secret key (32 bytes)
|
|
41
|
+
* @param {Uint8Array} publicKey - Their public key (32 bytes)
|
|
42
|
+
* @returns {Uint8Array} Shared secret (32 bytes)
|
|
43
|
+
*/
|
|
44
|
+
sharedSecret(_secretKey, _publicKey) {
|
|
45
|
+
throw new Error('CryptoProvider.sharedSecret() must be implemented');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* AEAD encrypt (XSalsa20-Poly1305 or ChaCha20-Poly1305)
|
|
50
|
+
* @param {Uint8Array} key - Encryption key (32 bytes)
|
|
51
|
+
* @param {Uint8Array} nonce - Nonce (24 bytes for XSalsa20, 12 for ChaCha20)
|
|
52
|
+
* @param {Uint8Array} plaintext - Data to encrypt
|
|
53
|
+
* @param {Uint8Array} [ad] - Additional authenticated data (optional)
|
|
54
|
+
* @returns {Uint8Array} Ciphertext with authentication tag
|
|
55
|
+
*/
|
|
56
|
+
encrypt(_key, _nonce, _plaintext, _ad) {
|
|
57
|
+
throw new Error('CryptoProvider.encrypt() must be implemented');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* AEAD decrypt
|
|
62
|
+
* @param {Uint8Array} key - Encryption key (32 bytes)
|
|
63
|
+
* @param {Uint8Array} nonce - Nonce
|
|
64
|
+
* @param {Uint8Array} ciphertext - Ciphertext with auth tag
|
|
65
|
+
* @param {Uint8Array} [ad] - Additional authenticated data (optional)
|
|
66
|
+
* @returns {Uint8Array|null} Plaintext or null if authentication fails
|
|
67
|
+
*/
|
|
68
|
+
decrypt(_key, _nonce, _ciphertext, _ad) {
|
|
69
|
+
throw new Error('CryptoProvider.decrypt() must be implemented');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Computes SHA-256 hash
|
|
74
|
+
* @param {Uint8Array} data - Data to hash
|
|
75
|
+
* @returns {Uint8Array} Hash (32 bytes)
|
|
76
|
+
*/
|
|
77
|
+
hash(_data) {
|
|
78
|
+
throw new Error('CryptoProvider.hash() must be implemented');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Generates cryptographically secure random bytes
|
|
83
|
+
* @param {number} length - Number of bytes
|
|
84
|
+
* @returns {Uint8Array} Random bytes
|
|
85
|
+
*/
|
|
86
|
+
randomBytes(_length) {
|
|
87
|
+
throw new Error('CryptoProvider.randomBytes() must be implemented');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Checks if this provider is available in the current environment
|
|
92
|
+
* @returns {boolean}
|
|
93
|
+
*/
|
|
94
|
+
static isAvailable() {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = CryptoProvider;
|
package/src/crypto/index.js
CHANGED
|
@@ -1,72 +1,24 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Crypto
|
|
5
|
-
* Main exports for all cryptographic primitives and protocols.
|
|
4
|
+
* @fileoverview Crypto module — pluggable provider system
|
|
6
5
|
* @module crypto
|
|
6
|
+
*
|
|
7
|
+
* Provides a CryptoProvider interface with auto-detection:
|
|
8
|
+
* - TweetNaClProvider (tweetnacl) — works everywhere
|
|
9
|
+
* - QuickCryptoProvider (react-native-quick-crypto) — native speed
|
|
10
|
+
* - ExpoCryptoProvider (expo-crypto) — for Expo projects
|
|
7
11
|
*/
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
const {
|
|
11
|
-
const {
|
|
12
|
-
const { extract, expand, derive, deriveMultiple } = require('./hkdf');
|
|
13
|
-
|
|
14
|
-
// Symmetric encryption
|
|
15
|
-
const { chacha20, chacha20Block } = require('./chacha20');
|
|
16
|
-
const { poly1305 } = require('./poly1305');
|
|
17
|
-
const { encrypt, decrypt } = require('./aead');
|
|
18
|
-
|
|
19
|
-
// Asymmetric encryption (key exchange)
|
|
20
|
-
const { generateKeyPair, scalarMult, scalarMultBase } = require('./x25519');
|
|
21
|
-
|
|
22
|
-
// Noise Protocol
|
|
23
|
-
const noise = require('./noise');
|
|
24
|
-
|
|
25
|
-
// Key management
|
|
26
|
-
const keys = require('./keys');
|
|
13
|
+
const CryptoProvider = require('./CryptoProvider');
|
|
14
|
+
const { TweetNaClProvider, QuickCryptoProvider, ExpoCryptoProvider } = require('./providers');
|
|
15
|
+
const { detectProvider, createProvider } = require('./AutoCrypto');
|
|
27
16
|
|
|
28
17
|
module.exports = {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
hmacSha256,
|
|
36
|
-
verifyHmac,
|
|
37
|
-
|
|
38
|
-
// HKDF
|
|
39
|
-
hkdfExtract: extract,
|
|
40
|
-
hkdfExpand: expand,
|
|
41
|
-
hkdf: derive,
|
|
42
|
-
hkdfMultiple: deriveMultiple,
|
|
43
|
-
|
|
44
|
-
// ChaCha20
|
|
45
|
-
chacha20,
|
|
46
|
-
chacha20Block,
|
|
47
|
-
|
|
48
|
-
// Poly1305
|
|
49
|
-
poly1305,
|
|
50
|
-
|
|
51
|
-
// AEAD (ChaCha20-Poly1305)
|
|
52
|
-
encrypt,
|
|
53
|
-
decrypt,
|
|
54
|
-
|
|
55
|
-
// X25519
|
|
56
|
-
generateKeyPair,
|
|
57
|
-
scalarMult,
|
|
58
|
-
scalarMultBase,
|
|
59
|
-
|
|
60
|
-
// Noise Protocol (as namespace)
|
|
61
|
-
noise,
|
|
62
|
-
|
|
63
|
-
// Key management (as namespace)
|
|
64
|
-
keys,
|
|
65
|
-
|
|
66
|
-
// Re-export commonly used classes at top level
|
|
67
|
-
NoiseHandshake: noise.NoiseHandshake,
|
|
68
|
-
NoiseSession: noise.NoiseSession,
|
|
69
|
-
SymmetricState: noise.SymmetricState,
|
|
70
|
-
KeyPair: keys.KeyPair,
|
|
71
|
-
KeyManager: keys.KeyManager
|
|
18
|
+
CryptoProvider,
|
|
19
|
+
TweetNaClProvider,
|
|
20
|
+
QuickCryptoProvider,
|
|
21
|
+
ExpoCryptoProvider,
|
|
22
|
+
detectProvider,
|
|
23
|
+
createProvider
|
|
72
24
|
};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Expo-crypto based provider
|
|
5
|
+
* @module crypto/providers/ExpoCryptoProvider
|
|
6
|
+
*
|
|
7
|
+
* Uses expo-crypto for Expo managed workflow projects.
|
|
8
|
+
* Note: expo-crypto provides hashing and random bytes but NOT key exchange or AEAD.
|
|
9
|
+
* Falls back to tweetnacl for those operations.
|
|
10
|
+
*
|
|
11
|
+
* Install: npx expo install expo-crypto tweetnacl
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const CryptoProvider = require('../CryptoProvider');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Crypto provider for Expo projects.
|
|
18
|
+
* Uses expo-crypto for hashing/random, tweetnacl for key exchange and AEAD.
|
|
19
|
+
*
|
|
20
|
+
* @class ExpoCryptoProvider
|
|
21
|
+
* @extends CryptoProvider
|
|
22
|
+
*/
|
|
23
|
+
class ExpoCryptoProvider extends CryptoProvider {
|
|
24
|
+
constructor(options = {}) {
|
|
25
|
+
super();
|
|
26
|
+
this._expoCrypto = options.expoCrypto || null;
|
|
27
|
+
this._nacl = options.nacl || null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get name() {
|
|
31
|
+
return 'expo-crypto';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
_getExpoCrypto() {
|
|
35
|
+
if (!this._expoCrypto) {
|
|
36
|
+
try {
|
|
37
|
+
this._expoCrypto = require('expo-crypto');
|
|
38
|
+
} catch (e) {
|
|
39
|
+
throw new Error('expo-crypto is required. Install: npx expo install expo-crypto');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return this._expoCrypto;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
_getNacl() {
|
|
46
|
+
if (!this._nacl) {
|
|
47
|
+
try {
|
|
48
|
+
this._nacl = require('tweetnacl');
|
|
49
|
+
} catch (e) {
|
|
50
|
+
throw new Error('tweetnacl is required with ExpoCryptoProvider. Install: npm install tweetnacl');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return this._nacl;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** @inheritdoc */
|
|
57
|
+
generateKeyPair() {
|
|
58
|
+
const nacl = this._getNacl();
|
|
59
|
+
const kp = nacl.box.keyPair();
|
|
60
|
+
return { publicKey: kp.publicKey, secretKey: kp.secretKey };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** @inheritdoc */
|
|
64
|
+
sharedSecret(secretKey, publicKey) {
|
|
65
|
+
const nacl = this._getNacl();
|
|
66
|
+
return nacl.box.before(publicKey, secretKey);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** @inheritdoc */
|
|
70
|
+
encrypt(key, nonce, plaintext, _ad) {
|
|
71
|
+
const nacl = this._getNacl();
|
|
72
|
+
|
|
73
|
+
// Ensure 24-byte nonce (pad short nonces with zeros)
|
|
74
|
+
let fullNonce = nonce;
|
|
75
|
+
if (nonce.length < 24) {
|
|
76
|
+
fullNonce = new Uint8Array(24);
|
|
77
|
+
fullNonce.set(nonce);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return nacl.secretbox(plaintext, fullNonce, key);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** @inheritdoc */
|
|
84
|
+
decrypt(key, nonce, ciphertext, _ad) {
|
|
85
|
+
const nacl = this._getNacl();
|
|
86
|
+
|
|
87
|
+
// Ensure 24-byte nonce (pad short nonces with zeros)
|
|
88
|
+
let fullNonce = nonce;
|
|
89
|
+
if (nonce.length < 24) {
|
|
90
|
+
fullNonce = new Uint8Array(24);
|
|
91
|
+
fullNonce.set(nonce);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return nacl.secretbox.open(ciphertext, fullNonce, key) || null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** @inheritdoc */
|
|
98
|
+
hash(data) {
|
|
99
|
+
// expo-crypto's digestStringAsync is async — for sync compat, use tweetnacl
|
|
100
|
+
const nacl = this._getNacl();
|
|
101
|
+
return nacl.hash(data).slice(0, 32);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** @inheritdoc */
|
|
105
|
+
randomBytes(length) {
|
|
106
|
+
const expoCrypto = this._getExpoCrypto();
|
|
107
|
+
if (expoCrypto.getRandomBytes) {
|
|
108
|
+
return new Uint8Array(expoCrypto.getRandomBytes(length));
|
|
109
|
+
}
|
|
110
|
+
// Fallback to tweetnacl
|
|
111
|
+
const nacl = this._getNacl();
|
|
112
|
+
return nacl.randomBytes(length);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
static isAvailable() {
|
|
116
|
+
try {
|
|
117
|
+
require('expo-crypto');
|
|
118
|
+
return true;
|
|
119
|
+
} catch (e) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = ExpoCryptoProvider;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview react-native-quick-crypto based provider
|
|
5
|
+
* @module crypto/providers/QuickCryptoProvider
|
|
6
|
+
*
|
|
7
|
+
* Uses native crypto via react-native-quick-crypto for maximum performance.
|
|
8
|
+
* Install: npm install react-native-quick-crypto
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const CryptoProvider = require('../CryptoProvider');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Crypto provider using react-native-quick-crypto.
|
|
15
|
+
* Provides native-speed crypto on React Native (JSI binding).
|
|
16
|
+
*
|
|
17
|
+
* @class QuickCryptoProvider
|
|
18
|
+
* @extends CryptoProvider
|
|
19
|
+
*/
|
|
20
|
+
class QuickCryptoProvider extends CryptoProvider {
|
|
21
|
+
constructor(options = {}) {
|
|
22
|
+
super();
|
|
23
|
+
this._crypto = options.crypto || null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get name() {
|
|
27
|
+
return 'quick-crypto';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
_getCrypto() {
|
|
31
|
+
if (!this._crypto) {
|
|
32
|
+
try {
|
|
33
|
+
this._crypto = require('react-native-quick-crypto');
|
|
34
|
+
} catch (e) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
'react-native-quick-crypto is required. Install: npm install react-native-quick-crypto'
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return this._crypto;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** @inheritdoc */
|
|
44
|
+
generateKeyPair() {
|
|
45
|
+
const crypto = this._getCrypto();
|
|
46
|
+
const { publicKey, privateKey } = crypto.generateKeyPairSync('x25519');
|
|
47
|
+
return {
|
|
48
|
+
publicKey: new Uint8Array(publicKey.export({ type: 'spki', format: 'der' }).slice(-32)),
|
|
49
|
+
secretKey: new Uint8Array(privateKey.export({ type: 'pkcs8', format: 'der' }).slice(-32))
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** @inheritdoc */
|
|
54
|
+
sharedSecret(secretKey, publicKey) {
|
|
55
|
+
const crypto = this._getCrypto();
|
|
56
|
+
const privKey = crypto.createPrivateKey({
|
|
57
|
+
key: Buffer.concat([
|
|
58
|
+
Buffer.from('302e020100300506032b656e04220420', 'hex'),
|
|
59
|
+
Buffer.from(secretKey)
|
|
60
|
+
]),
|
|
61
|
+
format: 'der',
|
|
62
|
+
type: 'pkcs8'
|
|
63
|
+
});
|
|
64
|
+
const pubKey = crypto.createPublicKey({
|
|
65
|
+
key: Buffer.concat([
|
|
66
|
+
Buffer.from('302a300506032b656e032100', 'hex'),
|
|
67
|
+
Buffer.from(publicKey)
|
|
68
|
+
]),
|
|
69
|
+
format: 'der',
|
|
70
|
+
type: 'spki'
|
|
71
|
+
});
|
|
72
|
+
const shared = crypto.diffieHellman({ privateKey: privKey, publicKey: pubKey });
|
|
73
|
+
return new Uint8Array(shared);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** @inheritdoc */
|
|
77
|
+
encrypt(key, nonce, plaintext, _ad) {
|
|
78
|
+
// Use tweetnacl for encryption to ensure cross-provider compatibility
|
|
79
|
+
// QuickCrypto's advantage is in fast native key generation (X25519), not AEAD
|
|
80
|
+
const nacl = require('tweetnacl');
|
|
81
|
+
|
|
82
|
+
// Ensure 24-byte nonce for XSalsa20-Poly1305
|
|
83
|
+
let fullNonce = nonce;
|
|
84
|
+
if (nonce.length < 24) {
|
|
85
|
+
fullNonce = new Uint8Array(24);
|
|
86
|
+
fullNonce.set(nonce);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return nacl.secretbox(plaintext, fullNonce, key);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** @inheritdoc */
|
|
93
|
+
decrypt(key, nonce, ciphertext, _ad) {
|
|
94
|
+
const nacl = require('tweetnacl');
|
|
95
|
+
|
|
96
|
+
// Ensure 24-byte nonce for XSalsa20-Poly1305
|
|
97
|
+
let fullNonce = nonce;
|
|
98
|
+
if (nonce.length < 24) {
|
|
99
|
+
fullNonce = new Uint8Array(24);
|
|
100
|
+
fullNonce.set(nonce);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const result = nacl.secretbox.open(ciphertext, fullNonce, key);
|
|
104
|
+
if (!result) {
|
|
105
|
+
return null; // Decryption failed: authentication error
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** @inheritdoc */
|
|
111
|
+
hash(data) {
|
|
112
|
+
// Use SHA-512 truncated to 32 bytes for cross-provider compatibility
|
|
113
|
+
const nacl = require('tweetnacl');
|
|
114
|
+
const full = nacl.hash(data); // SHA-512
|
|
115
|
+
return full.slice(0, 32);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** @inheritdoc */
|
|
119
|
+
randomBytes(length) {
|
|
120
|
+
const crypto = this._getCrypto();
|
|
121
|
+
return new Uint8Array(crypto.randomBytes(length));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
static isAvailable() {
|
|
125
|
+
try {
|
|
126
|
+
require('react-native-quick-crypto');
|
|
127
|
+
return true;
|
|
128
|
+
} catch (e) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
module.exports = QuickCryptoProvider;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview TweetNaCl-based crypto provider
|
|
5
|
+
* @module crypto/providers/TweetNaClProvider
|
|
6
|
+
*
|
|
7
|
+
* Uses the `tweetnacl` library — lightweight, audited, works everywhere.
|
|
8
|
+
* Install: npm install tweetnacl
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const CryptoProvider = require('../CryptoProvider');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Crypto provider using tweetnacl.
|
|
15
|
+
* Provides X25519 key exchange, XSalsa20-Poly1305 AEAD, SHA-512 (for hashing).
|
|
16
|
+
*
|
|
17
|
+
* @class TweetNaClProvider
|
|
18
|
+
* @extends CryptoProvider
|
|
19
|
+
*/
|
|
20
|
+
class TweetNaClProvider extends CryptoProvider {
|
|
21
|
+
/**
|
|
22
|
+
* @param {Object} [options={}]
|
|
23
|
+
* @param {Object} [options.nacl] - Injected tweetnacl instance (for testing)
|
|
24
|
+
*/
|
|
25
|
+
constructor(options = {}) {
|
|
26
|
+
super();
|
|
27
|
+
this._nacl = options.nacl || null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get name() {
|
|
31
|
+
return 'tweetnacl';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Lazily loads tweetnacl
|
|
36
|
+
* @returns {Object} nacl module
|
|
37
|
+
* @private
|
|
38
|
+
*/
|
|
39
|
+
_getNacl() {
|
|
40
|
+
if (!this._nacl) {
|
|
41
|
+
try {
|
|
42
|
+
this._nacl = require('tweetnacl');
|
|
43
|
+
} catch (e) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
'tweetnacl is required for TweetNaClProvider. Install: npm install tweetnacl'
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return this._nacl;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** @inheritdoc */
|
|
53
|
+
generateKeyPair() {
|
|
54
|
+
const nacl = this._getNacl();
|
|
55
|
+
const kp = nacl.box.keyPair();
|
|
56
|
+
return { publicKey: kp.publicKey, secretKey: kp.secretKey };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** @inheritdoc */
|
|
60
|
+
sharedSecret(secretKey, publicKey) {
|
|
61
|
+
const nacl = this._getNacl();
|
|
62
|
+
return nacl.box.before(publicKey, secretKey);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** @inheritdoc */
|
|
66
|
+
encrypt(key, nonce, plaintext, _ad) {
|
|
67
|
+
const nacl = this._getNacl();
|
|
68
|
+
// tweetnacl uses XSalsa20-Poly1305 with 24-byte nonce
|
|
69
|
+
// nacl.secretbox includes authentication
|
|
70
|
+
|
|
71
|
+
// Ensure 24-byte nonce (pad short nonces with zeros)
|
|
72
|
+
let fullNonce = nonce;
|
|
73
|
+
if (nonce.length < 24) {
|
|
74
|
+
fullNonce = new Uint8Array(24);
|
|
75
|
+
fullNonce.set(nonce);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return nacl.secretbox(plaintext, fullNonce, key);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** @inheritdoc */
|
|
82
|
+
decrypt(key, nonce, ciphertext, _ad) {
|
|
83
|
+
const nacl = this._getNacl();
|
|
84
|
+
|
|
85
|
+
// Ensure 24-byte nonce (pad short nonces with zeros)
|
|
86
|
+
let fullNonce = nonce;
|
|
87
|
+
if (nonce.length < 24) {
|
|
88
|
+
fullNonce = new Uint8Array(24);
|
|
89
|
+
fullNonce.set(nonce);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const result = nacl.secretbox.open(ciphertext, fullNonce, key);
|
|
93
|
+
return result || null; // returns null on auth failure
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** @inheritdoc */
|
|
97
|
+
hash(data) {
|
|
98
|
+
const nacl = this._getNacl();
|
|
99
|
+
// tweetnacl provides SHA-512; we return first 32 bytes for SHA-256 compatibility
|
|
100
|
+
const full = nacl.hash(data);
|
|
101
|
+
return full.slice(0, 32);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** @inheritdoc */
|
|
105
|
+
randomBytes(length) {
|
|
106
|
+
const nacl = this._getNacl();
|
|
107
|
+
return nacl.randomBytes(length);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Checks if tweetnacl is available
|
|
112
|
+
* @returns {boolean}
|
|
113
|
+
*/
|
|
114
|
+
static isAvailable() {
|
|
115
|
+
try {
|
|
116
|
+
require('tweetnacl');
|
|
117
|
+
return true;
|
|
118
|
+
} catch (e) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = TweetNaClProvider;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const TweetNaClProvider = require('./TweetNaClProvider');
|
|
4
|
+
const QuickCryptoProvider = require('./QuickCryptoProvider');
|
|
5
|
+
const ExpoCryptoProvider = require('./ExpoCryptoProvider');
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
TweetNaClProvider,
|
|
9
|
+
QuickCryptoProvider,
|
|
10
|
+
ExpoCryptoProvider
|
|
11
|
+
};
|
package/src/errors/MeshError.js
CHANGED
|
@@ -20,7 +20,8 @@ class MeshError extends Error {
|
|
|
20
20
|
* @param {Object|null} [details=null] - Additional error context
|
|
21
21
|
*/
|
|
22
22
|
constructor(message, code = 'E900', details = null) {
|
|
23
|
-
|
|
23
|
+
const className = new.target ? new.target.name : 'MeshError';
|
|
24
|
+
super(`${className}: ${message}`);
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* Error name
|