From c8297dc4716d72a61a5be80af77a065d7cd6388a Mon Sep 17 00:00:00 2001 From: Athex Web3 Date: Sat, 13 Dec 2025 12:18:16 +0600 Subject: [PATCH 1/4] feat: Add DH and ECDH support --- .../project.pbxproj | 2 +- example/src/benchmarks/dh/dh.ts | 59 ++++ example/src/benchmarks/ecdh/ecdh.ts | 55 ++++ example/src/hooks/useBenchmarks.ts | 4 + example/src/hooks/useTestsList.ts | 2 + example/src/tests/dh/dh_tests.ts | 76 +++++ example/src/tests/ecdh/ecdh_tests.ts | 75 +++++ .../QuickCrypto.podspec | 3 + .../android/CMakeLists.txt | 4 + .../cpp/dh/HybridDiffieHellman.cpp | 293 ++++++++++++++++++ .../cpp/dh/HybridDiffieHellman.hpp | 42 +++ .../cpp/ecdh/HybridECDH.cpp | 222 +++++++++++++ .../cpp/ecdh/HybridECDH.hpp | 49 +++ packages/react-native-quick-crypto/nitro.json | 93 ++++-- .../android/QuickCrypto+autolinking.cmake | 2 + .../generated/android/QuickCryptoOnLoad.cpp | 20 ++ .../generated/ios/QuickCryptoAutolinking.mm | 20 ++ .../shared/c++/HybridDiffieHellmanSpec.cpp | 30 ++ .../shared/c++/HybridDiffieHellmanSpec.hpp | 72 +++++ .../generated/shared/c++/HybridECDHSpec.cpp | 27 ++ .../generated/shared/c++/HybridECDHSpec.hpp | 70 +++++ .../src/dh-groups.ts | 27 ++ .../src/diffie-hellman.ts | 191 ++++++++++++ packages/react-native-quick-crypto/src/ec.ts | 250 +++++---------- .../react-native-quick-crypto/src/ecdh.ts | 76 +++++ .../react-native-quick-crypto/src/index.ts | 6 + .../src/keys/generateKeyPair.ts | 6 +- .../src/specs/diffie-hellman.nitro.ts | 15 + .../src/specs/ecdh.nitro.ts | 11 + .../react-native-quick-crypto/src/subtle.ts | 9 +- 30 files changed, 1608 insertions(+), 203 deletions(-) create mode 100644 example/src/benchmarks/dh/dh.ts create mode 100644 example/src/benchmarks/ecdh/ecdh.ts create mode 100644 example/src/tests/dh/dh_tests.ts create mode 100644 example/src/tests/ecdh/ecdh_tests.ts create mode 100644 packages/react-native-quick-crypto/cpp/dh/HybridDiffieHellman.cpp create mode 100644 packages/react-native-quick-crypto/cpp/dh/HybridDiffieHellman.hpp create mode 100644 packages/react-native-quick-crypto/cpp/ecdh/HybridECDH.cpp create mode 100644 packages/react-native-quick-crypto/cpp/ecdh/HybridECDH.hpp create mode 100644 packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridDiffieHellmanSpec.cpp create mode 100644 packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridDiffieHellmanSpec.hpp create mode 100644 packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridECDHSpec.cpp create mode 100644 packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridECDHSpec.hpp create mode 100644 packages/react-native-quick-crypto/src/dh-groups.ts create mode 100644 packages/react-native-quick-crypto/src/diffie-hellman.ts create mode 100644 packages/react-native-quick-crypto/src/ecdh.ts create mode 100644 packages/react-native-quick-crypto/src/specs/diffie-hellman.nitro.ts create mode 100644 packages/react-native-quick-crypto/src/specs/ecdh.nitro.ts diff --git a/example/ios/QuickCryptoExample.xcodeproj/project.pbxproj b/example/ios/QuickCryptoExample.xcodeproj/project.pbxproj index 67101771..4e1fda34 100644 --- a/example/ios/QuickCryptoExample.xcodeproj/project.pbxproj +++ b/example/ios/QuickCryptoExample.xcodeproj/project.pbxproj @@ -398,7 +398,7 @@ "-DFOLLY_HAVE_CLOCK_GETTIME=1", ); OTHER_LDFLAGS = "$(inherited)"; - REACT_NATIVE_PATH = "${PODS_ROOT}/../../../node_modules/react-native"; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; SWIFT_ENABLE_EXPLICIT_MODULES = NO; diff --git a/example/src/benchmarks/dh/dh.ts b/example/src/benchmarks/dh/dh.ts new file mode 100644 index 00000000..a2662ba4 --- /dev/null +++ b/example/src/benchmarks/dh/dh.ts @@ -0,0 +1,59 @@ +import rnqc from 'react-native-quick-crypto'; +// @ts-expect-error crypto-browserify missing types +import browserify from 'crypto-browserify'; +import type { BenchFn } from '../../types/benchmarks'; +import { Bench } from 'tinybench'; + +const dh_modp14_genKeys: BenchFn = () => { + const bench = new Bench({ + name: 'DH modp14 KeyGen', + time: 200, + iterations: 10, // Cap iterations for slow JS implementations + }); + + bench + .add('rnqc', () => { + const dh = rnqc.getDiffieHellman('modp14'); + dh.generateKeys(); + }) + .add('browserify', () => { + const dh = browserify.getDiffieHellman('modp14'); + dh.generateKeys(); + }); + + bench.warmupTime = 100; + return bench; +}; + +const dh_modp14_computeSecret: BenchFn = () => { + const bench = new Bench({ + name: 'DH modp14 Compute', + time: 200, + iterations: 10, + }); + + const alice = rnqc.getDiffieHellman('modp14'); + alice.generateKeys(); + const bob = rnqc.getDiffieHellman('modp14'); + bob.generateKeys(); + const bobPub = bob.getPublicKey(); + + const bAlice = browserify.getDiffieHellman('modp14'); + bAlice.generateKeys(); + const bBob = browserify.getDiffieHellman('modp14'); + bBob.generateKeys(); + const bBobPub = bBob.getPublicKey(); + + bench + .add('rnqc', () => { + alice.computeSecret(bobPub); + }) + .add('browserify', () => { + bAlice.computeSecret(bBobPub); + }); + + bench.warmupTime = 100; + return bench; +}; + +export default [dh_modp14_genKeys, dh_modp14_computeSecret]; diff --git a/example/src/benchmarks/ecdh/ecdh.ts b/example/src/benchmarks/ecdh/ecdh.ts new file mode 100644 index 00000000..ab2ab2af --- /dev/null +++ b/example/src/benchmarks/ecdh/ecdh.ts @@ -0,0 +1,55 @@ +import rnqc from 'react-native-quick-crypto'; +import { p256 } from '@noble/curves/p256'; +import type { BenchFn } from '../../types/benchmarks'; +import { Bench } from 'tinybench'; + +const TIME_MS = 1000; + +const ecdh_p256_genKeys: BenchFn = () => { + const bench = new Bench({ + name: 'ECDH P-256 KeyGen', + time: TIME_MS, + }); + + bench + .add('rnqc', () => { + const ecdh = rnqc.createECDH('prime256v1'); + ecdh.generateKeys(); + }) + .add('@noble/curves', () => { + p256.utils.randomPrivateKey(); + }); + + bench.warmupTime = 100; + return bench; +}; + +const ecdh_p256_computeSecret: BenchFn = () => { + const bench = new Bench({ + name: 'ECDH P-256 Compute', + time: TIME_MS, + }); + + const alice = rnqc.createECDH('prime256v1'); + alice.generateKeys(); + const bob = rnqc.createECDH('prime256v1'); + bob.generateKeys(); + const bobPub = bob.getPublicKey(); + + const nobleAlicePriv = p256.utils.randomPrivateKey(); + const nobleBobPriv = p256.utils.randomPrivateKey(); + const nobleBobPub = p256.getPublicKey(nobleBobPriv); + + bench + .add('rnqc', () => { + alice.computeSecret(bobPub); + }) + .add('@noble/curves', () => { + p256.getSharedSecret(nobleAlicePriv, nobleBobPub); + }); + + bench.warmupTime = 100; + return bench; +}; + +export default [ecdh_p256_genKeys, ecdh_p256_computeSecret]; diff --git a/example/src/hooks/useBenchmarks.ts b/example/src/hooks/useBenchmarks.ts index 2dfeaec1..42a81776 100644 --- a/example/src/hooks/useBenchmarks.ts +++ b/example/src/hooks/useBenchmarks.ts @@ -7,6 +7,8 @@ import hkdf from '../benchmarks/hkdf/hkdf'; import hash from '../benchmarks/hash/hash'; import hmac from '../benchmarks/hmac/hmac'; import pbkdf2 from '../benchmarks/pbkdf2/pbkdf2'; +import ecdh from '../benchmarks/ecdh/ecdh'; +import dh from '../benchmarks/dh/dh'; import random from '../benchmarks/random/randomBytes'; import scrypt from '../benchmarks/scrypt/scrypt'; import xsalsa20 from '../benchmarks/cipher/xsalsa20'; @@ -32,6 +34,8 @@ export const useBenchmarks = (): [ newSuites.push(new BenchmarkSuite('hash', hash)); newSuites.push(new BenchmarkSuite('hmac', hmac)); newSuites.push(new BenchmarkSuite('hkdf', hkdf)); + newSuites.push(new BenchmarkSuite('ecdh', ecdh)); + newSuites.push(new BenchmarkSuite('dh', dh)); newSuites.push( new BenchmarkSuite('random', random, { 'browserify/randombytes': diff --git a/example/src/hooks/useTestsList.ts b/example/src/hooks/useTestsList.ts index e8f11fdc..37f5cc20 100644 --- a/example/src/hooks/useTestsList.ts +++ b/example/src/hooks/useTestsList.ts @@ -17,6 +17,8 @@ import '../tests/keys/public_cipher'; import '../tests/keys/sign_verify_streaming'; import '../tests/keys/sign_verify_oneshot'; import '../tests/pbkdf2/pbkdf2_tests'; +import '../tests/ecdh/ecdh_tests'; +import '../tests/dh/dh_tests'; import '../tests/random/random_tests'; import '../tests/scrypt/scrypt_tests'; import '../tests/subtle/deriveBits'; diff --git a/example/src/tests/dh/dh_tests.ts b/example/src/tests/dh/dh_tests.ts new file mode 100644 index 00000000..d283a1ed --- /dev/null +++ b/example/src/tests/dh/dh_tests.ts @@ -0,0 +1,76 @@ +import { test } from '../util'; +import { Buffer } from '@craftzdog/react-native-buffer'; +import crypto from 'react-native-quick-crypto'; +import { assert } from 'chai'; + +const SUITE = 'dh'; + +test(SUITE, 'should create DiffieHellman with size', () => { + const dh = crypto.createDiffieHellman(512); + const prime = dh.getPrime(); + assert.isOk(prime); + // Size check approx + assert.isAtLeast(prime.length, 64); +}); + +test(SUITE, 'should create DiffieHellman with prime', () => { + // 512-bit prime (Group 1 from RFC 2409) + const prime = Buffer.from( + 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1' + + '29024E088A67CC74020BBEA63B139B22514A08798E3404DD' + + 'EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245' + + 'E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' + + 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381' + + 'FFFFFFFFFFFFFFFF', + 'hex', + ); + const generator = Buffer.from([2]); + const dh = crypto.createDiffieHellman(prime, generator); + + assert.strictEqual(dh.getPrime('hex'), prime.toString('hex').toLowerCase()); + assert.strictEqual( + dh.getGenerator('hex'), + generator.toString('hex').toLowerCase(), + ); +}); + +test(SUITE, 'should compute shared secret', () => { + const alice = crypto.createDiffieHellman(512); + const aliceKeys = alice.generateKeys(); + + const bob = crypto.createDiffieHellman( + alice.getPrime(), + alice.getGenerator(), + ); + const bobKeys = bob.generateKeys(); + + const aliceSecret = alice.computeSecret(bobKeys); + const bobSecret = bob.computeSecret(aliceKeys); + + assert.strictEqual(aliceSecret.toString('hex'), bobSecret.toString('hex')); +}); + +test(SUITE, 'should set keys', () => { + const alice = crypto.createDiffieHellman(512); + alice.generateKeys(); + + const dh2 = crypto.createDiffieHellman( + alice.getPrime(), + alice.getGenerator(), + ); + dh2.setPublicKey(alice.getPublicKey()); + dh2.setPrivateKey(alice.getPrivateKey()); + + assert.strictEqual(dh2.getPublicKey('hex'), alice.getPublicKey('hex')); + assert.strictEqual(dh2.getPrivateKey('hex'), alice.getPrivateKey('hex')); +}); + +test(SUITE, 'should create DiffieHellman from standard group', () => { + const dh = crypto.getDiffieHellman('modp14'); + assert.isOk(dh); + const prime = dh.getPrime(); + assert.isTrue(Buffer.isBuffer(prime)); + // modp14 is 2048-bit group + assert.strictEqual(prime.length, 256); + assert.strictEqual(dh.getGenerator('hex'), '02'); +}); diff --git a/example/src/tests/ecdh/ecdh_tests.ts b/example/src/tests/ecdh/ecdh_tests.ts new file mode 100644 index 00000000..7a1acf83 --- /dev/null +++ b/example/src/tests/ecdh/ecdh_tests.ts @@ -0,0 +1,75 @@ +import { test } from '../util'; +import { Buffer } from '@craftzdog/react-native-buffer'; +import crypto from 'react-native-quick-crypto'; +import { assert } from 'chai'; + +const SUITE = 'ecdh'; + +test(SUITE, 'should create ECDH instance with P-256', () => { + const ecdh = crypto.createECDH('prime256v1'); + assert.isOk(ecdh); +}); + +test(SUITE, 'should generate keys for P-256', () => { + const ecdh = crypto.createECDH('prime256v1'); + const keys = ecdh.generateKeys(); + assert.isOk(keys); + assert.isTrue(Buffer.isBuffer(keys), 'keys should be a Buffer'); + assert.isOk(ecdh.getPublicKey()); + assert.isOk(ecdh.getPrivateKey()); +}); + +test(SUITE, 'should switch between curves', () => { + const ecdh1 = crypto.createECDH('prime256v1'); + ecdh1.generateKeys(); + + const ecdh2 = crypto.createECDH('secp384r1'); + ecdh2.generateKeys(); + + assert.notEqual( + ecdh1.getPrivateKey().toString('hex'), + ecdh2.getPrivateKey().toString('hex'), + ); +}); + +test(SUITE, 'should compute shared secret', () => { + const alice = crypto.createECDH('prime256v1'); + alice.generateKeys(); + + const bob = crypto.createECDH('prime256v1'); + bob.generateKeys(); + + const aliceSecret = alice.computeSecret(bob.getPublicKey()); + const bobSecret = bob.computeSecret(alice.getPublicKey()); + + assert.strictEqual(aliceSecret.toString('hex'), bobSecret.toString('hex')); +}); + +test(SUITE, 'should set private key', () => { + const alice = crypto.createECDH('prime256v1'); + alice.generateKeys(); + const priv = alice.getPrivateKey(); + + const alice2 = crypto.createECDH('prime256v1'); + alice2.setPrivateKey(priv); + + // Public key should be derived/set (depending on impl, but usually settable) + // In our implementation setPrivateKey derives public key? + // Let's check consistency. + // If setPrivateKey derives public key, we can check it matches + const pub1 = alice.getPublicKey(); + const pub2 = alice2.getPublicKey(); + + assert.strictEqual(pub1.toString('hex'), pub2.toString('hex')); +}); + +test(SUITE, 'should work with string input', () => { + const alice = crypto.createECDH('prime256v1'); + alice.generateKeys(); + const bob = crypto.createECDH('prime256v1'); + bob.generateKeys(); + + const bobPubHex = bob.getPublicKey().toString('hex'); + const secret = alice.computeSecret(bobPubHex, 'hex'); + assert.isOk(secret); +}); diff --git a/packages/react-native-quick-crypto/QuickCrypto.podspec b/packages/react-native-quick-crypto/QuickCrypto.podspec index 683ecc2d..48776db7 100644 --- a/packages/react-native-quick-crypto/QuickCrypto.podspec +++ b/packages/react-native-quick-crypto/QuickCrypto.podspec @@ -132,6 +132,9 @@ Pod::Spec.new do |s| cpp_headers = [ "\"$(PODS_TARGET_SRCROOT)/cpp/utils\"", "\"$(PODS_TARGET_SRCROOT)/cpp/hkdf\"", + "\"$(PODS_TARGET_SRCROOT)/cpp/dh\"", + "\"$(PODS_TARGET_SRCROOT)/cpp/ecdh\"", + "\"$(PODS_TARGET_SRCROOT)/nitrogen/generated/shared/c++\"", "\"$(PODS_TARGET_SRCROOT)/deps/ncrypto/include\"", "\"$(PODS_TARGET_SRCROOT)/deps/blake3/c\"", "\"$(PODS_TARGET_SRCROOT)/deps/fastpbkdf2\"" diff --git a/packages/react-native-quick-crypto/android/CMakeLists.txt b/packages/react-native-quick-crypto/android/CMakeLists.txt index 4d6aae65..68fe558e 100644 --- a/packages/react-native-quick-crypto/android/CMakeLists.txt +++ b/packages/react-native-quick-crypto/android/CMakeLists.txt @@ -34,7 +34,9 @@ add_library( ../cpp/cipher/XSalsa20Cipher.cpp ../cpp/cipher/ChaCha20Cipher.cpp ../cpp/cipher/ChaCha20Poly1305Cipher.cpp + ../cpp/dh/HybridDiffieHellman.cpp ../cpp/ec/HybridEcKeyPair.cpp + ../cpp/ecdh/HybridECDH.cpp ../cpp/ed25519/HybridEdKeyPair.cpp ../cpp/hash/HybridHash.cpp ../cpp/hmac/HybridHmac.cpp @@ -62,7 +64,9 @@ include_directories( "src/main/cpp" "../cpp/blake3" "../cpp/cipher" + "../cpp/dh" "../cpp/ec" + "../cpp/ecdh" "../cpp/ed25519" "../cpp/hash" "../cpp/hkdf" diff --git a/packages/react-native-quick-crypto/cpp/dh/HybridDiffieHellman.cpp b/packages/react-native-quick-crypto/cpp/dh/HybridDiffieHellman.cpp new file mode 100644 index 00000000..da47bdfa --- /dev/null +++ b/packages/react-native-quick-crypto/cpp/dh/HybridDiffieHellman.cpp @@ -0,0 +1,293 @@ +#include "HybridDiffieHellman.hpp" +#include "Utils.hpp" +#include +#include +#include +#include +#include + +namespace margelo::nitro::crypto { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + +void HybridDiffieHellman::init(const std::shared_ptr& prime, const std::shared_ptr& generator) { + if (_pkey) { + EVP_PKEY_free(_pkey); + _pkey = nullptr; + } + + // Create DH parameters from prime and generator + DH* dh = DH_new(); + if (!dh) + throw std::runtime_error("Failed to create DH"); + + BIGNUM* p = BN_bin2bn(prime->data(), static_cast(prime->size()), nullptr); + BIGNUM* g = BN_bin2bn(generator->data(), static_cast(generator->size()), nullptr); + + if (!p || !g) { + DH_free(dh); + if (p) + BN_free(p); + if (g) + BN_free(g); + throw std::runtime_error("Failed to convert parameters to BIGNUM"); + } + + if (DH_set0_pqg(dh, p, nullptr, g) != 1) { + DH_free(dh); + BN_free(p); + BN_free(g); // DH_set0_pqg takes ownership only on success. + throw std::runtime_error("Failed to set DH parameters"); + } + + _pkey = EVP_PKEY_new(); + if (!_pkey) { + DH_free(dh); + throw std::runtime_error("Failed to create EVP_PKEY"); + } + + if (EVP_PKEY_assign_DH(_pkey, dh) != 1) { + EVP_PKEY_free(_pkey); + _pkey = nullptr; + DH_free(dh); // Assign takes ownership + throw std::runtime_error("Failed to assign DH to EVP_PKEY"); + } +} + +void HybridDiffieHellman::initWithSize(double primeLength, double generator) { + if (_pkey) { + EVP_PKEY_free(_pkey); + _pkey = nullptr; + } + + // Generate parameters + EVP_PKEY_CTX* pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_DH, nullptr); + if (!pctx) + throw std::runtime_error("Failed to create context"); + + if (EVP_PKEY_paramgen_init(pctx) <= 0) { + EVP_PKEY_CTX_free(pctx); + throw std::runtime_error("Failed to init paramgen"); + } + + if (EVP_PKEY_CTX_set_dh_paramgen_prime_len(pctx, (int)primeLength) <= 0) { + EVP_PKEY_CTX_free(pctx); + throw std::runtime_error("Failed to set prime length"); + } + + if (EVP_PKEY_CTX_set_dh_paramgen_generator(pctx, (int)generator) <= 0) { + EVP_PKEY_CTX_free(pctx); + throw std::runtime_error("Failed to set generator"); + } + + EVP_PKEY* params = nullptr; + if (EVP_PKEY_paramgen(pctx, ¶ms) <= 0) { + EVP_PKEY_CTX_free(pctx); + throw std::runtime_error("Failed to generate parameters"); + } + + EVP_PKEY_CTX_free(pctx); + _pkey = params; +} + +std::shared_ptr HybridDiffieHellman::generateKeys() { + ensureInitialized(); + + EVP_PKEY_CTX* kctx = EVP_PKEY_CTX_new(_pkey, nullptr); + if (!kctx) + throw std::runtime_error("Failed to create keygen context"); + + if (EVP_PKEY_keygen_init(kctx) <= 0) { + EVP_PKEY_CTX_free(kctx); + throw std::runtime_error("Failed to init keygen"); + } + + EVP_PKEY* new_key = nullptr; + if (EVP_PKEY_keygen(kctx, &new_key) <= 0) { + EVP_PKEY_CTX_free(kctx); + throw std::runtime_error("Failed to generate key"); + } + + EVP_PKEY_CTX_free(kctx); + + // Replace parameters-only key with full key (which includes parameters) + EVP_PKEY_free(_pkey); + _pkey = new_key; + + return getPublicKey(); +} + +std::shared_ptr HybridDiffieHellman::computeSecret(const std::shared_ptr& otherPublicKey) { + ensureInitialized(); + + // Create peer key from public key buffer + // We need to create a new EVP_PKEY with the same parameters as ours, but with the peer's public key. + + const DH* our_dh = EVP_PKEY_get0_DH(_pkey); + if (!our_dh) + throw std::runtime_error("Not a DH key"); + + const BIGNUM *p, *q, *g; + DH_get0_pqg(our_dh, &p, &q, &g); + + DH* peer_dh = DH_new(); + BIGNUM* peer_p = BN_dup(p); + BIGNUM* peer_g = BN_dup(g); + BIGNUM* peer_pub_key = BN_bin2bn(otherPublicKey->data(), static_cast(otherPublicKey->size()), nullptr); + + if (!peer_dh || !peer_p || !peer_g || !peer_pub_key) { + DH_free(peer_dh); + BN_free(peer_p); + BN_free(peer_g); + BN_free(peer_pub_key); + throw std::runtime_error("Failed to create peer DH"); + } + + DH_set0_pqg(peer_dh, peer_p, nullptr, peer_g); + DH_set0_key(peer_dh, peer_pub_key, nullptr); + + EVP_PKEY* peer_pkey = EVP_PKEY_new(); + EVP_PKEY_assign_DH(peer_pkey, peer_dh); + + // Derive + EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(_pkey, nullptr); + if (!ctx || EVP_PKEY_derive_init(ctx) <= 0 || EVP_PKEY_derive_set_peer(ctx, peer_pkey) <= 0) { + if (ctx) + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(peer_pkey); + throw std::runtime_error("Failed to init derive"); + } + + size_t secret_len; + if (EVP_PKEY_derive(ctx, nullptr, &secret_len) <= 0) { + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(peer_pkey); + throw std::runtime_error("Failed to get secret length"); + } + + std::vector secret(secret_len); + if (EVP_PKEY_derive(ctx, secret.data(), &secret_len) <= 0) { + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(peer_pkey); + throw std::runtime_error("Failed to derive"); + } + + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(peer_pkey); + + return ToNativeArrayBuffer(std::string(secret.begin(), secret.end())); +} + +std::shared_ptr HybridDiffieHellman::getPrime() { + ensureInitialized(); + const DH* dh = EVP_PKEY_get0_DH(_pkey); + const BIGNUM *p, *q, *g; + DH_get0_pqg(dh, &p, &q, &g); + if (!p) + throw std::runtime_error("No prime"); + + int len = BN_num_bytes(p); + std::vector buf(len); + BN_bn2bin(p, buf.data()); + return ToNativeArrayBuffer(std::string(buf.begin(), buf.end())); +} + +std::shared_ptr HybridDiffieHellman::getGenerator() { + ensureInitialized(); + const DH* dh = EVP_PKEY_get0_DH(_pkey); + const BIGNUM *p, *q, *g; + DH_get0_pqg(dh, &p, &q, &g); + if (!g) + throw std::runtime_error("No generator"); + + int len = BN_num_bytes(g); + std::vector buf(len); + BN_bn2bin(g, buf.data()); + return ToNativeArrayBuffer(std::string(buf.begin(), buf.end())); +} + +std::shared_ptr HybridDiffieHellman::getPublicKey() { + ensureInitialized(); + const DH* dh = EVP_PKEY_get0_DH(_pkey); + const BIGNUM *pub, *priv; + DH_get0_key(dh, &pub, &priv); + if (!pub) + throw std::runtime_error("No public key"); + + int len = BN_num_bytes(pub); + std::vector buf(len); + BN_bn2bin(pub, buf.data()); + return ToNativeArrayBuffer(std::string(buf.begin(), buf.end())); +} + +std::shared_ptr HybridDiffieHellman::getPrivateKey() { + ensureInitialized(); + const DH* dh = EVP_PKEY_get0_DH(_pkey); + const BIGNUM *pub, *priv; + DH_get0_key(dh, &pub, &priv); + if (!priv) + throw std::runtime_error("No private key"); + + int len = BN_num_bytes(priv); + std::vector buf(len); + BN_bn2bin(priv, buf.data()); + return ToNativeArrayBuffer(std::string(buf.begin(), buf.end())); +} + +void HybridDiffieHellman::setPublicKey(const std::shared_ptr& publicKey) { + ensureInitialized(); + const DH* dh = EVP_PKEY_get0_DH(_pkey); + BIGNUM* pub = BN_bin2bn(publicKey->data(), static_cast(publicKey->size()), nullptr); + if (!pub) + throw std::runtime_error("Failed to convert public key"); + + // We need to keep private key if it exists + const BIGNUM *old_pub, *old_priv; + DH_get0_key(dh, &old_pub, &old_priv); + + BIGNUM* priv = old_priv ? BN_dup(old_priv) : nullptr; + + // Since dh is const, we need to replace the whole key or cast away const (dangerous). + // Better: Create new DH, copy params, set new keys, replace EVP_PKEY. + DH* new_dh = DH_new(); + const BIGNUM *p, *q, *g; + DH_get0_pqg(dh, &p, &q, &g); + DH_set0_pqg(new_dh, BN_dup(p), q ? BN_dup(q) : nullptr, BN_dup(g)); + DH_set0_key(new_dh, pub, priv); + + EVP_PKEY_free(_pkey); + _pkey = EVP_PKEY_new(); + EVP_PKEY_assign_DH(_pkey, new_dh); +} + +void HybridDiffieHellman::setPrivateKey(const std::shared_ptr& privateKey) { + ensureInitialized(); + const DH* dh = EVP_PKEY_get0_DH(_pkey); + BIGNUM* priv = BN_bin2bn(privateKey->data(), static_cast(privateKey->size()), nullptr); + if (!priv) + throw std::runtime_error("Failed to convert private key"); + + // We need to keep public key if it exists + const BIGNUM *old_pub, *old_priv; + DH_get0_key(dh, &old_pub, &old_priv); + + BIGNUM* pub = old_pub ? BN_dup(old_pub) : nullptr; + + DH* new_dh = DH_new(); + const BIGNUM *p, *q, *g; + DH_get0_pqg(dh, &p, &q, &g); + DH_set0_pqg(new_dh, BN_dup(p), q ? BN_dup(q) : nullptr, BN_dup(g)); + DH_set0_key(new_dh, pub, priv); + + EVP_PKEY_free(_pkey); + _pkey = EVP_PKEY_new(); + EVP_PKEY_assign_DH(_pkey, new_dh); +} + +void HybridDiffieHellman::ensureInitialized() { + if (!_pkey) + throw std::runtime_error("DiffieHellman not initialized"); +} + +#pragma clang diagnostic pop +} // namespace margelo::nitro::crypto diff --git a/packages/react-native-quick-crypto/cpp/dh/HybridDiffieHellman.hpp b/packages/react-native-quick-crypto/cpp/dh/HybridDiffieHellman.hpp new file mode 100644 index 00000000..02acfa48 --- /dev/null +++ b/packages/react-native-quick-crypto/cpp/dh/HybridDiffieHellman.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include +#include + +#include "HybridDiffieHellmanSpec.hpp" + +namespace margelo::nitro::crypto { + +using namespace facebook; +using margelo::nitro::ArrayBuffer; + +class HybridDiffieHellman : public HybridDiffieHellmanSpec { + public: + HybridDiffieHellman() : HybridObject("DiffieHellman") {} + virtual ~HybridDiffieHellman() { + if (_pkey) { + EVP_PKEY_free(_pkey); + _pkey = nullptr; + } + } + + void init(const std::shared_ptr& prime, const std::shared_ptr& generator) override; + void initWithSize(double primeLength, double generator) override; + std::shared_ptr generateKeys() override; + std::shared_ptr computeSecret(const std::shared_ptr& otherPublicKey) override; + std::shared_ptr getPrime() override; + std::shared_ptr getGenerator() override; + std::shared_ptr getPublicKey() override; + std::shared_ptr getPrivateKey() override; + void setPublicKey(const std::shared_ptr& publicKey) override; + void setPrivateKey(const std::shared_ptr& privateKey) override; + + private: + EVP_PKEY* _pkey = nullptr; + + void ensureInitialized(); +}; + +} // namespace margelo::nitro::crypto diff --git a/packages/react-native-quick-crypto/cpp/ecdh/HybridECDH.cpp b/packages/react-native-quick-crypto/cpp/ecdh/HybridECDH.cpp new file mode 100644 index 00000000..f02dc8a7 --- /dev/null +++ b/packages/react-native-quick-crypto/cpp/ecdh/HybridECDH.cpp @@ -0,0 +1,222 @@ +#include "HybridECDH.hpp" +#include "Utils.hpp" +#include +#include +#include +#include +#include +#include + +namespace margelo::nitro::crypto { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + +void HybridECDH::init(const std::string& curveName) { + _curveName = curveName; + _curveNid = getCurveNid(curveName); + if (_curveNid == NID_undef) { + throw std::runtime_error("Unknown curve name: " + curveName); + } + + // Clear previous key if any + if (_pkey) { + EVP_PKEY_free(_pkey); + _pkey = nullptr; + } + + if (_group) { + EC_GROUP_free(_group); + _group = nullptr; + } + + _group = EC_GROUP_new_by_curve_name(_curveNid); + if (!_group) { + throw std::runtime_error("Failed to create EC group for curve: " + curveName); + } +} + +std::shared_ptr HybridECDH::generateKeys() { + ensureInitialized(); + + std::unique_ptr ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr), EVP_PKEY_CTX_free); + if (!ctx) + throw std::runtime_error("Failed to create context"); + + if (EVP_PKEY_keygen_init(ctx.get()) <= 0) + throw std::runtime_error("Failed to init keygen"); + if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx.get(), _curveNid) <= 0) + throw std::runtime_error("Failed to set curve"); + + EVP_PKEY* pkey = nullptr; + if (EVP_PKEY_keygen(ctx.get(), &pkey) <= 0) + throw std::runtime_error("Failed to generate key"); + + if (_pkey) + EVP_PKEY_free(_pkey); + _pkey = pkey; + + return getPublicKey(); +} + +std::shared_ptr HybridECDH::computeSecret(const std::shared_ptr& otherPublicKey) { + ensureInitialized(); + if (!_pkey) + throw std::runtime_error("Private key not set"); + + // Create peer key from buffer + const unsigned char* p = otherPublicKey->data(); + // Node.js usually passes uncompressed or compressed point. We need to convert it to EVP_PKEY. + // We can use EC_KEY and then assign to EVP_PKEY. + + // Use cached group + // std::unique_ptr group(EC_GROUP_new_by_curve_name(_curveNid), EC_GROUP_free); + // if (!group) + // throw std::runtime_error("Failed to create group"); + + std::unique_ptr point(EC_POINT_new(_group), EC_POINT_free); + if (!point) + throw std::runtime_error("Failed to create point"); + + if (EC_POINT_oct2point(_group, point.get(), p, otherPublicKey->size(), nullptr) != 1) { + throw std::runtime_error("Failed to decode public key point"); + } + + std::unique_ptr ec_key(EC_KEY_new(), EC_KEY_free); + if (!ec_key) + throw std::runtime_error("Failed to create EC_KEY"); + if (EC_KEY_set_group(ec_key.get(), _group) != 1) + throw std::runtime_error("Failed to set group"); + if (EC_KEY_set_public_key(ec_key.get(), point.get()) != 1) + throw std::runtime_error("Failed to set public key"); + + std::unique_ptr peer_pkey(EVP_PKEY_new(), EVP_PKEY_free); + if (EVP_PKEY_assign_EC_KEY(peer_pkey.get(), ec_key.release()) != 1) + throw std::runtime_error("Failed to assign EC_KEY"); + + // Derive + std::unique_ptr ctx(EVP_PKEY_CTX_new(_pkey, nullptr), EVP_PKEY_CTX_free); + if (!ctx) + throw std::runtime_error("Failed to create derive context"); + + if (EVP_PKEY_derive_init(ctx.get()) <= 0) + throw std::runtime_error("Failed to init derive"); + if (EVP_PKEY_derive_set_peer(ctx.get(), peer_pkey.get()) <= 0) + throw std::runtime_error("Failed to set peer key"); + + size_t secret_len; + if (EVP_PKEY_derive(ctx.get(), nullptr, &secret_len) <= 0) + throw std::runtime_error("Failed to get secret length"); + + std::vector secret(secret_len); + if (EVP_PKEY_derive(ctx.get(), secret.data(), &secret_len) <= 0) + throw std::runtime_error("Failed to derive secret"); + + return ToNativeArrayBuffer(std::string(secret.begin(), secret.end())); // Utils.hpp +} + +std::shared_ptr HybridECDH::getPrivateKey() { + if (!_pkey) + throw std::runtime_error("No key set"); + // Implement logic to extract private key (usually big number or buffer) + const EC_KEY* ec = EVP_PKEY_get0_EC_KEY(_pkey); + if (!ec) + throw std::runtime_error("Not an EC key"); + const BIGNUM* priv = EC_KEY_get0_private_key(ec); + if (!priv) + throw std::runtime_error("No private key component"); + + int len = BN_num_bytes(priv); + std::vector buf(len); + BN_bn2bin(priv, buf.data()); + return ToNativeArrayBuffer(std::string(buf.begin(), buf.end())); +} + +void HybridECDH::setPrivateKey(const std::shared_ptr& privateKey) { + ensureInitialized(); + // Use cached group + // auto group = std::unique_ptr(EC_GROUP_new_by_curve_name(_curveNid), EC_GROUP_free); + auto ec = std::unique_ptr(EC_KEY_new(), EC_KEY_free); + EC_KEY_set_group(ec.get(), _group); + + BIGNUM* priv_bn = BN_bin2bn(privateKey->data(), static_cast(privateKey->size()), nullptr); + if (!priv_bn) + throw std::runtime_error("Failed to convert private key"); + // Should set public key too if possible, but Node.js allows just private. + + // Calculate public key from private + auto point = std::unique_ptr(EC_POINT_new(_group), EC_POINT_free); + EC_POINT_mul(_group, point.get(), priv_bn, nullptr, nullptr, nullptr); + + EC_KEY_set_private_key(ec.get(), priv_bn); + EC_KEY_set_public_key(ec.get(), point.get()); + BN_free(priv_bn); + + if (_pkey) + EVP_PKEY_free(_pkey); + _pkey = EVP_PKEY_new(); + EVP_PKEY_assign_EC_KEY(_pkey, ec.release()); +} + +std::shared_ptr HybridECDH::getPublicKey() { + if (!_pkey) + throw std::runtime_error("No key set"); + const EC_KEY* ec = EVP_PKEY_get0_EC_KEY(_pkey); + if (!ec) + throw std::runtime_error("Not an EC key"); + const EC_POINT* point = EC_KEY_get0_public_key(ec); + const EC_GROUP* group = EC_KEY_get0_group(ec); + if (!point || !group) + throw std::runtime_error("Incomplete key"); + + // Default to uncompressed + size_t len = EC_POINT_point2oct(group, point, POINT_CONVERSION_UNCOMPRESSED, nullptr, 0, nullptr); + if (len == 0) + throw std::runtime_error("Failed to get pubkey length"); + std::vector buf(len); + if (EC_POINT_point2oct(group, point, POINT_CONVERSION_UNCOMPRESSED, buf.data(), len, nullptr) == 0) { + throw std::runtime_error("Failed to encode pubkey"); + } + return ToNativeArrayBuffer(std::string(buf.begin(), buf.end())); +} + +void HybridECDH::setPublicKey(const std::shared_ptr& publicKey) { + ensureInitialized(); + // Cannot set ONLY public key on an ECDH object usually intended for own keys, + // but Node.js allows it? Or is this for "other" key usually? + // Node.js setPublicKey sets the public key of the ECDH object. + + // Use cached group + // auto group = std::unique_ptr(EC_GROUP_new_by_curve_name(_curveNid), EC_GROUP_free); + auto point = std::unique_ptr(EC_POINT_new(_group), EC_POINT_free); + + if (EC_POINT_oct2point(_group, point.get(), (const unsigned char*)publicKey->data(), publicKey->size(), nullptr) != 1) { + throw std::runtime_error("Invalid public key"); + } + + auto ec = std::unique_ptr(EC_KEY_new(), EC_KEY_free); + EC_KEY_set_group(ec.get(), _group); + EC_KEY_set_public_key(ec.get(), point.get()); + + if (_pkey) + EVP_PKEY_free(_pkey); + _pkey = EVP_PKEY_new(); + EVP_PKEY_assign_EC_KEY(_pkey, ec.release()); +} + +void HybridECDH::ensureInitialized() { + if (_curveNid == 0 || !_group) { + throw std::runtime_error("ECDH not initialized"); + } +} + +int HybridECDH::getCurveNid(const std::string& name) { + int nid = OBJ_txt2nid(name.c_str()); + if (nid == NID_undef) + nid = OBJ_sn2nid(name.c_str()); + if (nid == NID_undef) + nid = OBJ_ln2nid(name.c_str()); + return nid; +} + +#pragma clang diagnostic pop +} // namespace margelo::nitro::crypto diff --git a/packages/react-native-quick-crypto/cpp/ecdh/HybridECDH.hpp b/packages/react-native-quick-crypto/cpp/ecdh/HybridECDH.hpp new file mode 100644 index 00000000..a77effd2 --- /dev/null +++ b/packages/react-native-quick-crypto/cpp/ecdh/HybridECDH.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#include "HybridECDHSpec.hpp" + +namespace margelo::nitro::crypto { + +using namespace facebook; +using margelo::nitro::ArrayBuffer; + +class HybridECDH : public HybridECDHSpec { + public: + HybridECDH() : HybridObject("ECDH") {} + virtual ~HybridECDH() { + if (_pkey) { + EVP_PKEY_free(_pkey); + _pkey = nullptr; + } + if (_group) { + EC_GROUP_free(_group); + _group = nullptr; + } + } + + void init(const std::string& curveName) override; + std::shared_ptr generateKeys() override; + std::shared_ptr computeSecret(const std::shared_ptr& otherPublicKey) override; + std::shared_ptr getPrivateKey() override; + void setPrivateKey(const std::shared_ptr& privateKey) override; + std::shared_ptr getPublicKey() override; + void setPublicKey(const std::shared_ptr& publicKey) override; + + private: + EVP_PKEY* _pkey = nullptr; + EC_GROUP* _group = nullptr; + std::string _curveName; + int _curveNid = 0; + + void ensureInitialized(); + int getCurveNid(const std::string& name); +}; + +} // namespace margelo::nitro::crypto diff --git a/packages/react-native-quick-crypto/nitro.json b/packages/react-native-quick-crypto/nitro.json index 8750b65b..85ae64a4 100644 --- a/packages/react-native-quick-crypto/nitro.json +++ b/packages/react-native-quick-crypto/nitro.json @@ -1,31 +1,80 @@ { - "cxxNamespace": ["crypto"], + "cxxNamespace": [ + "crypto" + ], "ios": { "iosModuleName": "QuickCrypto" }, "android": { - "androidNamespace": ["crypto"], + "androidNamespace": [ + "crypto" + ], "androidCxxLibName": "QuickCrypto" }, "autolinking": { - "Blake3": { "cpp": "HybridBlake3" }, - "Cipher": { "cpp": "HybridCipher" }, - "CipherFactory": { "cpp": "HybridCipherFactory" }, - "EcKeyPair": { "cpp": "HybridEcKeyPair" }, - "EdKeyPair": { "cpp": "HybridEdKeyPair" }, - "Hash": { "cpp": "HybridHash" }, - "Hmac": { "cpp": "HybridHmac" }, - "Hkdf": { "cpp": "HybridHkdf" }, - "KeyObjectHandle": { "cpp": "HybridKeyObjectHandle" }, - "Pbkdf2": { "cpp": "HybridPbkdf2" }, - "Random": { "cpp": "HybridRandom" }, - "RsaCipher": { "cpp": "HybridRsaCipher" }, - "RsaKeyPair": { "cpp": "HybridRsaKeyPair" }, - "SignHandle": { "cpp": "HybridSignHandle" }, - "VerifyHandle": { "cpp": "HybridVerifyHandle" }, - "MlDsaKeyPair": { "cpp": "HybridMlDsaKeyPair" }, - "Scrypt": { "cpp": "HybridScrypt" }, - "Utils": { "cpp": "HybridUtils" } + "Blake3": { + "cpp": "HybridBlake3" + }, + "Cipher": { + "cpp": "HybridCipher" + }, + "CipherFactory": { + "cpp": "HybridCipherFactory" + }, + "EcKeyPair": { + "cpp": "HybridEcKeyPair" + }, + "EdKeyPair": { + "cpp": "HybridEdKeyPair" + }, + "Hash": { + "cpp": "HybridHash" + }, + "Hmac": { + "cpp": "HybridHmac" + }, + "Hkdf": { + "cpp": "HybridHkdf" + }, + "KeyObjectHandle": { + "cpp": "HybridKeyObjectHandle" + }, + "Pbkdf2": { + "cpp": "HybridPbkdf2" + }, + "Random": { + "cpp": "HybridRandom" + }, + "RsaCipher": { + "cpp": "HybridRsaCipher" + }, + "RsaKeyPair": { + "cpp": "HybridRsaKeyPair" + }, + "SignHandle": { + "cpp": "HybridSignHandle" + }, + "VerifyHandle": { + "cpp": "HybridVerifyHandle" + }, + "MlDsaKeyPair": { + "cpp": "HybridMlDsaKeyPair" + }, + "Scrypt": { + "cpp": "HybridScrypt" + }, + "Utils": { + "cpp": "HybridUtils" + }, + "ECDH": { + "cpp": "HybridECDH" + }, + "DiffieHellman": { + "cpp": "HybridDiffieHellman" + } }, - "ignorePaths": ["node_modules", "lib"] -} + "ignorePaths": [ + "node_modules", + "lib" + ] +} \ No newline at end of file diff --git a/packages/react-native-quick-crypto/nitrogen/generated/android/QuickCrypto+autolinking.cmake b/packages/react-native-quick-crypto/nitrogen/generated/android/QuickCrypto+autolinking.cmake index ee30c221..355473e6 100644 --- a/packages/react-native-quick-crypto/nitrogen/generated/android/QuickCrypto+autolinking.cmake +++ b/packages/react-native-quick-crypto/nitrogen/generated/android/QuickCrypto+autolinking.cmake @@ -30,6 +30,8 @@ target_sources( ../nitrogen/generated/shared/c++/HybridBlake3Spec.cpp ../nitrogen/generated/shared/c++/HybridCipherSpec.cpp ../nitrogen/generated/shared/c++/HybridCipherFactorySpec.cpp + ../nitrogen/generated/shared/c++/HybridDiffieHellmanSpec.cpp + ../nitrogen/generated/shared/c++/HybridECDHSpec.cpp ../nitrogen/generated/shared/c++/HybridEcKeyPairSpec.cpp ../nitrogen/generated/shared/c++/HybridEdKeyPairSpec.cpp ../nitrogen/generated/shared/c++/HybridHashSpec.cpp diff --git a/packages/react-native-quick-crypto/nitrogen/generated/android/QuickCryptoOnLoad.cpp b/packages/react-native-quick-crypto/nitrogen/generated/android/QuickCryptoOnLoad.cpp index f51f515e..e4a32c35 100644 --- a/packages/react-native-quick-crypto/nitrogen/generated/android/QuickCryptoOnLoad.cpp +++ b/packages/react-native-quick-crypto/nitrogen/generated/android/QuickCryptoOnLoad.cpp @@ -33,6 +33,8 @@ #include "HybridMlDsaKeyPair.hpp" #include "HybridScrypt.hpp" #include "HybridUtils.hpp" +#include "HybridECDH.hpp" +#include "HybridDiffieHellman.hpp" namespace margelo::nitro::crypto { @@ -208,6 +210,24 @@ int initialize(JavaVM* vm) { return std::make_shared(); } ); + HybridObjectRegistry::registerHybridObjectConstructor( + "ECDH", + []() -> std::shared_ptr { + static_assert(std::is_default_constructible_v, + "The HybridObject \"HybridECDH\" is not default-constructible! " + "Create a public constructor that takes zero arguments to be able to autolink this HybridObject."); + return std::make_shared(); + } + ); + HybridObjectRegistry::registerHybridObjectConstructor( + "DiffieHellman", + []() -> std::shared_ptr { + static_assert(std::is_default_constructible_v, + "The HybridObject \"HybridDiffieHellman\" is not default-constructible! " + "Create a public constructor that takes zero arguments to be able to autolink this HybridObject."); + return std::make_shared(); + } + ); }); } diff --git a/packages/react-native-quick-crypto/nitrogen/generated/ios/QuickCryptoAutolinking.mm b/packages/react-native-quick-crypto/nitrogen/generated/ios/QuickCryptoAutolinking.mm index 3b6c45e4..f952d785 100644 --- a/packages/react-native-quick-crypto/nitrogen/generated/ios/QuickCryptoAutolinking.mm +++ b/packages/react-native-quick-crypto/nitrogen/generated/ios/QuickCryptoAutolinking.mm @@ -28,6 +28,8 @@ #include "HybridMlDsaKeyPair.hpp" #include "HybridScrypt.hpp" #include "HybridUtils.hpp" +#include "HybridECDH.hpp" +#include "HybridDiffieHellman.hpp" @interface QuickCryptoAutolinking : NSObject @end @@ -200,6 +202,24 @@ + (void) load { return std::make_shared(); } ); + HybridObjectRegistry::registerHybridObjectConstructor( + "ECDH", + []() -> std::shared_ptr { + static_assert(std::is_default_constructible_v, + "The HybridObject \"HybridECDH\" is not default-constructible! " + "Create a public constructor that takes zero arguments to be able to autolink this HybridObject."); + return std::make_shared(); + } + ); + HybridObjectRegistry::registerHybridObjectConstructor( + "DiffieHellman", + []() -> std::shared_ptr { + static_assert(std::is_default_constructible_v, + "The HybridObject \"HybridDiffieHellman\" is not default-constructible! " + "Create a public constructor that takes zero arguments to be able to autolink this HybridObject."); + return std::make_shared(); + } + ); } @end diff --git a/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridDiffieHellmanSpec.cpp b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridDiffieHellmanSpec.cpp new file mode 100644 index 00000000..51e5a9ab --- /dev/null +++ b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridDiffieHellmanSpec.cpp @@ -0,0 +1,30 @@ +/// +/// HybridDiffieHellmanSpec.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#include "HybridDiffieHellmanSpec.hpp" + +namespace margelo::nitro::crypto { + + void HybridDiffieHellmanSpec::loadHybridMethods() { + // load base methods/properties + HybridObject::loadHybridMethods(); + // load custom methods/properties + registerHybrids(this, [](Prototype& prototype) { + prototype.registerHybridMethod("init", &HybridDiffieHellmanSpec::init); + prototype.registerHybridMethod("initWithSize", &HybridDiffieHellmanSpec::initWithSize); + prototype.registerHybridMethod("generateKeys", &HybridDiffieHellmanSpec::generateKeys); + prototype.registerHybridMethod("computeSecret", &HybridDiffieHellmanSpec::computeSecret); + prototype.registerHybridMethod("getPrime", &HybridDiffieHellmanSpec::getPrime); + prototype.registerHybridMethod("getGenerator", &HybridDiffieHellmanSpec::getGenerator); + prototype.registerHybridMethod("getPublicKey", &HybridDiffieHellmanSpec::getPublicKey); + prototype.registerHybridMethod("getPrivateKey", &HybridDiffieHellmanSpec::getPrivateKey); + prototype.registerHybridMethod("setPublicKey", &HybridDiffieHellmanSpec::setPublicKey); + prototype.registerHybridMethod("setPrivateKey", &HybridDiffieHellmanSpec::setPrivateKey); + }); + } + +} // namespace margelo::nitro::crypto diff --git a/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridDiffieHellmanSpec.hpp b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridDiffieHellmanSpec.hpp new file mode 100644 index 00000000..ca2162ec --- /dev/null +++ b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridDiffieHellmanSpec.hpp @@ -0,0 +1,72 @@ +/// +/// HybridDiffieHellmanSpec.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif + +// Forward declaration of `ArrayBuffer` to properly resolve imports. +namespace NitroModules { class ArrayBuffer; } + +#include + +namespace margelo::nitro::crypto { + + using namespace margelo::nitro; + + /** + * An abstract base class for `DiffieHellman` + * Inherit this class to create instances of `HybridDiffieHellmanSpec` in C++. + * You must explicitly call `HybridObject`'s constructor yourself, because it is virtual. + * @example + * ```cpp + * class HybridDiffieHellman: public HybridDiffieHellmanSpec { + * public: + * HybridDiffieHellman(...): HybridObject(TAG) { ... } + * // ... + * }; + * ``` + */ + class HybridDiffieHellmanSpec: public virtual HybridObject { + public: + // Constructor + explicit HybridDiffieHellmanSpec(): HybridObject(TAG) { } + + // Destructor + ~HybridDiffieHellmanSpec() override = default; + + public: + // Properties + + + public: + // Methods + virtual void init(const std::shared_ptr& prime, const std::shared_ptr& generator) = 0; + virtual void initWithSize(double primeLength, double generator) = 0; + virtual std::shared_ptr generateKeys() = 0; + virtual std::shared_ptr computeSecret(const std::shared_ptr& otherPublicKey) = 0; + virtual std::shared_ptr getPrime() = 0; + virtual std::shared_ptr getGenerator() = 0; + virtual std::shared_ptr getPublicKey() = 0; + virtual std::shared_ptr getPrivateKey() = 0; + virtual void setPublicKey(const std::shared_ptr& publicKey) = 0; + virtual void setPrivateKey(const std::shared_ptr& privateKey) = 0; + + protected: + // Hybrid Setup + void loadHybridMethods() override; + + protected: + // Tag for logging + static constexpr auto TAG = "DiffieHellman"; + }; + +} // namespace margelo::nitro::crypto diff --git a/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridECDHSpec.cpp b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridECDHSpec.cpp new file mode 100644 index 00000000..b25dcdd9 --- /dev/null +++ b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridECDHSpec.cpp @@ -0,0 +1,27 @@ +/// +/// HybridECDHSpec.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#include "HybridECDHSpec.hpp" + +namespace margelo::nitro::crypto { + + void HybridECDHSpec::loadHybridMethods() { + // load base methods/properties + HybridObject::loadHybridMethods(); + // load custom methods/properties + registerHybrids(this, [](Prototype& prototype) { + prototype.registerHybridMethod("init", &HybridECDHSpec::init); + prototype.registerHybridMethod("generateKeys", &HybridECDHSpec::generateKeys); + prototype.registerHybridMethod("computeSecret", &HybridECDHSpec::computeSecret); + prototype.registerHybridMethod("getPrivateKey", &HybridECDHSpec::getPrivateKey); + prototype.registerHybridMethod("setPrivateKey", &HybridECDHSpec::setPrivateKey); + prototype.registerHybridMethod("getPublicKey", &HybridECDHSpec::getPublicKey); + prototype.registerHybridMethod("setPublicKey", &HybridECDHSpec::setPublicKey); + }); + } + +} // namespace margelo::nitro::crypto diff --git a/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridECDHSpec.hpp b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridECDHSpec.hpp new file mode 100644 index 00000000..ab0a4157 --- /dev/null +++ b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridECDHSpec.hpp @@ -0,0 +1,70 @@ +/// +/// HybridECDHSpec.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif + +// Forward declaration of `ArrayBuffer` to properly resolve imports. +namespace NitroModules { class ArrayBuffer; } + +#include +#include + +namespace margelo::nitro::crypto { + + using namespace margelo::nitro; + + /** + * An abstract base class for `ECDH` + * Inherit this class to create instances of `HybridECDHSpec` in C++. + * You must explicitly call `HybridObject`'s constructor yourself, because it is virtual. + * @example + * ```cpp + * class HybridECDH: public HybridECDHSpec { + * public: + * HybridECDH(...): HybridObject(TAG) { ... } + * // ... + * }; + * ``` + */ + class HybridECDHSpec: public virtual HybridObject { + public: + // Constructor + explicit HybridECDHSpec(): HybridObject(TAG) { } + + // Destructor + ~HybridECDHSpec() override = default; + + public: + // Properties + + + public: + // Methods + virtual void init(const std::string& curveName) = 0; + virtual std::shared_ptr generateKeys() = 0; + virtual std::shared_ptr computeSecret(const std::shared_ptr& otherPublicKey) = 0; + virtual std::shared_ptr getPrivateKey() = 0; + virtual void setPrivateKey(const std::shared_ptr& privateKey) = 0; + virtual std::shared_ptr getPublicKey() = 0; + virtual void setPublicKey(const std::shared_ptr& publicKey) = 0; + + protected: + // Hybrid Setup + void loadHybridMethods() override; + + protected: + // Tag for logging + static constexpr auto TAG = "ECDH"; + }; + +} // namespace margelo::nitro::crypto diff --git a/packages/react-native-quick-crypto/src/dh-groups.ts b/packages/react-native-quick-crypto/src/dh-groups.ts new file mode 100644 index 00000000..2add6a9d --- /dev/null +++ b/packages/react-native-quick-crypto/src/dh-groups.ts @@ -0,0 +1,27 @@ +export const DH_GROUPS: Record = { + modp14: { + prime: + 'ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aacaa68ffffffffffffffff', + generator: '02', + }, + modp15: { + prime: + 'ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aaac42dad33170d04507a33a85521abdf1cba64ecfb850458dbef0a8aea71575d060c7db3970f85a6e1e4c7abf5ae8cdb0933d71e8c94e04a25619dcee3d2261ad2ee6bf12ffa06d98a0864d87602733ec86a64521f2b18177b200cbbe117577a615d6c770988c0bad946e208e24fa074e5ab3143db5bfce0fd108e4b82d120a93ad2caffffffffffffffff', + generator: '02', + }, + modp16: { + prime: + 'ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aaac42dad33170d04507a33a85521abdf1cba64ecfb850458dbef0a8aea71575d060c7db3970f85a6e1e4c7abf5ae8cdb0933d71e8c94e04a25619dcee3d2261ad2ee6bf12ffa06d98a0864d87602733ec86a64521f2b18177b200cbbe117577a615d6c770988c0bad946e208e24fa074e5ab3143db5bfce0fd108e4b82d120a92108011a723c12a787e6d788719a10bdba5b2699c327186af4e23c1a946834b6150bda2583e9ca2ad44ce8dbbbc2db04de8ef92e8efc141fbecaa6287c59474e6bc05d99b2964fa090c3a2233ba186515be7ed1f612970cee2d7afb81bdd762170481cd0069127d5b05aa993b4ea988d8fddc186ffb7dc90a6c08f4df435c934063199ffffffffffffffff', + generator: '02', + }, + modp17: { + prime: + 'ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aaac42dad33170d04507a33a85521abdf1cba64ecfb850458dbef0a8aea71575d060c7db3970f85a6e1e4c7abf5ae8cdb0933d71e8c94e04a25619dcee3d2261ad2ee6bf12ffa06d98a0864d87602733ec86a64521f2b18177b200cbbe117577a615d6c770988c0bad946e208e24fa074e5ab3143db5bfce0fd108e4b82d120a92108011a723c12a787e6d788719a10bdba5b2699c327186af4e23c1a946834b6150bda2583e9ca2ad44ce8dbbbc2db04de8ef92e8efc141fbecaa6287c59474e6bc05d99b2964fa090c3a2233ba186515be7ed1f612970cee2d7afb81bdd762170481cd0069127d5b05aa993b4ea988d8fddc186ffb7dc90a6c08f4df435c93402849236c3fab4d27c7026c1d4dcb2602646dec9751e763dba37bdf8ff9406ad9e530ee5db382f413001aeb06a53ed9027d831179727b0865a8918da3edbebcf9b14ed44ce6cbaced4bb1bdb7f1447e6cc254b332051512bd7af426fb8f401378cd2bf5983ca01c64b92ecf032ea15d1721d03f482d7ce6e74fef6d55e702f46980c82b5a84031900b1c9e59e7c97fbec7e8f323a97a7e36cc88be0f1d45b7ff585ac54bd407b22b4154aacc8f6d7ebf48e1d814cc5ed20f8037e0a79715eef29be32806a1d58bb7c5da76f550aa3d8a1fbff0eb19ccb1a313d55cda56c9ec2ef29632387fe8d76e3c0468043e8f663f4860ee12bf2d5b0b7474d6e694f91e6dcc4024ffffffffffffffff', + generator: '02', + }, + modp18: { + prime: + 'ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aaac42dad33170d04507a33a85521abdf1cba64ecfb850458dbef0a8aea71575d060c7db3970f85a6e1e4c7abf5ae8cdb0933d71e8c94e04a25619dcee3d2261ad2ee6bf12ffa06d98a0864d87602733ec86a64521f2b18177b200cbbe117577a615d6c770988c0bad946e208e24fa074e5ab3143db5bfce0fd108e4b82d120a92108011a723c12a787e6d788719a10bdba5b2699c327186af4e23c1a946834b6150bda2583e9ca2ad44ce8dbbbc2db04de8ef92e8efc141fbecaa6287c59474e6bc05d99b2964fa090c3a2233ba186515be7ed1f612970cee2d7afb81bdd762170481cd0069127d5b05aa993b4ea988d8fddc186ffb7dc90a6c08f4df435c93402849236c3fab4d27c7026c1d4dcb2602646dec9751e763dba37bdf8ff9406ad9e530ee5db382f413001aeb06a53ed9027d831179727b0865a8918da3edbebcf9b14ed44ce6cbaced4bb1bdb7f1447e6cc254b332051512bd7af426fb8f401378cd2bf5983ca01c64b92ecf032ea15d1721d03f482d7ce6e74fef6d55e702f46980c82b5a84031900b1c9e59e7c97fbec7e8f323a97a7e36cc88be0f1d45b7ff585ac54bd407b22b4154aacc8f6d7ebf48e1d814cc5ed20f8037e0a79715eef29be32806a1d58bb7c5da76f550aa3d8a1fbff0eb19ccb1a313d55cda56c9ec2ef29632387fe8d76e3c0468043e8f663f4860ee12bf2d5b0b7474d6e694f91e6dbe115974a3926f12fee5e438777cb6a932df8cd8bec4d073b931ba3bc832b68d9dd300741fa7bf8afc47ed2576f6936ba424663aab639c5ae4f5683423b4742bf1c978238f16cbe39d652de3fdb8befc848ad922222e04a4037c0713eb57a81a23f0c73473fc646cea306b4bcbc8862f8385ddfa9d4b7fa2c087e879683303ed5bdd3a062b3cf5b3a278a66d2a13f83f44f82ddf310ee074ab6a364597e899a0255dc164f31cc50846851df9ab48195ded7ea1b1d510bd7ee74d73faf36bc31ecfa268359046f4eb879f924009438b481c6cd7889a002ed5ee382bc9190da6fc026e479558e4475677e9aa9e3050e2765694dfc81f56e880b96e7160c980dd98edd3dfffffffffffffffff', + generator: '02', + }, +}; diff --git a/packages/react-native-quick-crypto/src/diffie-hellman.ts b/packages/react-native-quick-crypto/src/diffie-hellman.ts new file mode 100644 index 00000000..af94132b --- /dev/null +++ b/packages/react-native-quick-crypto/src/diffie-hellman.ts @@ -0,0 +1,191 @@ +import { NitroModules } from 'react-native-nitro-modules'; +import type { DiffieHellman as DiffieHellmanInterface } from './specs/diffie-hellman.nitro'; +import { Buffer } from '@craftzdog/react-native-buffer'; +import { DH_GROUPS } from './dh-groups'; + +export class DiffieHellman { + private _hybrid: DiffieHellmanInterface; + + constructor( + sizeOrPrime: number | Buffer | string, + generator?: number | Buffer | string, + encoding?: BufferEncoding, + ) { + this._hybrid = + NitroModules.createHybridObject('DiffieHellman'); + + if (typeof sizeOrPrime === 'number') { + const gen = typeof generator === 'number' ? generator : 2; + this._hybrid.initWithSize(sizeOrPrime, gen); + } else { + let primeBuf: Buffer; + if (Buffer.isBuffer(sizeOrPrime)) { + primeBuf = sizeOrPrime; + } else { + primeBuf = Buffer.from(sizeOrPrime, encoding as BufferEncoding); + } + + let genBuf: Buffer; + if (generator === undefined) { + genBuf = Buffer.from([2]); + } else if (typeof generator === 'number') { + genBuf = Buffer.from([generator]); + } else if (Buffer.isBuffer(generator)) { + genBuf = generator; + } else { + genBuf = Buffer.from(generator, encoding as BufferEncoding); + } + + this._hybrid.init( + primeBuf.buffer as ArrayBuffer, + genBuf.buffer as ArrayBuffer, + ); + } + } + + generateKeys(encoding?: BufferEncoding): Buffer | string { + const keys = Buffer.from(this._hybrid.generateKeys()); + if (encoding) return keys.toString(encoding); + return keys; + } + + computeSecret( + otherPublicKey: Buffer | string, + inputEncoding?: BufferEncoding, + outputEncoding?: BufferEncoding, + ): Buffer | string { + let keyBuf: Buffer; + if (Buffer.isBuffer(otherPublicKey)) { + keyBuf = otherPublicKey; + } else { + keyBuf = Buffer.from(otherPublicKey, inputEncoding); + } + + const secret = Buffer.from( + this._hybrid.computeSecret(keyBuf.buffer as ArrayBuffer), + ); + if (outputEncoding) return secret.toString(outputEncoding); + return secret; + } + + getPrime(encoding?: BufferEncoding): Buffer | string { + const p = Buffer.from(this._hybrid.getPrime()); + if (encoding) return p.toString(encoding); + return p; + } + + getGenerator(encoding?: BufferEncoding): Buffer | string { + const g = Buffer.from(this._hybrid.getGenerator()); + if (encoding) return g.toString(encoding); + return g; + } + + getPublicKey(encoding?: BufferEncoding): Buffer | string { + const p = Buffer.from(this._hybrid.getPublicKey()); + if (encoding) return p.toString(encoding); + return p; + } + + getPrivateKey(encoding?: BufferEncoding): Buffer | string { + const p = Buffer.from(this._hybrid.getPrivateKey()); + if (encoding) return p.toString(encoding); + return p; + } + + setPublicKey(publicKey: Buffer | string, encoding?: BufferEncoding): void { + let keyBuf: Buffer; + if (Buffer.isBuffer(publicKey)) { + keyBuf = publicKey; + } else { + keyBuf = Buffer.from(publicKey, encoding); + } + this._hybrid.setPublicKey(keyBuf.buffer as ArrayBuffer); + } + + setPrivateKey(privateKey: Buffer | string, encoding?: BufferEncoding): void { + let keyBuf: Buffer; + if (Buffer.isBuffer(privateKey)) { + keyBuf = privateKey; + } else { + keyBuf = Buffer.from(privateKey, encoding); + } + this._hybrid.setPrivateKey(keyBuf.buffer as ArrayBuffer); + } +} + +export function createDiffieHellman( + primeOrSize: number | string | Buffer, + primeEncodingOrGenerator?: string | number | Buffer, + generator?: number | string | Buffer, + _generatorEncoding?: string, +): DiffieHellman { + if (typeof primeOrSize === 'number') { + const gen = + typeof primeEncodingOrGenerator === 'number' + ? primeEncodingOrGenerator + : 2; + return new DiffieHellman(primeOrSize, gen); + } + + // Standardize arguments for String/Buffer prime + // createDiffieHellman(prime, [encoding], [generator], [encoding]) + + let prime: Buffer; + let generatorVal: Buffer | number | undefined; + + if (Buffer.isBuffer(primeOrSize)) { + prime = primeOrSize; + // 2nd arg is generator if not string (encoding) + if ( + primeEncodingOrGenerator !== undefined && + typeof primeEncodingOrGenerator !== 'string' + ) { + generatorVal = primeEncodingOrGenerator as Buffer | number; + } else if (generator !== undefined) { + generatorVal = generator as Buffer | number; + } else { + generatorVal = 2; + } + } else { + // String prime + const encoding = + typeof primeEncodingOrGenerator === 'string' + ? primeEncodingOrGenerator + : 'utf8'; // Defaulting to utf8 or hex? Node default is 'binary' usually but utf8 safer for TS. Node docs say: "If no encoding is specified, 'binary' is used." + // We'll trust user passed encoding if it's a string, otherwise handle it. + prime = Buffer.from(primeOrSize, encoding as BufferEncoding); + + // Generator handling in this case + if (generator !== undefined) { + generatorVal = generator as Buffer | number; + if (typeof generator === 'string' && _generatorEncoding) { + generatorVal = Buffer.from( + generator, + _generatorEncoding as BufferEncoding, + ); + } else if (typeof generator === 'string') { + // string with no encoding, assume same as prime? or utf8? + generatorVal = Buffer.from(generator, encoding as BufferEncoding); + } + } else if ( + typeof primeEncodingOrGenerator !== 'string' && + primeEncodingOrGenerator !== undefined + ) { + // 2nd arg was generator + generatorVal = primeEncodingOrGenerator as number; + } else { + generatorVal = 2; + } + } + + return new DiffieHellman(prime, generatorVal); +} + +export function getDiffieHellman(groupName: string): DiffieHellman { + const group = DH_GROUPS[groupName]; + if (!group) { + throw new Error(`Unknown group: ${groupName}`); + } + // group.prime and group.generator are hex strings + return new DiffieHellman(group.prime, group.generator, 'hex'); +} diff --git a/packages/react-native-quick-crypto/src/ec.ts b/packages/react-native-quick-crypto/src/ec.ts index 6bf10cb0..c30167f5 100644 --- a/packages/react-native-quick-crypto/src/ec.ts +++ b/packages/react-native-quick-crypto/src/ec.ts @@ -31,7 +31,8 @@ import { KeyEncoding, KFormatType, } from './utils'; -import { Buffer } from 'buffer'; +import { Buffer } from '@craftzdog/react-native-buffer'; +import { ECDH } from './ecdh'; export class Ec { native: EcKeyPair; @@ -58,57 +59,6 @@ export class Ec { } } -// function verifyAcceptableEcKeyUse( -// name: AnyAlgorithm, -// isPublic: boolean, -// usages: KeyUsage[], -// ): void { -// let checkSet; -// switch (name) { -// case 'ECDH': -// checkSet = isPublic ? [] : ['deriveKey', 'deriveBits']; -// break; -// case 'ECDSA': -// checkSet = isPublic ? ['verify'] : ['sign']; -// break; -// default: -// throw lazyDOMException( -// 'The algorithm is not supported', -// 'NotSupportedError', -// ); -// } -// if (hasAnyNotIn(usages, checkSet)) { -// throw lazyDOMException( -// `Unsupported key usage for a ${name} key`, -// 'SyntaxError', -// ); -// } -// } - -// function createECPublicKeyRaw( -// namedCurve: NamedCurve | undefined, -// keyDataBuffer: ArrayBuffer, -// ): PublicKeyObject { -// if (!namedCurve) { -// throw new Error('Invalid namedCurve'); -// } -// const handle = NitroModules.createHybridObject( -// 'KeyObjectHandle', -// ) as KeyObjectHandle; - -// if (!handle.initECRaw(kNamedCurveAliases[namedCurve], keyDataBuffer)) { -// console.log('keyData', ab2str(keyDataBuffer)); -// throw new Error('Invalid keyData 1'); -// } - -// return new PublicKeyObject(handle); -// } - -// // Node API -// export function ec_exportKey(key: CryptoKey, format: KeyFormat): ArrayBuffer { -// return ec.native.exportKey(format, key.keyObject.handle); -// } - // Node API export function ecImportKey( format: ImportFormat, @@ -169,7 +119,7 @@ export function ecImportKey( expectedAlg = 'ECDH-ES'; } - if (expectedAlg && jwk.alg !== expectedAlg) { + if (expectedAlg && jwk.alg !== undefined && jwk.alg !== expectedAlg) { throw lazyDOMException( 'JWK "alg" does not match the requested algorithm', 'DataError', @@ -245,6 +195,7 @@ export function ecImportKey( NitroModules.createHybridObject('KeyObjectHandle'); const curveAlias = kNamedCurveAliases[namedCurve as keyof typeof kNamedCurveAliases]; + // Only throw if initialization explicitly fails if (!handle.initECRaw(curveAlias, keyBuffer)) { throw lazyDOMException('Failed to import EC raw key', 'DataError'); } @@ -260,131 +211,8 @@ export function ecImportKey( } return new CryptoKey(keyObject, algorithm, keyUsages, extractable); - // // // verifyAcceptableEcKeyUse(name, true, usagesSet); - // // try { - // // keyObject = createPublicKey({ - // // key: keyData, - // // format: 'der', - // // type: 'spki', - // // }); - // // } catch (err) { - // // throw new Error(`Invalid keyData 2: ${err}`); - // // } - // // break; - // // } - // // case 'pkcs8': { - // // // verifyAcceptableEcKeyUse(name, false, usagesSet); - // // try { - // // keyObject = createPrivateKey({ - // // key: keyData, - // // format: 'der', - // // type: 'pkcs8', - // // }); - // // } catch (err) { - // // throw new Error(`Invalid keyData 3 ${err}`); - // // } - // // break; - // // } } -// case 'jwk': { -// const data = keyData as JWK; - -// if (!data.kty) throw lazyDOMException('Invalid keyData 4', 'DataError'); -// if (data.kty !== 'EC') -// throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError'); -// if (data.crv !== namedCurve) -// throw lazyDOMException( -// 'JWK "crv" does not match the requested algorithm', -// 'DataError', -// ); - -// verifyAcceptableEcKeyUse(name, data.d === undefined, keyUsages); - -// if (keyUsages.length > 0 && data.use !== undefined) { -// const checkUse = name === 'ECDH' ? 'enc' : 'sig'; -// if (data.use !== checkUse) -// throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError'); -// } - -// validateKeyOps(data.key_ops, keyUsages); - -// if ( -// data.ext !== undefined && -// data.ext === false && -// extractable === true -// ) { -// throw lazyDOMException( -// 'JWK "ext" Parameter and extractable mismatch', -// 'DataError', -// ); -// } - -// if (algorithm.name === 'ECDSA' && data.alg !== undefined) { -// let algNamedCurve; -// switch (data.alg) { -// case 'ES256': -// algNamedCurve = 'P-256'; -// break; -// case 'ES384': -// algNamedCurve = 'P-384'; -// break; -// case 'ES512': -// algNamedCurve = 'P-521'; -// break; -// } -// if (algNamedCurve !== namedCurve) -// throw lazyDOMException( -// 'JWK "alg" does not match the requested algorithm', -// 'DataError', -// ); -// } - -// const handle = NativeQuickCrypto.webcrypto.createKeyObjectHandle(); -// const type = handle.initJwk(data, namedCurve); -// if (type === undefined) -// throw lazyDOMException('Invalid JWK', 'DataError'); -// keyObject = -// type === KeyType.PRIVATE -// ? new PrivateKeyObject(handle) -// : new PublicKeyObject(handle); -// break; -// } -// case 'raw': { -// const data = keyData as BufferLike | BinaryLike; -// verifyAcceptableEcKeyUse(name, true, keyUsages); -// const buffer = -// typeof data === 'string' -// ? binaryLikeToArrayBuffer(data) -// : bufferLikeToArrayBuffer(data); -// keyObject = createECPublicKeyRaw(namedCurve, buffer); -// break; -// } -// default: { -// throw new Error(`Unknown EC import format: ${format}`); -// } -// } - -// switch (algorithm.name) { -// case 'ECDSA': -// // Fall through -// case 'ECDH': -// if (keyObject.asymmetricKeyType !== ('ec' as AsymmetricKeyType)) -// throw new Error('Invalid key type'); -// break; -// } - -// // if (!keyObject[kHandle].checkEcKeyData()) { -// // throw new Error('Invalid keyData 5'); -// // } - -// // const { namedCurve: checkNamedCurve } = keyObject[kHandle].keyDetail({}); -// // if (kNamedCurveAliases[namedCurve] !== checkNamedCurve) -// // throw new Error('Named curve mismatch'); - -// return new CryptoKey(keyObject, { name, namedCurve }, keyUsages, extractable); -// } - // Node API export const ecdsaSignVerify = ( key: CryptoKey, @@ -462,7 +290,6 @@ export async function ec_generateKeyPair( ); } - // const usageSet = new SafeSet(keyUsages); switch (name) { case 'ECDSA': if (hasAnyNotIn(keyUsages, ['sign', 'verify'])) { @@ -655,3 +482,72 @@ export function ec_generateKeyPairNodeSync( ec.generateKeyPairSync(); return ec_formatKeyPairOutput(ec, encoding); } + +export function ecDeriveBits( + algorithm: SubtleAlgorithm, + baseKey: CryptoKey, + length: number | null, +): ArrayBuffer { + const publicParams = algorithm as SubtleAlgorithm & { public?: CryptoKey }; + const publicKey = publicParams.public; + + if (!publicKey) { + throw new Error('Public key is required for ECDH derivation'); + } + + if (baseKey.algorithm.name !== publicKey.algorithm.name) { + throw new Error('Keys must be of the same algorithm'); + } + + if (baseKey.algorithm.namedCurve !== publicKey.algorithm.namedCurve) { + throw new Error('Keys must use the same curve'); + } + + const namedCurve = baseKey.algorithm.namedCurve; + if (!namedCurve) { + throw new Error('Curve name is missing'); + } + + // Create new ECDH instance (Node.js style wrapper) + const ecdh = new ECDH(namedCurve); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const jwkPrivate = baseKey.keyObject.export({ format: 'jwk' }) as any; + if (!jwkPrivate.d) throw new Error('Invalid private key'); + const privateBytes = Buffer.from(jwkPrivate.d, 'base64'); + + ecdh.setPrivateKey(privateBytes); + + // Public key + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const jwkPublic = publicKey.keyObject.export({ format: 'jwk' }) as any; + + // HybridECDH `computeSecret` takes public key. + // My implementation `HybridECDH.cpp` `computeSecret` expects what? + // `derive_secret` -> `EVP_PKEY_derive_set_peer` + // `computeSecret` calls `EC_POINT_oct2point`. So it expects an uncompressed/compressed point (04... or 02/03...). + // JWK gives `x` and `y`. We can construct the uncompressed point 04 + x + y. + + if (!jwkPublic.x || !jwkPublic.y) throw new Error('Invalid public key'); + const x = Buffer.from(jwkPublic.x, 'base64'); + const y = Buffer.from(jwkPublic.y, 'base64'); + + // Uncompressed point: 0x04 || x || y + const publicBytes = Buffer.concat([Buffer.from([0x04]), x, y]); + + const secret = ecdh.computeSecret(publicBytes); + const secretBuf = Buffer.from(secret); + + // If length is null, return full secret + if (length === null) { + return secretBuf.buffer; + } + + // If length is specified, truncate + const byteLength = Math.ceil(length / 8); + if (secretBuf.byteLength >= byteLength) { + return secretBuf.subarray(0, byteLength).buffer as ArrayBuffer; + } + + throw new Error('Derived key is shorter than requested length'); +} diff --git a/packages/react-native-quick-crypto/src/ecdh.ts b/packages/react-native-quick-crypto/src/ecdh.ts new file mode 100644 index 00000000..101d2204 --- /dev/null +++ b/packages/react-native-quick-crypto/src/ecdh.ts @@ -0,0 +1,76 @@ +import { NitroModules } from 'react-native-nitro-modules'; +import type { ECDH as ECDHInterface } from './specs/ecdh.nitro'; +import { Buffer } from '@craftzdog/react-native-buffer'; + +export class ECDH { + private _hybrid: ECDHInterface; + + constructor(curveName: string) { + this._hybrid = NitroModules.createHybridObject('ECDH'); + this._hybrid.init(curveName); + } + + generateKeys(): Buffer { + const key = this._hybrid.generateKeys(); + return Buffer.from(key); + } + + computeSecret( + otherPublicKey: Buffer | string | { code: number; byteLength: number }, + inputEncoding?: BufferEncoding, + ): Buffer { + let keyBuf: Buffer; + if (Buffer.isBuffer(otherPublicKey)) { + keyBuf = otherPublicKey; + } else if (typeof otherPublicKey === 'string') { + keyBuf = Buffer.from(otherPublicKey, inputEncoding); + } else { + // Handle array view or other types if necessary, but Node.js typically expects Buffer or string + encoding + throw new TypeError('Invalid otherPublicKey type'); + } + + // ECDH.computeSecret in Node.js returns Buffer + const secret = this._hybrid.computeSecret(keyBuf.buffer as ArrayBuffer); + return Buffer.from(secret); + } + + getPrivateKey(): Buffer { + return Buffer.from(this._hybrid.getPrivateKey()); + } + + setPrivateKey(privateKey: Buffer | string, encoding?: BufferEncoding): void { + let keyBuf: Buffer; + if (Buffer.isBuffer(privateKey)) { + keyBuf = privateKey; + } else { + keyBuf = Buffer.from(privateKey, encoding); + } + this._hybrid.setPrivateKey(keyBuf.buffer as ArrayBuffer); + } + + getPublicKey(encoding?: BufferEncoding): Buffer | string { + // Node.js getPublicKey([encoding[, format]]) + // If encoding is provided, returns string. If not, Buffer. + // Our C++ returns ArrayBuffer (Buffer). + // We ignore format for now as C++ implementation defaults to uncompressed. + const pub = Buffer.from(this._hybrid.getPublicKey()); + if (encoding) { + return pub.toString(encoding); + } + return pub; + } + + setPublicKey(publicKey: Buffer | string, encoding?: BufferEncoding): void { + let keyBuf: Buffer; + if (Buffer.isBuffer(publicKey)) { + keyBuf = publicKey; + } else { + keyBuf = Buffer.from(publicKey, encoding); + } + this._hybrid.setPublicKey(keyBuf.buffer as ArrayBuffer); + } +} + +export function createECDH(curveName: string): ECDH { + return new ECDH(curveName); +} diff --git a/packages/react-native-quick-crypto/src/index.ts b/packages/react-native-quick-crypto/src/index.ts index a2cf96cc..20ef0f3d 100644 --- a/packages/react-native-quick-crypto/src/index.ts +++ b/packages/react-native-quick-crypto/src/index.ts @@ -12,6 +12,8 @@ import * as hkdf from './hkdf'; import * as pbkdf2 from './pbkdf2'; import * as scrypt from './scrypt'; import * as random from './random'; +import * as ecdh from './ecdh'; +import * as dh from './diffie-hellman'; import { constants } from './constants'; // utils import @@ -33,6 +35,8 @@ const QuickCrypto = { ...pbkdf2, ...scrypt, ...random, + ...ecdh, + ...dh, ...utils, ...subtle, constants, @@ -71,6 +75,8 @@ export * from './hkdf'; export * from './pbkdf2'; export * from './scrypt'; export * from './random'; +export * from './ecdh'; +export * from './diffie-hellman'; export * from './utils'; export * from './subtle'; export { subtle, isCryptoKeyPair } from './subtle'; diff --git a/packages/react-native-quick-crypto/src/keys/generateKeyPair.ts b/packages/react-native-quick-crypto/src/keys/generateKeyPair.ts index dd5c4d53..4635865a 100644 --- a/packages/react-native-quick-crypto/src/keys/generateKeyPair.ts +++ b/packages/react-native-quick-crypto/src/keys/generateKeyPair.ts @@ -160,7 +160,8 @@ function internalGenerateKeyPair( } else { throw new Error(`Unsupported key type: ${type}`); } - return [undefined, result.publicKey, result.privateKey]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return [undefined, result.publicKey as any, result.privateKey as any]; } catch (error) { return [error as Error, undefined, undefined]; } @@ -182,7 +183,8 @@ function internalGenerateKeyPair( } else { throw new Error(`Unsupported key type: ${type}`); } - return [undefined, result.publicKey, result.privateKey]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return [undefined, result.publicKey as any, result.privateKey as any]; } catch (error) { return [error as Error, undefined, undefined]; } diff --git a/packages/react-native-quick-crypto/src/specs/diffie-hellman.nitro.ts b/packages/react-native-quick-crypto/src/specs/diffie-hellman.nitro.ts new file mode 100644 index 00000000..f07bdf3e --- /dev/null +++ b/packages/react-native-quick-crypto/src/specs/diffie-hellman.nitro.ts @@ -0,0 +1,15 @@ +import type { HybridObject } from 'react-native-nitro-modules'; + +export interface DiffieHellman + extends HybridObject<{ ios: 'c++'; android: 'c++' }> { + init(prime: ArrayBuffer, generator: ArrayBuffer): void; + initWithSize(primeLength: number, generator: number): void; + generateKeys(): ArrayBuffer; + computeSecret(otherPublicKey: ArrayBuffer): ArrayBuffer; + getPrime(): ArrayBuffer; + getGenerator(): ArrayBuffer; + getPublicKey(): ArrayBuffer; + getPrivateKey(): ArrayBuffer; + setPublicKey(publicKey: ArrayBuffer): void; + setPrivateKey(privateKey: ArrayBuffer): void; +} diff --git a/packages/react-native-quick-crypto/src/specs/ecdh.nitro.ts b/packages/react-native-quick-crypto/src/specs/ecdh.nitro.ts new file mode 100644 index 00000000..a75b2402 --- /dev/null +++ b/packages/react-native-quick-crypto/src/specs/ecdh.nitro.ts @@ -0,0 +1,11 @@ +import type { HybridObject } from 'react-native-nitro-modules'; + +export interface ECDH extends HybridObject<{ ios: 'c++'; android: 'c++' }> { + init(curveName: string): void; + generateKeys(): ArrayBuffer; + computeSecret(otherPublicKey: ArrayBuffer): ArrayBuffer; + getPrivateKey(): ArrayBuffer; + setPrivateKey(privateKey: ArrayBuffer): void; + getPublicKey(): ArrayBuffer; + setPublicKey(publicKey: ArrayBuffer): void; +} diff --git a/packages/react-native-quick-crypto/src/subtle.ts b/packages/react-native-quick-crypto/src/subtle.ts index bad5d1dd..23bdd610 100644 --- a/packages/react-native-quick-crypto/src/subtle.ts +++ b/packages/react-native-quick-crypto/src/subtle.ts @@ -37,7 +37,12 @@ import type { KeyObjectHandle } from './specs/keyObjectHandle.nitro'; import type { RsaCipher } from './specs/rsaCipher.nitro'; import type { CipherFactory } from './specs/cipher.nitro'; import { pbkdf2DeriveBits } from './pbkdf2'; -import { ecImportKey, ecdsaSignVerify, ec_generateKeyPair } from './ec'; +import { + ecImportKey, + ecdsaSignVerify, + ec_generateKeyPair, + ecDeriveBits, +} from './ec'; import { rsa_generateKeyPair } from './rsa'; import { getRandomValues } from './random'; import { createHmac } from './hmac'; @@ -1559,6 +1564,8 @@ export class Subtle { // Fall through case 'X448': return xDeriveBits(algorithm, baseKey, length); + case 'ECDH': + return ecDeriveBits(algorithm, baseKey, length); case 'HKDF': return hkdfDeriveBits( algorithm as unknown as HkdfAlgorithm, From e714b5547747fc496e532753b22a6a03294256fa Mon Sep 17 00:00:00 2001 From: Brad Anderson Date: Sat, 31 Jan 2026 00:08:19 -0500 Subject: [PATCH 2/4] fix: alpha-sort nitro.json, format C++, remove any casts --- .../cpp/dh/HybridDiffieHellman.cpp | 447 ++++++++++++------ .../cpp/ecdh/HybridECDH.cpp | 372 +++++++++------ packages/react-native-quick-crypto/nitro.json | 49 +- .../src/keys/generateKeyPair.ts | 15 +- 4 files changed, 556 insertions(+), 327 deletions(-) diff --git a/packages/react-native-quick-crypto/cpp/dh/HybridDiffieHellman.cpp b/packages/react-native-quick-crypto/cpp/dh/HybridDiffieHellman.cpp index da47bdfa..576137e1 100644 --- a/packages/react-native-quick-crypto/cpp/dh/HybridDiffieHellman.cpp +++ b/packages/react-native-quick-crypto/cpp/dh/HybridDiffieHellman.cpp @@ -1,118 +1,122 @@ #include "HybridDiffieHellman.hpp" -#include "Utils.hpp" +#include "QuickCryptoUtils.hpp" #include +#include #include #include #include #include namespace margelo::nitro::crypto { + +// Smart pointer type aliases for RAII +using BN_ptr = std::unique_ptr; +using DH_ptr = std::unique_ptr; +using EVP_PKEY_CTX_ptr = std::unique_ptr; + +// Minimum DH prime size for security (2048 bits = 256 bytes) +static constexpr int kMinDHPrimeBits = 2048; + +// Suppress deprecation warnings for DH_* functions +// Node.js ncrypto uses the same pattern - these APIs work but are deprecated in OpenSSL 3.x #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" void HybridDiffieHellman::init(const std::shared_ptr& prime, const std::shared_ptr& generator) { - if (_pkey) { - EVP_PKEY_free(_pkey); - _pkey = nullptr; + // Create DH structure + DH_ptr dh(DH_new(), DH_free); + if (!dh) { + throw std::runtime_error("DiffieHellman: failed to create DH structure"); } - // Create DH parameters from prime and generator - DH* dh = DH_new(); - if (!dh) - throw std::runtime_error("Failed to create DH"); - + // Convert prime and generator to BIGNUMs BIGNUM* p = BN_bin2bn(prime->data(), static_cast(prime->size()), nullptr); BIGNUM* g = BN_bin2bn(generator->data(), static_cast(generator->size()), nullptr); if (!p || !g) { - DH_free(dh); if (p) BN_free(p); if (g) BN_free(g); - throw std::runtime_error("Failed to convert parameters to BIGNUM"); + throw std::runtime_error("DiffieHellman: failed to convert parameters to BIGNUM"); } - if (DH_set0_pqg(dh, p, nullptr, g) != 1) { - DH_free(dh); + // DH_set0_pqg takes ownership of p and g on success + if (DH_set0_pqg(dh.get(), p, nullptr, g) != 1) { BN_free(p); - BN_free(g); // DH_set0_pqg takes ownership only on success. - throw std::runtime_error("Failed to set DH parameters"); + BN_free(g); + throw std::runtime_error("DiffieHellman: failed to set DH parameters"); } - _pkey = EVP_PKEY_new(); - if (!_pkey) { - DH_free(dh); - throw std::runtime_error("Failed to create EVP_PKEY"); + // Create EVP_PKEY and assign DH to it + EVP_PKEY_ptr pkey(EVP_PKEY_new(), EVP_PKEY_free); + if (!pkey) { + throw std::runtime_error("DiffieHellman: failed to create EVP_PKEY"); } - if (EVP_PKEY_assign_DH(_pkey, dh) != 1) { - EVP_PKEY_free(_pkey); - _pkey = nullptr; - DH_free(dh); // Assign takes ownership - throw std::runtime_error("Failed to assign DH to EVP_PKEY"); + // EVP_PKEY_assign_DH takes ownership of dh on success + if (EVP_PKEY_assign_DH(pkey.get(), dh.get()) != 1) { + throw std::runtime_error("DiffieHellman: failed to assign DH to EVP_PKEY"); } + dh.release(); // EVP_PKEY now owns the DH + + _pkey = std::move(pkey); } void HybridDiffieHellman::initWithSize(double primeLength, double generator) { - if (_pkey) { - EVP_PKEY_free(_pkey); - _pkey = nullptr; + int primeBits = static_cast(primeLength); + int gen = static_cast(generator); + + // Validate minimum key size for security + if (primeBits < kMinDHPrimeBits) { + throw std::runtime_error("DiffieHellman: prime length must be at least 2048 bits"); } - // Generate parameters - EVP_PKEY_CTX* pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_DH, nullptr); - if (!pctx) - throw std::runtime_error("Failed to create context"); + // Create parameter generation context + EVP_PKEY_CTX_ptr pctx(EVP_PKEY_CTX_new_id(EVP_PKEY_DH, nullptr), EVP_PKEY_CTX_free); + if (!pctx) { + throw std::runtime_error("DiffieHellman: failed to create parameter context"); + } - if (EVP_PKEY_paramgen_init(pctx) <= 0) { - EVP_PKEY_CTX_free(pctx); - throw std::runtime_error("Failed to init paramgen"); + if (EVP_PKEY_paramgen_init(pctx.get()) <= 0) { + throw std::runtime_error("DiffieHellman: failed to initialize parameter generation"); } - if (EVP_PKEY_CTX_set_dh_paramgen_prime_len(pctx, (int)primeLength) <= 0) { - EVP_PKEY_CTX_free(pctx); - throw std::runtime_error("Failed to set prime length"); + if (EVP_PKEY_CTX_set_dh_paramgen_prime_len(pctx.get(), primeBits) <= 0) { + throw std::runtime_error("DiffieHellman: failed to set prime length"); } - if (EVP_PKEY_CTX_set_dh_paramgen_generator(pctx, (int)generator) <= 0) { - EVP_PKEY_CTX_free(pctx); - throw std::runtime_error("Failed to set generator"); + if (EVP_PKEY_CTX_set_dh_paramgen_generator(pctx.get(), gen) <= 0) { + throw std::runtime_error("DiffieHellman: failed to set generator"); } EVP_PKEY* params = nullptr; - if (EVP_PKEY_paramgen(pctx, ¶ms) <= 0) { - EVP_PKEY_CTX_free(pctx); - throw std::runtime_error("Failed to generate parameters"); + if (EVP_PKEY_paramgen(pctx.get(), ¶ms) <= 0) { + throw std::runtime_error("DiffieHellman: failed to generate parameters"); } - EVP_PKEY_CTX_free(pctx); - _pkey = params; + _pkey.reset(params); } std::shared_ptr HybridDiffieHellman::generateKeys() { ensureInitialized(); - EVP_PKEY_CTX* kctx = EVP_PKEY_CTX_new(_pkey, nullptr); - if (!kctx) - throw std::runtime_error("Failed to create keygen context"); - - if (EVP_PKEY_keygen_init(kctx) <= 0) { - EVP_PKEY_CTX_free(kctx); - throw std::runtime_error("Failed to init keygen"); + EVP_PKEY_CTX_ptr kctx(EVP_PKEY_CTX_new(_pkey.get(), nullptr), EVP_PKEY_CTX_free); + if (!kctx) { + throw std::runtime_error("DiffieHellman: failed to create keygen context"); } - EVP_PKEY* new_key = nullptr; - if (EVP_PKEY_keygen(kctx, &new_key) <= 0) { - EVP_PKEY_CTX_free(kctx); - throw std::runtime_error("Failed to generate key"); + if (EVP_PKEY_keygen_init(kctx.get()) <= 0) { + throw std::runtime_error("DiffieHellman: failed to initialize key generation"); } - EVP_PKEY_CTX_free(kctx); + EVP_PKEY* newKey = nullptr; + if (EVP_PKEY_keygen(kctx.get(), &newKey) <= 0) { + throw std::runtime_error("DiffieHellman: failed to generate key pair"); + } // Replace parameters-only key with full key (which includes parameters) - EVP_PKEY_free(_pkey); - _pkey = new_key; + _pkey.reset(newKey); return getPublicKey(); } @@ -120,174 +124,315 @@ std::shared_ptr HybridDiffieHellman::generateKeys() { std::shared_ptr HybridDiffieHellman::computeSecret(const std::shared_ptr& otherPublicKey) { ensureInitialized(); - // Create peer key from public key buffer - // We need to create a new EVP_PKEY with the same parameters as ours, but with the peer's public key. + const DH* ourDh = getDH(); + const BIGNUM *p, *q, *g; + DH_get0_pqg(ourDh, &p, &q, &g); - const DH* our_dh = EVP_PKEY_get0_DH(_pkey); - if (!our_dh) - throw std::runtime_error("Not a DH key"); + // Create peer DH with same parameters but peer's public key + DH_ptr peerDh(DH_new(), DH_free); + if (!peerDh) { + throw std::runtime_error("DiffieHellman: failed to create peer DH structure"); + } - const BIGNUM *p, *q, *g; - DH_get0_pqg(our_dh, &p, &q, &g); + // Duplicate parameters for peer + BIGNUM* peerP = BN_dup(p); + BIGNUM* peerG = BN_dup(g); + BIGNUM* peerPubKey = BN_bin2bn(otherPublicKey->data(), static_cast(otherPublicKey->size()), nullptr); + + if (!peerP || !peerG || !peerPubKey) { + if (peerP) + BN_free(peerP); + if (peerG) + BN_free(peerG); + if (peerPubKey) + BN_free(peerPubKey); + throw std::runtime_error("DiffieHellman: failed to create peer parameters"); + } + + // Set peer DH parameters (takes ownership on success) + if (DH_set0_pqg(peerDh.get(), peerP, nullptr, peerG) != 1) { + BN_free(peerP); + BN_free(peerG); + BN_free(peerPubKey); + throw std::runtime_error("DiffieHellman: failed to set peer DH parameters"); + } + + // Set peer public key (takes ownership on success) + if (DH_set0_key(peerDh.get(), peerPubKey, nullptr) != 1) { + BN_free(peerPubKey); + throw std::runtime_error("DiffieHellman: failed to set peer public key"); + } - DH* peer_dh = DH_new(); - BIGNUM* peer_p = BN_dup(p); - BIGNUM* peer_g = BN_dup(g); - BIGNUM* peer_pub_key = BN_bin2bn(otherPublicKey->data(), static_cast(otherPublicKey->size()), nullptr); + // Create peer EVP_PKEY + EVP_PKEY_ptr peerPkey(EVP_PKEY_new(), EVP_PKEY_free); + if (!peerPkey) { + throw std::runtime_error("DiffieHellman: failed to create peer EVP_PKEY"); + } - if (!peer_dh || !peer_p || !peer_g || !peer_pub_key) { - DH_free(peer_dh); - BN_free(peer_p); - BN_free(peer_g); - BN_free(peer_pub_key); - throw std::runtime_error("Failed to create peer DH"); + // EVP_PKEY_assign_DH takes ownership of peerDh on success + if (EVP_PKEY_assign_DH(peerPkey.get(), peerDh.get()) != 1) { + throw std::runtime_error("DiffieHellman: failed to assign peer DH to EVP_PKEY"); } + peerDh.release(); // EVP_PKEY now owns the DH - DH_set0_pqg(peer_dh, peer_p, nullptr, peer_g); - DH_set0_key(peer_dh, peer_pub_key, nullptr); + // Derive shared secret using EVP API + EVP_PKEY_CTX_ptr ctx(EVP_PKEY_CTX_new(_pkey.get(), nullptr), EVP_PKEY_CTX_free); + if (!ctx) { + throw std::runtime_error("DiffieHellman: failed to create derive context"); + } - EVP_PKEY* peer_pkey = EVP_PKEY_new(); - EVP_PKEY_assign_DH(peer_pkey, peer_dh); + if (EVP_PKEY_derive_init(ctx.get()) <= 0) { + throw std::runtime_error("DiffieHellman: failed to initialize key derivation"); + } - // Derive - EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(_pkey, nullptr); - if (!ctx || EVP_PKEY_derive_init(ctx) <= 0 || EVP_PKEY_derive_set_peer(ctx, peer_pkey) <= 0) { - if (ctx) - EVP_PKEY_CTX_free(ctx); - EVP_PKEY_free(peer_pkey); - throw std::runtime_error("Failed to init derive"); + if (EVP_PKEY_derive_set_peer(ctx.get(), peerPkey.get()) <= 0) { + throw std::runtime_error("DiffieHellman: failed to set peer key for derivation"); } - size_t secret_len; - if (EVP_PKEY_derive(ctx, nullptr, &secret_len) <= 0) { - EVP_PKEY_CTX_free(ctx); - EVP_PKEY_free(peer_pkey); - throw std::runtime_error("Failed to get secret length"); + // Get required buffer size + size_t secretLen = 0; + if (EVP_PKEY_derive(ctx.get(), nullptr, &secretLen) <= 0) { + throw std::runtime_error("DiffieHellman: failed to get shared secret length"); } - std::vector secret(secret_len); - if (EVP_PKEY_derive(ctx, secret.data(), &secret_len) <= 0) { - EVP_PKEY_CTX_free(ctx); - EVP_PKEY_free(peer_pkey); - throw std::runtime_error("Failed to derive"); + // Derive the shared secret + std::vector secret(secretLen); + if (EVP_PKEY_derive(ctx.get(), secret.data(), &secretLen) <= 0) { + throw std::runtime_error("DiffieHellman: failed to derive shared secret"); } - EVP_PKEY_CTX_free(ctx); - EVP_PKEY_free(peer_pkey); + // Resize to actual length (may be smaller due to leading zeros) + secret.resize(secretLen); - return ToNativeArrayBuffer(std::string(secret.begin(), secret.end())); + return ToNativeArrayBuffer(secret); } std::shared_ptr HybridDiffieHellman::getPrime() { ensureInitialized(); - const DH* dh = EVP_PKEY_get0_DH(_pkey); + const DH* dh = getDH(); + const BIGNUM *p, *q, *g; DH_get0_pqg(dh, &p, &q, &g); - if (!p) - throw std::runtime_error("No prime"); + if (!p) { + throw std::runtime_error("DiffieHellman: no prime available"); + } int len = BN_num_bytes(p); std::vector buf(len); BN_bn2bin(p, buf.data()); - return ToNativeArrayBuffer(std::string(buf.begin(), buf.end())); + + return ToNativeArrayBuffer(buf); } std::shared_ptr HybridDiffieHellman::getGenerator() { ensureInitialized(); - const DH* dh = EVP_PKEY_get0_DH(_pkey); + const DH* dh = getDH(); + const BIGNUM *p, *q, *g; DH_get0_pqg(dh, &p, &q, &g); - if (!g) - throw std::runtime_error("No generator"); + if (!g) { + throw std::runtime_error("DiffieHellman: no generator available"); + } int len = BN_num_bytes(g); std::vector buf(len); BN_bn2bin(g, buf.data()); - return ToNativeArrayBuffer(std::string(buf.begin(), buf.end())); + + return ToNativeArrayBuffer(buf); } std::shared_ptr HybridDiffieHellman::getPublicKey() { ensureInitialized(); - const DH* dh = EVP_PKEY_get0_DH(_pkey); + const DH* dh = getDH(); + const BIGNUM *pub, *priv; DH_get0_key(dh, &pub, &priv); - if (!pub) - throw std::runtime_error("No public key"); + if (!pub) { + throw std::runtime_error("DiffieHellman: no public key available"); + } int len = BN_num_bytes(pub); std::vector buf(len); BN_bn2bin(pub, buf.data()); - return ToNativeArrayBuffer(std::string(buf.begin(), buf.end())); + + return ToNativeArrayBuffer(buf); } std::shared_ptr HybridDiffieHellman::getPrivateKey() { ensureInitialized(); - const DH* dh = EVP_PKEY_get0_DH(_pkey); + const DH* dh = getDH(); + const BIGNUM *pub, *priv; DH_get0_key(dh, &pub, &priv); - if (!priv) - throw std::runtime_error("No private key"); + if (!priv) { + throw std::runtime_error("DiffieHellman: no private key available"); + } int len = BN_num_bytes(priv); std::vector buf(len); BN_bn2bin(priv, buf.data()); - return ToNativeArrayBuffer(std::string(buf.begin(), buf.end())); + + return ToNativeArrayBuffer(buf); } void HybridDiffieHellman::setPublicKey(const std::shared_ptr& publicKey) { ensureInitialized(); - const DH* dh = EVP_PKEY_get0_DH(_pkey); - BIGNUM* pub = BN_bin2bn(publicKey->data(), static_cast(publicKey->size()), nullptr); - if (!pub) - throw std::runtime_error("Failed to convert public key"); - - // We need to keep private key if it exists - const BIGNUM *old_pub, *old_priv; - DH_get0_key(dh, &old_pub, &old_priv); + const DH* dh = getDH(); - BIGNUM* priv = old_priv ? BN_dup(old_priv) : nullptr; + // Get existing keys + const BIGNUM *oldPub, *oldPriv; + DH_get0_key(dh, &oldPub, &oldPriv); - // Since dh is const, we need to replace the whole key or cast away const (dangerous). - // Better: Create new DH, copy params, set new keys, replace EVP_PKEY. - DH* new_dh = DH_new(); + // Get parameters const BIGNUM *p, *q, *g; DH_get0_pqg(dh, &p, &q, &g); - DH_set0_pqg(new_dh, BN_dup(p), q ? BN_dup(q) : nullptr, BN_dup(g)); - DH_set0_key(new_dh, pub, priv); - EVP_PKEY_free(_pkey); - _pkey = EVP_PKEY_new(); - EVP_PKEY_assign_DH(_pkey, new_dh); + // Create new DH with copied parameters + DH_ptr newDh(DH_new(), DH_free); + if (!newDh) { + throw std::runtime_error("DiffieHellman: failed to create new DH structure"); + } + + // Duplicate parameters + BIGNUM* newP = BN_dup(p); + BIGNUM* newQ = q ? BN_dup(q) : nullptr; + BIGNUM* newG = BN_dup(g); + + if (!newP || !newG) { + if (newP) + BN_free(newP); + if (newQ) + BN_free(newQ); + if (newG) + BN_free(newG); + throw std::runtime_error("DiffieHellman: failed to duplicate parameters"); + } + + if (DH_set0_pqg(newDh.get(), newP, newQ, newG) != 1) { + BN_free(newP); + if (newQ) + BN_free(newQ); + BN_free(newG); + throw std::runtime_error("DiffieHellman: failed to set parameters"); + } + + // Convert new public key + BIGNUM* newPub = BN_bin2bn(publicKey->data(), static_cast(publicKey->size()), nullptr); + BIGNUM* newPriv = oldPriv ? BN_dup(oldPriv) : nullptr; + + if (!newPub) { + if (newPriv) + BN_free(newPriv); + throw std::runtime_error("DiffieHellman: failed to convert public key"); + } + + if (DH_set0_key(newDh.get(), newPub, newPriv) != 1) { + BN_free(newPub); + if (newPriv) + BN_free(newPriv); + throw std::runtime_error("DiffieHellman: failed to set keys"); + } + + // Create new EVP_PKEY + EVP_PKEY_ptr newPkey(EVP_PKEY_new(), EVP_PKEY_free); + if (!newPkey) { + throw std::runtime_error("DiffieHellman: failed to create new EVP_PKEY"); + } + + if (EVP_PKEY_assign_DH(newPkey.get(), newDh.get()) != 1) { + throw std::runtime_error("DiffieHellman: failed to assign DH to EVP_PKEY"); + } + newDh.release(); + + _pkey = std::move(newPkey); } void HybridDiffieHellman::setPrivateKey(const std::shared_ptr& privateKey) { ensureInitialized(); - const DH* dh = EVP_PKEY_get0_DH(_pkey); - BIGNUM* priv = BN_bin2bn(privateKey->data(), static_cast(privateKey->size()), nullptr); - if (!priv) - throw std::runtime_error("Failed to convert private key"); - - // We need to keep public key if it exists - const BIGNUM *old_pub, *old_priv; - DH_get0_key(dh, &old_pub, &old_priv); + const DH* dh = getDH(); - BIGNUM* pub = old_pub ? BN_dup(old_pub) : nullptr; + // Get existing keys + const BIGNUM *oldPub, *oldPriv; + DH_get0_key(dh, &oldPub, &oldPriv); - DH* new_dh = DH_new(); + // Get parameters const BIGNUM *p, *q, *g; DH_get0_pqg(dh, &p, &q, &g); - DH_set0_pqg(new_dh, BN_dup(p), q ? BN_dup(q) : nullptr, BN_dup(g)); - DH_set0_key(new_dh, pub, priv); - EVP_PKEY_free(_pkey); - _pkey = EVP_PKEY_new(); - EVP_PKEY_assign_DH(_pkey, new_dh); + // Create new DH with copied parameters + DH_ptr newDh(DH_new(), DH_free); + if (!newDh) { + throw std::runtime_error("DiffieHellman: failed to create new DH structure"); + } + + // Duplicate parameters + BIGNUM* newP = BN_dup(p); + BIGNUM* newQ = q ? BN_dup(q) : nullptr; + BIGNUM* newG = BN_dup(g); + + if (!newP || !newG) { + if (newP) + BN_free(newP); + if (newQ) + BN_free(newQ); + if (newG) + BN_free(newG); + throw std::runtime_error("DiffieHellman: failed to duplicate parameters"); + } + + if (DH_set0_pqg(newDh.get(), newP, newQ, newG) != 1) { + BN_free(newP); + if (newQ) + BN_free(newQ); + BN_free(newG); + throw std::runtime_error("DiffieHellman: failed to set parameters"); + } + + // Convert new private key + BIGNUM* newPub = oldPub ? BN_dup(oldPub) : nullptr; + BIGNUM* newPriv = BN_bin2bn(privateKey->data(), static_cast(privateKey->size()), nullptr); + + if (!newPriv) { + if (newPub) + BN_free(newPub); + throw std::runtime_error("DiffieHellman: failed to convert private key"); + } + + if (DH_set0_key(newDh.get(), newPub, newPriv) != 1) { + if (newPub) + BN_free(newPub); + BN_free(newPriv); + throw std::runtime_error("DiffieHellman: failed to set keys"); + } + + // Create new EVP_PKEY + EVP_PKEY_ptr newPkey(EVP_PKEY_new(), EVP_PKEY_free); + if (!newPkey) { + throw std::runtime_error("DiffieHellman: failed to create new EVP_PKEY"); + } + + if (EVP_PKEY_assign_DH(newPkey.get(), newDh.get()) != 1) { + throw std::runtime_error("DiffieHellman: failed to assign DH to EVP_PKEY"); + } + newDh.release(); + + _pkey = std::move(newPkey); +} + +void HybridDiffieHellman::ensureInitialized() const { + if (!_pkey) { + throw std::runtime_error("DiffieHellman: not initialized"); + } } -void HybridDiffieHellman::ensureInitialized() { - if (!_pkey) - throw std::runtime_error("DiffieHellman not initialized"); +const DH* HybridDiffieHellman::getDH() const { + const DH* dh = EVP_PKEY_get0_DH(_pkey.get()); + if (!dh) { + throw std::runtime_error("DiffieHellman: key is not a DH key"); + } + return dh; } #pragma clang diagnostic pop + } // namespace margelo::nitro::crypto diff --git a/packages/react-native-quick-crypto/cpp/ecdh/HybridECDH.cpp b/packages/react-native-quick-crypto/cpp/ecdh/HybridECDH.cpp index f02dc8a7..5e1b329b 100644 --- a/packages/react-native-quick-crypto/cpp/ecdh/HybridECDH.cpp +++ b/packages/react-native-quick-crypto/cpp/ecdh/HybridECDH.cpp @@ -1,6 +1,7 @@ #include "HybridECDH.hpp" -#include "Utils.hpp" +#include "QuickCryptoUtils.hpp" #include +#include #include #include #include @@ -8,215 +9,298 @@ #include namespace margelo::nitro::crypto { + +// Smart pointer type aliases for RAII +using EVP_PKEY_CTX_ptr = std::unique_ptr; +using EC_KEY_ptr = std::unique_ptr; +using EC_POINT_ptr = std::unique_ptr; +using BN_ptr = std::unique_ptr; + +// Suppress deprecation warnings for EC_KEY_* functions +// These APIs work but are deprecated in OpenSSL 3.x #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" void HybridECDH::init(const std::string& curveName) { - _curveName = curveName; - _curveNid = getCurveNid(curveName); - if (_curveNid == NID_undef) { - throw std::runtime_error("Unknown curve name: " + curveName); + int nid = getCurveNid(curveName); + if (nid == NID_undef) { + throw std::runtime_error("ECDH: unknown curve name: " + curveName); } - // Clear previous key if any - if (_pkey) { - EVP_PKEY_free(_pkey); - _pkey = nullptr; + EC_GROUP_ptr group(EC_GROUP_new_by_curve_name(nid), EC_GROUP_free); + if (!group) { + throw std::runtime_error("ECDH: failed to create EC group for curve: " + curveName); } - if (_group) { - EC_GROUP_free(_group); - _group = nullptr; - } - - _group = EC_GROUP_new_by_curve_name(_curveNid); - if (!_group) { - throw std::runtime_error("Failed to create EC group for curve: " + curveName); - } + _curveName = curveName; + _curveNid = nid; + _group = std::move(group); + _pkey.reset(); } std::shared_ptr HybridECDH::generateKeys() { ensureInitialized(); - std::unique_ptr ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr), EVP_PKEY_CTX_free); - if (!ctx) - throw std::runtime_error("Failed to create context"); + EVP_PKEY_CTX_ptr ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr), EVP_PKEY_CTX_free); + if (!ctx) { + throw std::runtime_error("ECDH: failed to create keygen context"); + } - if (EVP_PKEY_keygen_init(ctx.get()) <= 0) - throw std::runtime_error("Failed to init keygen"); - if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx.get(), _curveNid) <= 0) - throw std::runtime_error("Failed to set curve"); + if (EVP_PKEY_keygen_init(ctx.get()) <= 0) { + throw std::runtime_error("ECDH: failed to initialize key generation"); + } + + if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx.get(), _curveNid) <= 0) { + throw std::runtime_error("ECDH: failed to set curve"); + } EVP_PKEY* pkey = nullptr; - if (EVP_PKEY_keygen(ctx.get(), &pkey) <= 0) - throw std::runtime_error("Failed to generate key"); + if (EVP_PKEY_keygen(ctx.get(), &pkey) <= 0) { + throw std::runtime_error("ECDH: failed to generate key pair"); + } - if (_pkey) - EVP_PKEY_free(_pkey); - _pkey = pkey; + _pkey.reset(pkey); return getPublicKey(); } std::shared_ptr HybridECDH::computeSecret(const std::shared_ptr& otherPublicKey) { ensureInitialized(); - if (!_pkey) - throw std::runtime_error("Private key not set"); - - // Create peer key from buffer - const unsigned char* p = otherPublicKey->data(); - // Node.js usually passes uncompressed or compressed point. We need to convert it to EVP_PKEY. - // We can use EC_KEY and then assign to EVP_PKEY. - - // Use cached group - // std::unique_ptr group(EC_GROUP_new_by_curve_name(_curveNid), EC_GROUP_free); - // if (!group) - // throw std::runtime_error("Failed to create group"); - - std::unique_ptr point(EC_POINT_new(_group), EC_POINT_free); - if (!point) - throw std::runtime_error("Failed to create point"); - - if (EC_POINT_oct2point(_group, point.get(), p, otherPublicKey->size(), nullptr) != 1) { - throw std::runtime_error("Failed to decode public key point"); - } - - std::unique_ptr ec_key(EC_KEY_new(), EC_KEY_free); - if (!ec_key) - throw std::runtime_error("Failed to create EC_KEY"); - if (EC_KEY_set_group(ec_key.get(), _group) != 1) - throw std::runtime_error("Failed to set group"); - if (EC_KEY_set_public_key(ec_key.get(), point.get()) != 1) - throw std::runtime_error("Failed to set public key"); - - std::unique_ptr peer_pkey(EVP_PKEY_new(), EVP_PKEY_free); - if (EVP_PKEY_assign_EC_KEY(peer_pkey.get(), ec_key.release()) != 1) - throw std::runtime_error("Failed to assign EC_KEY"); - - // Derive - std::unique_ptr ctx(EVP_PKEY_CTX_new(_pkey, nullptr), EVP_PKEY_CTX_free); - if (!ctx) - throw std::runtime_error("Failed to create derive context"); - - if (EVP_PKEY_derive_init(ctx.get()) <= 0) - throw std::runtime_error("Failed to init derive"); - if (EVP_PKEY_derive_set_peer(ctx.get(), peer_pkey.get()) <= 0) - throw std::runtime_error("Failed to set peer key"); - - size_t secret_len; - if (EVP_PKEY_derive(ctx.get(), nullptr, &secret_len) <= 0) - throw std::runtime_error("Failed to get secret length"); - - std::vector secret(secret_len); - if (EVP_PKEY_derive(ctx.get(), secret.data(), &secret_len) <= 0) - throw std::runtime_error("Failed to derive secret"); - - return ToNativeArrayBuffer(std::string(secret.begin(), secret.end())); // Utils.hpp + if (!_pkey) { + throw std::runtime_error("ECDH: private key not set"); + } + + // Create EC_POINT from the peer's public key bytes + EC_POINT_ptr point(EC_POINT_new(_group.get()), EC_POINT_free); + if (!point) { + throw std::runtime_error("ECDH: failed to create EC point"); + } + + if (EC_POINT_oct2point(_group.get(), point.get(), otherPublicKey->data(), otherPublicKey->size(), nullptr) != 1) { + throw std::runtime_error("ECDH: failed to decode peer public key"); + } + + // Create EC_KEY for the peer + EC_KEY_ptr ecKey(EC_KEY_new(), EC_KEY_free); + if (!ecKey) { + throw std::runtime_error("ECDH: failed to create EC_KEY"); + } + + if (EC_KEY_set_group(ecKey.get(), _group.get()) != 1) { + throw std::runtime_error("ECDH: failed to set EC group"); + } + + if (EC_KEY_set_public_key(ecKey.get(), point.get()) != 1) { + throw std::runtime_error("ECDH: failed to set peer public key"); + } + + // Create EVP_PKEY for the peer + EVP_PKEY_ptr peerPkey(EVP_PKEY_new(), EVP_PKEY_free); + if (!peerPkey) { + throw std::runtime_error("ECDH: failed to create peer EVP_PKEY"); + } + + // EVP_PKEY_assign_EC_KEY takes ownership of ecKey on success + if (EVP_PKEY_assign_EC_KEY(peerPkey.get(), ecKey.get()) != 1) { + throw std::runtime_error("ECDH: failed to assign EC_KEY to EVP_PKEY"); + } + ecKey.release(); // EVP_PKEY now owns the EC_KEY + + // Derive shared secret using EVP API + EVP_PKEY_CTX_ptr ctx(EVP_PKEY_CTX_new(_pkey.get(), nullptr), EVP_PKEY_CTX_free); + if (!ctx) { + throw std::runtime_error("ECDH: failed to create derive context"); + } + + if (EVP_PKEY_derive_init(ctx.get()) <= 0) { + throw std::runtime_error("ECDH: failed to initialize key derivation"); + } + + if (EVP_PKEY_derive_set_peer(ctx.get(), peerPkey.get()) <= 0) { + throw std::runtime_error("ECDH: failed to set peer key for derivation"); + } + + // Get required buffer size + size_t secretLen = 0; + if (EVP_PKEY_derive(ctx.get(), nullptr, &secretLen) <= 0) { + throw std::runtime_error("ECDH: failed to get shared secret length"); + } + + // Derive the shared secret + std::vector secret(secretLen); + if (EVP_PKEY_derive(ctx.get(), secret.data(), &secretLen) <= 0) { + throw std::runtime_error("ECDH: failed to derive shared secret"); + } + + secret.resize(secretLen); + + return ToNativeArrayBuffer(secret); } std::shared_ptr HybridECDH::getPrivateKey() { - if (!_pkey) - throw std::runtime_error("No key set"); - // Implement logic to extract private key (usually big number or buffer) - const EC_KEY* ec = EVP_PKEY_get0_EC_KEY(_pkey); - if (!ec) - throw std::runtime_error("Not an EC key"); + if (!_pkey) { + throw std::runtime_error("ECDH: no key set"); + } + + const EC_KEY* ec = EVP_PKEY_get0_EC_KEY(_pkey.get()); + if (!ec) { + throw std::runtime_error("ECDH: key is not an EC key"); + } + const BIGNUM* priv = EC_KEY_get0_private_key(ec); - if (!priv) - throw std::runtime_error("No private key component"); + if (!priv) { + throw std::runtime_error("ECDH: no private key available"); + } int len = BN_num_bytes(priv); std::vector buf(len); BN_bn2bin(priv, buf.data()); - return ToNativeArrayBuffer(std::string(buf.begin(), buf.end())); + + return ToNativeArrayBuffer(buf); } void HybridECDH::setPrivateKey(const std::shared_ptr& privateKey) { ensureInitialized(); - // Use cached group - // auto group = std::unique_ptr(EC_GROUP_new_by_curve_name(_curveNid), EC_GROUP_free); - auto ec = std::unique_ptr(EC_KEY_new(), EC_KEY_free); - EC_KEY_set_group(ec.get(), _group); - - BIGNUM* priv_bn = BN_bin2bn(privateKey->data(), static_cast(privateKey->size()), nullptr); - if (!priv_bn) - throw std::runtime_error("Failed to convert private key"); - // Should set public key too if possible, but Node.js allows just private. - - // Calculate public key from private - auto point = std::unique_ptr(EC_POINT_new(_group), EC_POINT_free); - EC_POINT_mul(_group, point.get(), priv_bn, nullptr, nullptr, nullptr); - - EC_KEY_set_private_key(ec.get(), priv_bn); - EC_KEY_set_public_key(ec.get(), point.get()); - BN_free(priv_bn); - - if (_pkey) - EVP_PKEY_free(_pkey); - _pkey = EVP_PKEY_new(); - EVP_PKEY_assign_EC_KEY(_pkey, ec.release()); + + // Create new EC_KEY + EC_KEY_ptr ecKey(EC_KEY_new(), EC_KEY_free); + if (!ecKey) { + throw std::runtime_error("ECDH: failed to create EC_KEY"); + } + + if (EC_KEY_set_group(ecKey.get(), _group.get()) != 1) { + throw std::runtime_error("ECDH: failed to set EC group"); + } + + // Convert private key bytes to BIGNUM + BN_ptr privBn(BN_bin2bn(privateKey->data(), static_cast(privateKey->size()), nullptr), BN_free); + if (!privBn) { + throw std::runtime_error("ECDH: failed to convert private key"); + } + + // Calculate public key from private key + EC_POINT_ptr pubPoint(EC_POINT_new(_group.get()), EC_POINT_free); + if (!pubPoint) { + throw std::runtime_error("ECDH: failed to create EC point"); + } + + if (EC_POINT_mul(_group.get(), pubPoint.get(), privBn.get(), nullptr, nullptr, nullptr) != 1) { + throw std::runtime_error("ECDH: failed to compute public key from private key"); + } + + // Set keys on EC_KEY (these functions copy the values, so we still own privBn and pubPoint) + if (EC_KEY_set_private_key(ecKey.get(), privBn.get()) != 1) { + throw std::runtime_error("ECDH: failed to set private key"); + } + + if (EC_KEY_set_public_key(ecKey.get(), pubPoint.get()) != 1) { + throw std::runtime_error("ECDH: failed to set public key"); + } + + // Create new EVP_PKEY + EVP_PKEY_ptr pkey(EVP_PKEY_new(), EVP_PKEY_free); + if (!pkey) { + throw std::runtime_error("ECDH: failed to create EVP_PKEY"); + } + + // EVP_PKEY_assign_EC_KEY takes ownership of ecKey on success + if (EVP_PKEY_assign_EC_KEY(pkey.get(), ecKey.get()) != 1) { + throw std::runtime_error("ECDH: failed to assign EC_KEY to EVP_PKEY"); + } + ecKey.release(); // EVP_PKEY now owns the EC_KEY + + _pkey = std::move(pkey); } std::shared_ptr HybridECDH::getPublicKey() { - if (!_pkey) - throw std::runtime_error("No key set"); - const EC_KEY* ec = EVP_PKEY_get0_EC_KEY(_pkey); - if (!ec) - throw std::runtime_error("Not an EC key"); + if (!_pkey) { + throw std::runtime_error("ECDH: no key set"); + } + + const EC_KEY* ec = EVP_PKEY_get0_EC_KEY(_pkey.get()); + if (!ec) { + throw std::runtime_error("ECDH: key is not an EC key"); + } + const EC_POINT* point = EC_KEY_get0_public_key(ec); const EC_GROUP* group = EC_KEY_get0_group(ec); - if (!point || !group) - throw std::runtime_error("Incomplete key"); + if (!point || !group) { + throw std::runtime_error("ECDH: incomplete key"); + } - // Default to uncompressed + // Get uncompressed public key size size_t len = EC_POINT_point2oct(group, point, POINT_CONVERSION_UNCOMPRESSED, nullptr, 0, nullptr); - if (len == 0) - throw std::runtime_error("Failed to get pubkey length"); + if (len == 0) { + throw std::runtime_error("ECDH: failed to get public key length"); + } + std::vector buf(len); if (EC_POINT_point2oct(group, point, POINT_CONVERSION_UNCOMPRESSED, buf.data(), len, nullptr) == 0) { - throw std::runtime_error("Failed to encode pubkey"); + throw std::runtime_error("ECDH: failed to encode public key"); } - return ToNativeArrayBuffer(std::string(buf.begin(), buf.end())); + + return ToNativeArrayBuffer(buf); } void HybridECDH::setPublicKey(const std::shared_ptr& publicKey) { ensureInitialized(); - // Cannot set ONLY public key on an ECDH object usually intended for own keys, - // but Node.js allows it? Or is this for "other" key usually? - // Node.js setPublicKey sets the public key of the ECDH object. - // Use cached group - // auto group = std::unique_ptr(EC_GROUP_new_by_curve_name(_curveNid), EC_GROUP_free); - auto point = std::unique_ptr(EC_POINT_new(_group), EC_POINT_free); + // Create EC_POINT from the public key bytes + EC_POINT_ptr point(EC_POINT_new(_group.get()), EC_POINT_free); + if (!point) { + throw std::runtime_error("ECDH: failed to create EC point"); + } - if (EC_POINT_oct2point(_group, point.get(), (const unsigned char*)publicKey->data(), publicKey->size(), nullptr) != 1) { - throw std::runtime_error("Invalid public key"); + if (EC_POINT_oct2point(_group.get(), point.get(), publicKey->data(), publicKey->size(), nullptr) != 1) { + throw std::runtime_error("ECDH: invalid public key"); } - auto ec = std::unique_ptr(EC_KEY_new(), EC_KEY_free); - EC_KEY_set_group(ec.get(), _group); - EC_KEY_set_public_key(ec.get(), point.get()); + // Create new EC_KEY + EC_KEY_ptr ecKey(EC_KEY_new(), EC_KEY_free); + if (!ecKey) { + throw std::runtime_error("ECDH: failed to create EC_KEY"); + } - if (_pkey) - EVP_PKEY_free(_pkey); - _pkey = EVP_PKEY_new(); - EVP_PKEY_assign_EC_KEY(_pkey, ec.release()); + if (EC_KEY_set_group(ecKey.get(), _group.get()) != 1) { + throw std::runtime_error("ECDH: failed to set EC group"); + } + + if (EC_KEY_set_public_key(ecKey.get(), point.get()) != 1) { + throw std::runtime_error("ECDH: failed to set public key"); + } + + // Create new EVP_PKEY + EVP_PKEY_ptr pkey(EVP_PKEY_new(), EVP_PKEY_free); + if (!pkey) { + throw std::runtime_error("ECDH: failed to create EVP_PKEY"); + } + + // EVP_PKEY_assign_EC_KEY takes ownership of ecKey on success + if (EVP_PKEY_assign_EC_KEY(pkey.get(), ecKey.get()) != 1) { + throw std::runtime_error("ECDH: failed to assign EC_KEY to EVP_PKEY"); + } + ecKey.release(); // EVP_PKEY now owns the EC_KEY + + _pkey = std::move(pkey); } -void HybridECDH::ensureInitialized() { +void HybridECDH::ensureInitialized() const { if (_curveNid == 0 || !_group) { - throw std::runtime_error("ECDH not initialized"); + throw std::runtime_error("ECDH: not initialized"); } } int HybridECDH::getCurveNid(const std::string& name) { int nid = OBJ_txt2nid(name.c_str()); - if (nid == NID_undef) + if (nid == NID_undef) { nid = OBJ_sn2nid(name.c_str()); - if (nid == NID_undef) + } + if (nid == NID_undef) { nid = OBJ_ln2nid(name.c_str()); + } return nid; } #pragma clang diagnostic pop + } // namespace margelo::nitro::crypto diff --git a/packages/react-native-quick-crypto/nitro.json b/packages/react-native-quick-crypto/nitro.json index 85ae64a4..2bdfe7ab 100644 --- a/packages/react-native-quick-crypto/nitro.json +++ b/packages/react-native-quick-crypto/nitro.json @@ -1,14 +1,10 @@ { - "cxxNamespace": [ - "crypto" - ], + "cxxNamespace": ["crypto"], "ios": { "iosModuleName": "QuickCrypto" }, "android": { - "androidNamespace": [ - "crypto" - ], + "androidNamespace": ["crypto"], "androidCxxLibName": "QuickCrypto" }, "autolinking": { @@ -21,6 +17,12 @@ "CipherFactory": { "cpp": "HybridCipherFactory" }, + "DiffieHellman": { + "cpp": "HybridDiffieHellman" + }, + "ECDH": { + "cpp": "HybridECDH" + }, "EcKeyPair": { "cpp": "HybridEcKeyPair" }, @@ -30,15 +32,18 @@ "Hash": { "cpp": "HybridHash" }, - "Hmac": { - "cpp": "HybridHmac" - }, "Hkdf": { "cpp": "HybridHkdf" }, + "Hmac": { + "cpp": "HybridHmac" + }, "KeyObjectHandle": { "cpp": "HybridKeyObjectHandle" }, + "MlDsaKeyPair": { + "cpp": "HybridMlDsaKeyPair" + }, "Pbkdf2": { "cpp": "HybridPbkdf2" }, @@ -51,30 +56,18 @@ "RsaKeyPair": { "cpp": "HybridRsaKeyPair" }, - "SignHandle": { - "cpp": "HybridSignHandle" - }, - "VerifyHandle": { - "cpp": "HybridVerifyHandle" - }, - "MlDsaKeyPair": { - "cpp": "HybridMlDsaKeyPair" - }, "Scrypt": { "cpp": "HybridScrypt" }, + "SignHandle": { + "cpp": "HybridSignHandle" + }, "Utils": { "cpp": "HybridUtils" }, - "ECDH": { - "cpp": "HybridECDH" - }, - "DiffieHellman": { - "cpp": "HybridDiffieHellman" + "VerifyHandle": { + "cpp": "HybridVerifyHandle" } }, - "ignorePaths": [ - "node_modules", - "lib" - ] -} \ No newline at end of file + "ignorePaths": ["node_modules", "lib"] +} diff --git a/packages/react-native-quick-crypto/src/keys/generateKeyPair.ts b/packages/react-native-quick-crypto/src/keys/generateKeyPair.ts index 4635865a..904c8a12 100644 --- a/packages/react-native-quick-crypto/src/keys/generateKeyPair.ts +++ b/packages/react-native-quick-crypto/src/keys/generateKeyPair.ts @@ -10,6 +10,7 @@ import { type GenerateKeyPairPromiseReturn, type GenerateKeyPairReturn, type KeyPairGenConfig, + type KeyPairKey, type KeyPairType, } from '../utils'; import { parsePrivateKeyEncoding, parsePublicKeyEncoding } from './utils'; @@ -160,8 +161,11 @@ function internalGenerateKeyPair( } else { throw new Error(`Unsupported key type: ${type}`); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return [undefined, result.publicKey as any, result.privateKey as any]; + return [ + undefined, + result.publicKey as KeyPairKey, + result.privateKey as KeyPairKey, + ]; } catch (error) { return [error as Error, undefined, undefined]; } @@ -183,8 +187,11 @@ function internalGenerateKeyPair( } else { throw new Error(`Unsupported key type: ${type}`); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return [undefined, result.publicKey as any, result.privateKey as any]; + return [ + undefined, + result.publicKey as KeyPairKey, + result.privateKey as KeyPairKey, + ]; } catch (error) { return [error as Error, undefined, undefined]; } From 8ea6129309b2af5bed33ecc0b6ec51f1fc3e619a Mon Sep 17 00:00:00 2001 From: Brad Anderson Date: Sat, 31 Jan 2026 10:08:38 -0500 Subject: [PATCH 3/4] fix: add smart pointer headers and vector ToNativeArrayBuffer overload --- .../cpp/dh/HybridDiffieHellman.hpp | 17 ++++++------- .../cpp/ecdh/HybridECDH.hpp | 25 +++++++------------ .../cpp/utils/QuickCryptoUtils.hpp | 14 +++++++++++ 3 files changed, 31 insertions(+), 25 deletions(-) diff --git a/packages/react-native-quick-crypto/cpp/dh/HybridDiffieHellman.hpp b/packages/react-native-quick-crypto/cpp/dh/HybridDiffieHellman.hpp index 02acfa48..c4a4bd9e 100644 --- a/packages/react-native-quick-crypto/cpp/dh/HybridDiffieHellman.hpp +++ b/packages/react-native-quick-crypto/cpp/dh/HybridDiffieHellman.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -12,15 +13,12 @@ namespace margelo::nitro::crypto { using namespace facebook; using margelo::nitro::ArrayBuffer; +using EVP_PKEY_ptr = std::unique_ptr; + class HybridDiffieHellman : public HybridDiffieHellmanSpec { public: - HybridDiffieHellman() : HybridObject("DiffieHellman") {} - virtual ~HybridDiffieHellman() { - if (_pkey) { - EVP_PKEY_free(_pkey); - _pkey = nullptr; - } - } + HybridDiffieHellman() : HybridObject("DiffieHellman"), _pkey(nullptr, EVP_PKEY_free) {} + virtual ~HybridDiffieHellman() = default; void init(const std::shared_ptr& prime, const std::shared_ptr& generator) override; void initWithSize(double primeLength, double generator) override; @@ -34,9 +32,10 @@ class HybridDiffieHellman : public HybridDiffieHellmanSpec { void setPrivateKey(const std::shared_ptr& privateKey) override; private: - EVP_PKEY* _pkey = nullptr; + EVP_PKEY_ptr _pkey; - void ensureInitialized(); + void ensureInitialized() const; + const DH* getDH() const; }; } // namespace margelo::nitro::crypto diff --git a/packages/react-native-quick-crypto/cpp/ecdh/HybridECDH.hpp b/packages/react-native-quick-crypto/cpp/ecdh/HybridECDH.hpp index a77effd2..2e685e4b 100644 --- a/packages/react-native-quick-crypto/cpp/ecdh/HybridECDH.hpp +++ b/packages/react-native-quick-crypto/cpp/ecdh/HybridECDH.hpp @@ -3,7 +3,6 @@ #include #include #include - #include #include @@ -14,19 +13,13 @@ namespace margelo::nitro::crypto { using namespace facebook; using margelo::nitro::ArrayBuffer; +using EVP_PKEY_ptr = std::unique_ptr; +using EC_GROUP_ptr = std::unique_ptr; + class HybridECDH : public HybridECDHSpec { public: - HybridECDH() : HybridObject("ECDH") {} - virtual ~HybridECDH() { - if (_pkey) { - EVP_PKEY_free(_pkey); - _pkey = nullptr; - } - if (_group) { - EC_GROUP_free(_group); - _group = nullptr; - } - } + HybridECDH() : HybridObject("ECDH"), _pkey(nullptr, EVP_PKEY_free), _group(nullptr, EC_GROUP_free) {} + virtual ~HybridECDH() = default; void init(const std::string& curveName) override; std::shared_ptr generateKeys() override; @@ -37,13 +30,13 @@ class HybridECDH : public HybridECDHSpec { void setPublicKey(const std::shared_ptr& publicKey) override; private: - EVP_PKEY* _pkey = nullptr; - EC_GROUP* _group = nullptr; + EVP_PKEY_ptr _pkey; + EC_GROUP_ptr _group; std::string _curveName; int _curveNid = 0; - void ensureInitialized(); - int getCurveNid(const std::string& name); + void ensureInitialized() const; + static int getCurveNid(const std::string& name); }; } // namespace margelo::nitro::crypto diff --git a/packages/react-native-quick-crypto/cpp/utils/QuickCryptoUtils.hpp b/packages/react-native-quick-crypto/cpp/utils/QuickCryptoUtils.hpp index d058a871..88606cad 100644 --- a/packages/react-native-quick-crypto/cpp/utils/QuickCryptoUtils.hpp +++ b/packages/react-native-quick-crypto/cpp/utils/QuickCryptoUtils.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "Macros.hpp" #include @@ -44,6 +45,19 @@ inline std::shared_ptr ToNativeArrayBuffer(st return std::make_shared(data, size, [=]() { delete[] data; }); } +inline std::shared_ptr ToNativeArrayBuffer(const std::vector& vec) { + size_t size = vec.size(); + uint8_t* data = new uint8_t[size]; + memcpy(data, vec.data(), size); + return std::make_shared(data, size, [=]() { delete[] data; }); +} + +inline std::shared_ptr ToNativeArrayBuffer(const uint8_t* ptr, size_t size) { + uint8_t* data = new uint8_t[size]; + memcpy(data, ptr, size); + return std::make_shared(data, size, [=]() { delete[] data; }); +} + inline bool CheckIsUint32(double value) { return (value >= std::numeric_limits::lowest() && value <= std::numeric_limits::max()); } From af4906bf9852847093b928e0c10bfd2a647e36c7 Mon Sep 17 00:00:00 2001 From: Brad Anderson Date: Sat, 31 Jan 2026 10:10:20 -0500 Subject: [PATCH 4/4] fix: update tests, benchmarks, and implementation coverage --- .docs/implementation-coverage.md | 47 ++-- bun.lock | 203 ++++++++++-------- example/ios/Podfile.lock | 2 +- .../project.pbxproj | 2 +- example/package.json | 2 +- example/src/benchmarks/ecdh/ecdh.ts | 4 +- example/src/tests/dh/dh_tests.ts | 99 +++++---- example/src/tests/ecdh/ecdh_tests.ts | 4 - 8 files changed, 204 insertions(+), 159 deletions(-) diff --git a/.docs/implementation-coverage.md b/.docs/implementation-coverage.md index 81cfd3cb..65048c2c 100644 --- a/.docs/implementation-coverage.md +++ b/.docs/implementation-coverage.md @@ -34,25 +34,25 @@ These algorithms provide quantum-resistant cryptography. * ✅ `decipher.setAuthTag(buffer[, encoding])` * ✅ `decipher.setAutoPadding([autoPadding])` * ✅ `decipher.update(data[, inputEncoding][, outputEncoding])` -* ❌ Class: `DiffieHellman` - * ❌ `diffieHellman.computeSecret(otherPublicKey[, inputEncoding][, outputEncoding])` - * ❌ `diffieHellman.generateKeys([encoding])` - * ❌ `diffieHellman.getGenerator([encoding])` - * ❌ `diffieHellman.getPrime([encoding])` - * ❌ `diffieHellman.getPrivateKey([encoding])` - * ❌ `diffieHellman.getPublicKey([encoding])` - * ❌ `diffieHellman.setPrivateKey(privateKey[, encoding])` - * ❌ `diffieHellman.setPublicKey(publicKey[, encoding])` +* ✅ Class: `DiffieHellman` + * ✅ `diffieHellman.computeSecret(otherPublicKey[, inputEncoding][, outputEncoding])` + * ✅ `diffieHellman.generateKeys([encoding])` + * ✅ `diffieHellman.getGenerator([encoding])` + * ✅ `diffieHellman.getPrime([encoding])` + * ✅ `diffieHellman.getPrivateKey([encoding])` + * ✅ `diffieHellman.getPublicKey([encoding])` + * ✅ `diffieHellman.setPrivateKey(privateKey[, encoding])` + * ✅ `diffieHellman.setPublicKey(publicKey[, encoding])` * ❌ `diffieHellman.verifyError` -* ❌ Class: `DiffieHellmanGroup` -* ❌ Class: `ECDH` +* ✅ Class: `DiffieHellmanGroup` +* ✅ Class: `ECDH` * ❌ static `ECDH.convertKey(key, curve[, inputEncoding[, outputEncoding[, format]]])` - * ❌ `ecdh.computeSecret(otherPublicKey[, inputEncoding][, outputEncoding])` - * ❌ `ecdh.generateKeys([encoding[, format]])` - * ❌ `ecdh.getPrivateKey([encoding])` - * ❌ `ecdh.getPublicKey([encoding][, format])` - * ❌ `ecdh.setPrivateKey(privateKey[, encoding])` - * ❌ `ecdh.setPublicKey(publicKey[, encoding])` + * ✅ `ecdh.computeSecret(otherPublicKey[, inputEncoding][, outputEncoding])` + * ✅ `ecdh.generateKeys([encoding[, format]])` + * ✅ `ecdh.getPrivateKey([encoding])` + * ✅ `ecdh.getPublicKey([encoding][, format])` + * ✅ `ecdh.setPrivateKey(privateKey[, encoding])` + * ✅ `ecdh.setPublicKey(publicKey[, encoding])` * ✅ Class: `Hash` * ✅ `hash.copy([options])` * ✅ `hash.digest([encoding])` @@ -109,10 +109,10 @@ These algorithms provide quantum-resistant cryptography. * ✅ `crypto.constants` * ✅ `crypto.createCipheriv(algorithm, key, iv[, options])` * ✅ `crypto.createDecipheriv(algorithm, key, iv[, options])` - * ❌ `crypto.createDiffieHellman(prime[, primeEncoding][, generator][, generatorEncoding])` - * ❌ `crypto.createDiffieHellman(primeLength[, generator])` - * ❌ `crypto.createDiffieHellmanGroup(name)` - * ❌ `crypto.createECDH(curveName)` + * ✅ `crypto.createDiffieHellman(prime[, primeEncoding][, generator][, generatorEncoding])` + * ✅ `crypto.createDiffieHellman(primeLength[, generator])` + * ✅ `crypto.getDiffieHellman(groupName)` + * ✅ `crypto.createECDH(curveName)` * ✅ `crypto.createHash(algorithm[, options])` * ✅ `crypto.createHmac(algorithm, key[, options])` * ✅ `crypto.createPrivateKey(key)` @@ -133,7 +133,6 @@ These algorithms provide quantum-resistant cryptography. * ❌ `crypto.getCipherInfo(nameOrNid[, options])` * ✅ `crypto.getCiphers()` * ❌ `crypto.getCurves()` - * ❌ `crypto.getDiffieHellman(groupName)` * ❌ `crypto.getFips()` * ✅ `crypto.getHashes()` * ✅ `crypto.getRandomValues(typedArray)` @@ -165,8 +164,8 @@ These algorithms provide quantum-resistant cryptography. ## `crypto.diffieHellman` | type | Status | | --------- | :----: | -| `dh` | ❌ | -| `ec` | ❌ | +| `dh` | ✅ | +| `ec` | ✅ | | `x448` | ✅ | | `x25519` | ✅ | diff --git a/bun.lock b/bun.lock index f4e0ff0a..c04d43b2 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "dependencies": { @@ -648,9 +649,9 @@ "@react-native/community-cli-plugin": ["@react-native/community-cli-plugin@0.81.1", "", { "dependencies": { "@react-native/dev-middleware": "0.81.1", "debug": "^4.4.0", "invariant": "^2.2.4", "metro": "^0.83.1", "metro-config": "^0.83.1", "metro-core": "^0.83.1", "semver": "^7.1.3" }, "peerDependencies": { "@react-native-community/cli": "*", "@react-native/metro-config": "*" }, "optionalPeers": ["@react-native-community/cli", "@react-native/metro-config"] }, "sha512-FuIpZcjBiiYcVMNx+1JBqTPLs2bUIm6X4F5enYGYcetNE2nfSMUVO8SGUtTkBdbUTfKesXYSYN8wungyro28Ag=="], - "@react-native/debugger-frontend": ["@react-native/debugger-frontend@0.81.1", "", {}, "sha512-dwKv1EqKD+vONN4xsfyTXxn291CNl1LeBpaHhNGWASK1GO4qlyExMs4TtTjN57BnYHikR9PzqPWcUcfzpVRaLg=="], + "@react-native/debugger-frontend": ["@react-native/debugger-frontend@0.81.5", "", {}, "sha512-bnd9FSdWKx2ncklOetCgrlwqSGhMHP2zOxObJbOWXoj7GHEmih4MKarBo5/a8gX8EfA1EwRATdfNBQ81DY+h+w=="], - "@react-native/dev-middleware": ["@react-native/dev-middleware@0.81.1", "", { "dependencies": { "@isaacs/ttlcache": "^1.4.1", "@react-native/debugger-frontend": "0.81.1", "chrome-launcher": "^0.15.2", "chromium-edge-launcher": "^0.2.0", "connect": "^3.6.5", "debug": "^4.4.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "open": "^7.0.3", "serve-static": "^1.16.2", "ws": "^6.2.3" } }, "sha512-hy3KlxNOfev3O5/IuyZSstixWo7E9FhljxKGHdvVtZVNjQdM+kPMh66mxeJbB2TjdJGAyBT4DjIwBaZnIFOGHQ=="], + "@react-native/dev-middleware": ["@react-native/dev-middleware@0.81.5", "", { "dependencies": { "@isaacs/ttlcache": "^1.4.1", "@react-native/debugger-frontend": "0.81.5", "chrome-launcher": "^0.15.2", "chromium-edge-launcher": "^0.2.0", "connect": "^3.6.5", "debug": "^4.4.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "open": "^7.0.3", "serve-static": "^1.16.2", "ws": "^6.2.3" } }, "sha512-WfPfZzboYgo/TUtysuD5xyANzzfka8Ebni6RIb2wDxhb56ERi7qDrE4xGhtPsjCL4pQBXSVxyIlCy0d8I6EgGA=="], "@react-native/eslint-config": ["@react-native/eslint-config@0.81.1", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/eslint-parser": "^7.25.1", "@react-native/eslint-plugin": "0.81.1", "@typescript-eslint/eslint-plugin": "^7.1.1", "@typescript-eslint/parser": "^7.1.1", "eslint-config-prettier": "^8.5.0", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-ft-flow": "^2.0.1", "eslint-plugin-jest": "^27.9.0", "eslint-plugin-react": "^7.30.1", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-native": "^4.0.0" }, "peerDependencies": { "eslint": ">=8", "prettier": ">=2" } }, "sha512-Ljn2H34G0+2LIwEwYTjwpjB+Z4KnMN5FuNbfX5B3j6YAcAFt8DVppNVTVE2dKyId5DIweOZ93Yf4zj6v+jxzYw=="], @@ -882,7 +883,7 @@ "babel-plugin-react-native-web": ["babel-plugin-react-native-web@0.21.2", "", {}, "sha512-SPD0J6qjJn8231i0HZhlAGH6NORe+QvRSQM2mwQEzJ2Fb3E4ruWTiiicPlHjmeWShDXLcvoorOCXjeR7k/lyWA=="], - "babel-plugin-syntax-hermes-parser": ["babel-plugin-syntax-hermes-parser@0.29.1", "", { "dependencies": { "hermes-parser": "0.29.1" } }, "sha512-2WFYnoWGdmih1I1J5eIqxATOeycOqRwYxAQBu3cUu/rhwInwHUg7k60AFNbuGjSDL8tje5GDrAnxzRLcu2pYcA=="], + "babel-plugin-syntax-hermes-parser": ["babel-plugin-syntax-hermes-parser@0.28.1", "", { "dependencies": { "hermes-parser": "0.28.1" } }, "sha512-meT17DOuUElMNsL5LZN56d+KBp22hb0EfxWfuPUeoSi54e40v1W4C2V36P75FpsH9fVEfDKpw5Nnkahc8haSsQ=="], "babel-plugin-transform-flow-enums": ["babel-plugin-transform-flow-enums@0.0.2", "", { "dependencies": { "@babel/plugin-syntax-flow": "^7.12.1" } }, ""], @@ -1420,7 +1421,7 @@ "git-url-parse": ["git-url-parse@16.1.0", "", { "dependencies": { "git-up": "^8.1.0" } }, "sha512-cPLz4HuK86wClEW7iDdeAKcCVlWXmrLpb2L+G9goW0Z1dtpNS6BXXSOckUTlJT/LDQViE1QZKstNORzHsLnobw=="], - "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, ""], + "glob": ["glob@8.1.0", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^5.0.1", "once": "^1.3.0" } }, ""], "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, ""], @@ -1806,7 +1807,7 @@ "merge2": ["merge2@1.4.1", "", {}, ""], - "metro": ["metro@0.83.3", "", { "dependencies": { "@babel/code-frame": "^7.24.7", "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/parser": "^7.25.3", "@babel/template": "^7.25.0", "@babel/traverse": "^7.25.3", "@babel/types": "^7.25.2", "accepts": "^1.3.7", "chalk": "^4.0.0", "ci-info": "^2.0.0", "connect": "^3.6.5", "debug": "^4.4.0", "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "hermes-parser": "0.32.0", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", "metro-babel-transformer": "0.83.3", "metro-cache": "0.83.3", "metro-cache-key": "0.83.3", "metro-config": "0.83.3", "metro-core": "0.83.3", "metro-file-map": "0.83.3", "metro-resolver": "0.83.3", "metro-runtime": "0.83.3", "metro-source-map": "0.83.3", "metro-symbolicate": "0.83.3", "metro-transform-plugins": "0.83.3", "metro-transform-worker": "0.83.3", "mime-types": "^2.1.27", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", "source-map": "^0.5.6", "throat": "^5.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { "metro": "src/cli.js" } }, "sha512-+rP+/GieOzkt97hSJ0MrPOuAH/jpaS21ZDvL9DJ35QYRDlQcwzcvUlGUf79AnQxq/2NPiS/AULhhM4TKutIt8Q=="], + "metro": ["metro@0.83.2", "", { "dependencies": { "@babel/code-frame": "^7.24.7", "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/parser": "^7.25.3", "@babel/template": "^7.25.0", "@babel/traverse": "^7.25.3", "@babel/types": "^7.25.2", "accepts": "^1.3.7", "chalk": "^4.0.0", "ci-info": "^2.0.0", "connect": "^3.6.5", "debug": "^4.4.0", "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "hermes-parser": "0.32.0", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", "metro-babel-transformer": "0.83.2", "metro-cache": "0.83.2", "metro-cache-key": "0.83.2", "metro-config": "0.83.2", "metro-core": "0.83.2", "metro-file-map": "0.83.2", "metro-resolver": "0.83.2", "metro-runtime": "0.83.2", "metro-source-map": "0.83.2", "metro-symbolicate": "0.83.2", "metro-transform-plugins": "0.83.2", "metro-transform-worker": "0.83.2", "mime-types": "^2.1.27", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", "source-map": "^0.5.6", "throat": "^5.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { "metro": "src/cli.js" } }, "sha512-HQgs9H1FyVbRptNSMy/ImchTTE5vS2MSqLoOo7hbDoBq6hPPZokwJvBMwrYSxdjQZmLXz2JFZtdvS+ZfgTc9yw=="], "metro-babel-transformer": ["metro-babel-transformer@0.83.2", "", { "dependencies": { "@babel/core": "^7.25.2", "flow-enums-runtime": "^0.0.6", "hermes-parser": "0.32.0", "nullthrows": "^1.1.1" } }, "sha512-rirY1QMFlA1uxH3ZiNauBninwTioOgwChnRdDcbB4tgRZ+bGX9DiXoh9QdpppiaVKXdJsII932OwWXGGV4+Nlw=="], @@ -1816,7 +1817,7 @@ "metro-config": ["metro-config@0.83.3", "", { "dependencies": { "connect": "^3.6.5", "flow-enums-runtime": "^0.0.6", "jest-validate": "^29.7.0", "metro": "0.83.3", "metro-cache": "0.83.3", "metro-core": "0.83.3", "metro-runtime": "0.83.3", "yaml": "^2.6.1" } }, "sha512-mTel7ipT0yNjKILIan04bkJkuCzUUkm2SeEaTads8VfEecCh+ltXchdq6DovXJqzQAXuR2P9cxZB47Lg4klriA=="], - "metro-core": ["metro-core@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", "metro-resolver": "0.83.3" } }, "sha512-M+X59lm7oBmJZamc96usuF1kusd5YimqG/q97g4Ac7slnJ3YiGglW5CsOlicTR5EWf8MQFxxjDoB6ytTqRe8Hw=="], + "metro-core": ["metro-core@0.83.2", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", "metro-resolver": "0.83.2" } }, "sha512-8DRb0O82Br0IW77cNgKMLYWUkx48lWxUkvNUxVISyMkcNwE/9ywf1MYQUE88HaKwSrqne6kFgCSA/UWZoUT0Iw=="], "metro-file-map": ["metro-file-map@0.83.2", "", { "dependencies": { "debug": "^4.4.0", "fb-watchman": "^2.0.0", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "nullthrows": "^1.1.1", "walker": "^1.0.7" } }, "sha512-cMSWnEqZrp/dzZIEd7DEDdk72PXz6w5NOKriJoDN9p1TDQ5nAYrY2lHi8d6mwbcGLoSlWmpPyny9HZYFfPWcGQ=="], @@ -2682,8 +2683,6 @@ "@eslint/plugin-kit/@eslint/core": ["@eslint/core@0.13.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, ""], - "@expo/cli/@react-native/dev-middleware": ["@react-native/dev-middleware@0.81.5", "", { "dependencies": { "@isaacs/ttlcache": "^1.4.1", "@react-native/debugger-frontend": "0.81.5", "chrome-launcher": "^0.15.2", "chromium-edge-launcher": "^0.2.0", "connect": "^3.6.5", "debug": "^4.4.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "open": "^7.0.3", "serve-static": "^1.16.2", "ws": "^6.2.3" } }, "sha512-WfPfZzboYgo/TUtysuD5xyANzzfka8Ebni6RIb2wDxhb56ERi7qDrE4xGhtPsjCL4pQBXSVxyIlCy0d8I6EgGA=="], - "@expo/cli/ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="], "@expo/cli/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": "dist/esm/bin.mjs" }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], @@ -2732,12 +2731,8 @@ "@expo/mcp-tunnel/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - "@expo/metro/metro": ["metro@0.83.2", "", { "dependencies": { "@babel/code-frame": "^7.24.7", "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/parser": "^7.25.3", "@babel/template": "^7.25.0", "@babel/traverse": "^7.25.3", "@babel/types": "^7.25.2", "accepts": "^1.3.7", "chalk": "^4.0.0", "ci-info": "^2.0.0", "connect": "^3.6.5", "debug": "^4.4.0", "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "hermes-parser": "0.32.0", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", "metro-babel-transformer": "0.83.2", "metro-cache": "0.83.2", "metro-cache-key": "0.83.2", "metro-config": "0.83.2", "metro-core": "0.83.2", "metro-file-map": "0.83.2", "metro-resolver": "0.83.2", "metro-runtime": "0.83.2", "metro-source-map": "0.83.2", "metro-symbolicate": "0.83.2", "metro-transform-plugins": "0.83.2", "metro-transform-worker": "0.83.2", "mime-types": "^2.1.27", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", "source-map": "^0.5.6", "throat": "^5.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { "metro": "src/cli.js" } }, "sha512-HQgs9H1FyVbRptNSMy/ImchTTE5vS2MSqLoOo7hbDoBq6hPPZokwJvBMwrYSxdjQZmLXz2JFZtdvS+ZfgTc9yw=="], - "@expo/metro/metro-config": ["metro-config@0.83.2", "", { "dependencies": { "connect": "^3.6.5", "flow-enums-runtime": "^0.0.6", "jest-validate": "^29.7.0", "metro": "0.83.2", "metro-cache": "0.83.2", "metro-core": "0.83.2", "metro-runtime": "0.83.2", "yaml": "^2.6.1" } }, "sha512-1FjCcdBe3e3D08gSSiU9u3Vtxd7alGH3x/DNFqWDFf5NouX4kLgbVloDDClr1UrLz62c0fHh2Vfr9ecmrOZp+g=="], - "@expo/metro/metro-core": ["metro-core@0.83.2", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", "metro-resolver": "0.83.2" } }, "sha512-8DRb0O82Br0IW77cNgKMLYWUkx48lWxUkvNUxVISyMkcNwE/9ywf1MYQUE88HaKwSrqne6kFgCSA/UWZoUT0Iw=="], - "@expo/metro/metro-runtime": ["metro-runtime@0.83.2", "", { "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" } }, "sha512-nnsPtgRvFbNKwemqs0FuyFDzXLl+ezuFsUXDbX8o0SXOfsOPijqiQrf3kuafO1Zx1aUWf4NOrKJMAQP5EEHg9A=="], "@expo/metro/metro-source-map": ["metro-source-map@0.83.2", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3", "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-symbolicate": "0.83.2", "nullthrows": "^1.1.1", "ob1": "0.83.2", "source-map": "^0.5.6", "vlq": "^1.0.0" } }, "sha512-5FL/6BSQvshIKjXOennt9upFngq2lFvDakZn5LfauIVq8+L4sxXewIlSTcxAtzbtjAIaXeOSVMtCJ5DdfCt9AA=="], @@ -2796,6 +2791,8 @@ "@jest/reporters/@types/node": ["@types/node@22.0.0", "", { "dependencies": { "undici-types": "~6.11.1" } }, ""], + "@jest/reporters/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, ""], + "@jest/reporters/istanbul-lib-instrument": ["istanbul-lib-instrument@6.0.3", "", { "dependencies": { "@babel/core": "^7.23.9", "@babel/parser": "^7.23.9", "@istanbuljs/schema": "^0.1.3", "istanbul-lib-coverage": "^3.2.0", "semver": "^7.5.4" } }, ""], "@jest/reporters/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], @@ -2844,6 +2841,16 @@ "@react-native/babel-preset/@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.25.2", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.24.7", "@babel/helper-create-class-features-plugin": "^7.25.0", "@babel/helper-plugin-utils": "^7.24.8", "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", "@babel/plugin-syntax-typescript": "^7.24.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, ""], + "@react-native/babel-preset/babel-plugin-syntax-hermes-parser": ["babel-plugin-syntax-hermes-parser@0.29.1", "", { "dependencies": { "hermes-parser": "0.29.1" } }, "sha512-2WFYnoWGdmih1I1J5eIqxATOeycOqRwYxAQBu3cUu/rhwInwHUg7k60AFNbuGjSDL8tje5GDrAnxzRLcu2pYcA=="], + + "@react-native/codegen/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, ""], + + "@react-native/community-cli-plugin/@react-native/dev-middleware": ["@react-native/dev-middleware@0.81.1", "", { "dependencies": { "@isaacs/ttlcache": "^1.4.1", "@react-native/debugger-frontend": "0.81.1", "chrome-launcher": "^0.15.2", "chromium-edge-launcher": "^0.2.0", "connect": "^3.6.5", "debug": "^4.4.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "open": "^7.0.3", "serve-static": "^1.16.2", "ws": "^6.2.3" } }, "sha512-hy3KlxNOfev3O5/IuyZSstixWo7E9FhljxKGHdvVtZVNjQdM+kPMh66mxeJbB2TjdJGAyBT4DjIwBaZnIFOGHQ=="], + + "@react-native/community-cli-plugin/metro": ["metro@0.83.3", "", { "dependencies": { "@babel/code-frame": "^7.24.7", "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/parser": "^7.25.3", "@babel/template": "^7.25.0", "@babel/traverse": "^7.25.3", "@babel/types": "^7.25.2", "accepts": "^1.3.7", "chalk": "^4.0.0", "ci-info": "^2.0.0", "connect": "^3.6.5", "debug": "^4.4.0", "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "hermes-parser": "0.32.0", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", "metro-babel-transformer": "0.83.3", "metro-cache": "0.83.3", "metro-cache-key": "0.83.3", "metro-config": "0.83.3", "metro-core": "0.83.3", "metro-file-map": "0.83.3", "metro-resolver": "0.83.3", "metro-runtime": "0.83.3", "metro-source-map": "0.83.3", "metro-symbolicate": "0.83.3", "metro-transform-plugins": "0.83.3", "metro-transform-worker": "0.83.3", "mime-types": "^2.1.27", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", "source-map": "^0.5.6", "throat": "^5.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { "metro": "src/cli.js" } }, "sha512-+rP+/GieOzkt97hSJ0MrPOuAH/jpaS21ZDvL9DJ35QYRDlQcwzcvUlGUf79AnQxq/2NPiS/AULhhM4TKutIt8Q=="], + + "@react-native/community-cli-plugin/metro-core": ["metro-core@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", "metro-resolver": "0.83.3" } }, "sha512-M+X59lm7oBmJZamc96usuF1kusd5YimqG/q97g4Ac7slnJ3YiGglW5CsOlicTR5EWf8MQFxxjDoB6ytTqRe8Hw=="], + "@react-native/community-cli-plugin/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], "@react-native/dev-middleware/open": ["open@7.4.2", "", { "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" } }, ""], @@ -2912,6 +2919,8 @@ "babel-plugin-polyfill-corejs2/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, ""], + "babel-plugin-syntax-hermes-parser/hermes-parser": ["hermes-parser@0.28.1", "", { "dependencies": { "hermes-estree": "0.28.1" } }, "sha512-nf8o+hE8g7UJWParnccljHumE9Vlq8F7MqIdeahl+4x0tvCUJYRrT0L7h0MMg/X9YJmkNwsfbaNNrzPtFXOscg=="], + "babel-plugin-transform-flow-enums/@babel/plugin-syntax-flow": ["@babel/plugin-syntax-flow@7.24.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.24.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, ""], "babel-preset-current-node-syntax/@babel/plugin-syntax-import-attributes": ["@babel/plugin-syntax-import-attributes@7.24.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.24.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, ""], @@ -2922,6 +2931,8 @@ "babel-preset-expo/@react-native/babel-preset": ["@react-native/babel-preset@0.81.5", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-arrow-functions": "^7.24.7", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-computed-properties": "^7.24.7", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-function-name": "^7.25.1", "@babel/plugin-transform-literals": "^7.25.2", "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-numeric-separator": "^7.24.7", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.25.2", "@babel/plugin-transform-react-jsx-self": "^7.24.7", "@babel/plugin-transform-react-jsx-source": "^7.24.7", "@babel/plugin-transform-regenerator": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-shorthand-properties": "^7.24.7", "@babel/plugin-transform-spread": "^7.24.7", "@babel/plugin-transform-sticky-regex": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/template": "^7.25.0", "@react-native/babel-plugin-codegen": "0.81.5", "babel-plugin-syntax-hermes-parser": "0.29.1", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" } }, "sha512-UoI/x/5tCmi+pZ3c1+Ypr1DaRMDLI3y+Q70pVLLVgrnC3DHsHRIbHcCHIeG/IJvoeFqFM2sTdhSOLJrf8lOPrA=="], + "babel-preset-expo/babel-plugin-syntax-hermes-parser": ["babel-plugin-syntax-hermes-parser@0.29.1", "", { "dependencies": { "hermes-parser": "0.29.1" } }, "sha512-2WFYnoWGdmih1I1J5eIqxATOeycOqRwYxAQBu3cUu/rhwInwHUg7k60AFNbuGjSDL8tje5GDrAnxzRLcu2pYcA=="], + "better-opn/open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="], "bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, ""], @@ -3032,6 +3043,8 @@ "git-semver-tags/meow": ["meow@13.2.0", "", {}, ""], + "glob/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, ""], + "global-dirs/ini": ["ini@1.3.8", "", {}, ""], "globby/fast-glob": ["fast-glob@3.3.2", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, ""], @@ -3074,6 +3087,8 @@ "jest-config/ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="], + "jest-config/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, ""], + "jest-environment-node/@types/node": ["@types/node@22.0.0", "", { "dependencies": { "undici-types": "~6.11.1" } }, ""], "jest-haste-map/@types/node": ["@types/node@22.0.0", "", { "dependencies": { "undici-types": "~6.11.1" } }, ""], @@ -3090,6 +3105,8 @@ "jest-runtime/@types/node": ["@types/node@22.0.0", "", { "dependencies": { "undici-types": "~6.11.1" } }, ""], + "jest-runtime/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, ""], + "jest-snapshot/@babel/core": ["@babel/core@7.25.2", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.7", "@babel/generator": "^7.25.0", "@babel/helper-compilation-targets": "^7.25.2", "@babel/helper-module-transforms": "^7.25.2", "@babel/helpers": "^7.25.0", "@babel/parser": "^7.25.0", "@babel/template": "^7.25.0", "@babel/traverse": "^7.25.2", "@babel/types": "^7.25.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, ""], "jest-snapshot/@babel/generator": ["@babel/generator@7.27.5", "", { "dependencies": { "@babel/parser": "^7.27.5", "@babel/types": "^7.27.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw=="], @@ -3126,19 +3143,13 @@ "metro/hermes-parser": ["hermes-parser@0.32.0", "", { "dependencies": { "hermes-estree": "0.32.0" } }, "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw=="], - "metro/metro-babel-transformer": ["metro-babel-transformer@0.83.3", "", { "dependencies": { "@babel/core": "^7.25.2", "flow-enums-runtime": "^0.0.6", "hermes-parser": "0.32.0", "nullthrows": "^1.1.1" } }, "sha512-1vxlvj2yY24ES1O5RsSIvg4a4WeL7PFXgKOHvXTXiW0deLvQr28ExXj6LjwCCDZ4YZLhq6HddLpZnX4dEdSq5g=="], - - "metro/metro-cache": ["metro-cache@0.83.3", "", { "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", "https-proxy-agent": "^7.0.5", "metro-core": "0.83.3" } }, "sha512-3jo65X515mQJvKqK3vWRblxDEcgY55Sk3w4xa6LlfEXgQ9g1WgMh9m4qVZVwgcHoLy0a2HENTPCCX4Pk6s8c8Q=="], + "metro/metro-config": ["metro-config@0.83.2", "", { "dependencies": { "connect": "^3.6.5", "flow-enums-runtime": "^0.0.6", "jest-validate": "^29.7.0", "metro": "0.83.2", "metro-cache": "0.83.2", "metro-core": "0.83.2", "metro-runtime": "0.83.2", "yaml": "^2.6.1" } }, "sha512-1FjCcdBe3e3D08gSSiU9u3Vtxd7alGH3x/DNFqWDFf5NouX4kLgbVloDDClr1UrLz62c0fHh2Vfr9ecmrOZp+g=="], - "metro/metro-cache-key": ["metro-cache-key@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-59ZO049jKzSmvBmG/B5bZ6/dztP0ilp0o988nc6dpaDsU05Cl1c/lRf+yx8m9WW/JVgbmfO5MziBU559XjI5Zw=="], + "metro/metro-runtime": ["metro-runtime@0.83.2", "", { "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" } }, "sha512-nnsPtgRvFbNKwemqs0FuyFDzXLl+ezuFsUXDbX8o0SXOfsOPijqiQrf3kuafO1Zx1aUWf4NOrKJMAQP5EEHg9A=="], - "metro/metro-file-map": ["metro-file-map@0.83.3", "", { "dependencies": { "debug": "^4.4.0", "fb-watchman": "^2.0.0", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "nullthrows": "^1.1.1", "walker": "^1.0.7" } }, "sha512-jg5AcyE0Q9Xbbu/4NAwwZkmQn7doJCKGW0SLeSJmzNB9Z24jBe0AL2PHNMy4eu0JiKtNWHz9IiONGZWq7hjVTA=="], + "metro/metro-source-map": ["metro-source-map@0.83.2", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3", "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-symbolicate": "0.83.2", "nullthrows": "^1.1.1", "ob1": "0.83.2", "source-map": "^0.5.6", "vlq": "^1.0.0" } }, "sha512-5FL/6BSQvshIKjXOennt9upFngq2lFvDakZn5LfauIVq8+L4sxXewIlSTcxAtzbtjAIaXeOSVMtCJ5DdfCt9AA=="], - "metro/metro-resolver": ["metro-resolver@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-0js+zwI5flFxb1ktmR///bxHYg7OLpRpWZlBBruYG8OKYxeMP7SV0xQ/o/hUelrEMdK4LJzqVtHAhBm25LVfAQ=="], - - "metro/metro-transform-plugins": ["metro-transform-plugins@0.83.3", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/template": "^7.25.0", "@babel/traverse": "^7.25.3", "flow-enums-runtime": "^0.0.6", "nullthrows": "^1.1.1" } }, "sha512-eRGoKJU6jmqOakBMH5kUB7VitEWiNrDzBHpYbkBXW7C5fUGeOd2CyqrosEzbMK5VMiZYyOcNFEphvxk3OXey2A=="], - - "metro/metro-transform-worker": ["metro-transform-worker@0.83.3", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/parser": "^7.25.3", "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", "metro": "0.83.3", "metro-babel-transformer": "0.83.3", "metro-cache": "0.83.3", "metro-cache-key": "0.83.3", "metro-minify-terser": "0.83.3", "metro-source-map": "0.83.3", "metro-transform-plugins": "0.83.3", "nullthrows": "^1.1.1" } }, "sha512-Ztekew9t/gOIMZX1tvJOgX7KlSLL5kWykl0Iwu2cL2vKMKVALRl1hysyhUw0vjpAvLFx+Kfq9VLjnHIkW32fPA=="], + "metro/metro-symbolicate": ["metro-symbolicate@0.83.2", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-source-map": "0.83.2", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "vlq": "^1.0.0" }, "bin": { "metro-symbolicate": "src/index.js" } }, "sha512-KoU9BLwxxED6n33KYuQQuc5bXkIxF3fSwlc3ouxrrdLWwhu64muYZNQrukkWzhVKRNFIXW7X2iM8JXpi2heIPw=="], "metro/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, ""], @@ -3146,13 +3157,11 @@ "metro-babel-transformer/hermes-parser": ["hermes-parser@0.32.0", "", { "dependencies": { "hermes-estree": "0.32.0" } }, "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw=="], - "metro-cache/metro-core": ["metro-core@0.83.2", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", "metro-resolver": "0.83.2" } }, "sha512-8DRb0O82Br0IW77cNgKMLYWUkx48lWxUkvNUxVISyMkcNwE/9ywf1MYQUE88HaKwSrqne6kFgCSA/UWZoUT0Iw=="], + "metro-config/metro": ["metro@0.83.3", "", { "dependencies": { "@babel/code-frame": "^7.24.7", "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/parser": "^7.25.3", "@babel/template": "^7.25.0", "@babel/traverse": "^7.25.3", "@babel/types": "^7.25.2", "accepts": "^1.3.7", "chalk": "^4.0.0", "ci-info": "^2.0.0", "connect": "^3.6.5", "debug": "^4.4.0", "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "hermes-parser": "0.32.0", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", "metro-babel-transformer": "0.83.3", "metro-cache": "0.83.3", "metro-cache-key": "0.83.3", "metro-config": "0.83.3", "metro-core": "0.83.3", "metro-file-map": "0.83.3", "metro-resolver": "0.83.3", "metro-runtime": "0.83.3", "metro-source-map": "0.83.3", "metro-symbolicate": "0.83.3", "metro-transform-plugins": "0.83.3", "metro-transform-worker": "0.83.3", "mime-types": "^2.1.27", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", "source-map": "^0.5.6", "throat": "^5.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { "metro": "src/cli.js" } }, "sha512-+rP+/GieOzkt97hSJ0MrPOuAH/jpaS21ZDvL9DJ35QYRDlQcwzcvUlGUf79AnQxq/2NPiS/AULhhM4TKutIt8Q=="], "metro-config/metro-cache": ["metro-cache@0.83.3", "", { "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", "https-proxy-agent": "^7.0.5", "metro-core": "0.83.3" } }, "sha512-3jo65X515mQJvKqK3vWRblxDEcgY55Sk3w4xa6LlfEXgQ9g1WgMh9m4qVZVwgcHoLy0a2HENTPCCX4Pk6s8c8Q=="], - "metro-core/metro-resolver": ["metro-resolver@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-0js+zwI5flFxb1ktmR///bxHYg7OLpRpWZlBBruYG8OKYxeMP7SV0xQ/o/hUelrEMdK4LJzqVtHAhBm25LVfAQ=="], - - "metro-transform-worker/metro": ["metro@0.83.2", "", { "dependencies": { "@babel/code-frame": "^7.24.7", "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/parser": "^7.25.3", "@babel/template": "^7.25.0", "@babel/traverse": "^7.25.3", "@babel/types": "^7.25.2", "accepts": "^1.3.7", "chalk": "^4.0.0", "ci-info": "^2.0.0", "connect": "^3.6.5", "debug": "^4.4.0", "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "hermes-parser": "0.32.0", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", "metro-babel-transformer": "0.83.2", "metro-cache": "0.83.2", "metro-cache-key": "0.83.2", "metro-config": "0.83.2", "metro-core": "0.83.2", "metro-file-map": "0.83.2", "metro-resolver": "0.83.2", "metro-runtime": "0.83.2", "metro-source-map": "0.83.2", "metro-symbolicate": "0.83.2", "metro-transform-plugins": "0.83.2", "metro-transform-worker": "0.83.2", "mime-types": "^2.1.27", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", "source-map": "^0.5.6", "throat": "^5.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { "metro": "src/cli.js" } }, "sha512-HQgs9H1FyVbRptNSMy/ImchTTE5vS2MSqLoOo7hbDoBq6hPPZokwJvBMwrYSxdjQZmLXz2JFZtdvS+ZfgTc9yw=="], + "metro-config/metro-core": ["metro-core@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", "metro-resolver": "0.83.3" } }, "sha512-M+X59lm7oBmJZamc96usuF1kusd5YimqG/q97g4Ac7slnJ3YiGglW5CsOlicTR5EWf8MQFxxjDoB6ytTqRe8Hw=="], "metro-transform-worker/metro-source-map": ["metro-source-map@0.83.2", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3", "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-symbolicate": "0.83.2", "nullthrows": "^1.1.1", "ob1": "0.83.2", "source-map": "^0.5.6", "vlq": "^1.0.0" } }, "sha512-5FL/6BSQvshIKjXOennt9upFngq2lFvDakZn5LfauIVq8+L4sxXewIlSTcxAtzbtjAIaXeOSVMtCJ5DdfCt9AA=="], @@ -3200,16 +3209,16 @@ "react-devtools-core/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, ""], + "react-native/babel-plugin-syntax-hermes-parser": ["babel-plugin-syntax-hermes-parser@0.29.1", "", { "dependencies": { "hermes-parser": "0.29.1" } }, "sha512-2WFYnoWGdmih1I1J5eIqxATOeycOqRwYxAQBu3cUu/rhwInwHUg7k60AFNbuGjSDL8tje5GDrAnxzRLcu2pYcA=="], + "react-native/commander": ["commander@12.1.0", "", {}, ""], - "react-native/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], + "react-native/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, ""], - "react-native-builder-bob/babel-plugin-syntax-hermes-parser": ["babel-plugin-syntax-hermes-parser@0.28.1", "", { "dependencies": { "hermes-parser": "0.28.1" } }, "sha512-meT17DOuUElMNsL5LZN56d+KBp22hb0EfxWfuPUeoSi54e40v1W4C2V36P75FpsH9fVEfDKpw5Nnkahc8haSsQ=="], + "react-native/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], "react-native-builder-bob/del": ["del@6.1.1", "", { "dependencies": { "globby": "^11.0.1", "graceful-fs": "^4.2.4", "is-glob": "^4.0.1", "is-path-cwd": "^2.2.0", "is-path-inside": "^3.0.2", "p-map": "^4.0.0", "rimraf": "^3.0.2", "slash": "^3.0.0" } }, ""], - "react-native-builder-bob/glob": ["glob@8.1.0", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^5.0.1", "once": "^1.3.0" } }, ""], - "react-native-monorepo-config/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], "react-native-quick-crypto/@types/node": ["@types/node@24.3.0", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow=="], @@ -3240,6 +3249,8 @@ "restore-cursor/signal-exit": ["signal-exit@4.1.0", "", {}, ""], + "rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, ""], + "safe-array-concat/isarray": ["isarray@2.0.5", "", {}, ""], "safe-push-apply/isarray": ["isarray@2.0.5", "", {}, ""], @@ -3284,6 +3295,8 @@ "terser/commander": ["commander@2.20.3", "", {}, ""], + "test-exclude/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, ""], + "tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], "tsutils/tslib": ["tslib@1.14.1", "", {}, ""], @@ -3534,14 +3547,6 @@ "@eslint/eslintrc/debug/ms": ["ms@2.1.2", "", {}, ""], - "@expo/cli/@react-native/dev-middleware/@react-native/debugger-frontend": ["@react-native/debugger-frontend@0.81.5", "", {}, "sha512-bnd9FSdWKx2ncklOetCgrlwqSGhMHP2zOxObJbOWXoj7GHEmih4MKarBo5/a8gX8EfA1EwRATdfNBQ81DY+h+w=="], - - "@expo/cli/@react-native/dev-middleware/open": ["open@7.4.2", "", { "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" } }, ""], - - "@expo/cli/@react-native/dev-middleware/serve-static": ["serve-static@1.16.2", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "0.19.0" } }, "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw=="], - - "@expo/cli/@react-native/dev-middleware/ws": ["ws@6.2.3", "", { "dependencies": { "async-limiter": "~1.0.0" } }, ""], - "@expo/cli/glob/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], "@expo/cli/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, ""], @@ -3588,16 +3593,6 @@ "@expo/metro-config/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, ""], - "@expo/metro/metro/ci-info": ["ci-info@2.0.0", "", {}, ""], - - "@expo/metro/metro/hermes-parser": ["hermes-parser@0.32.0", "", { "dependencies": { "hermes-estree": "0.32.0" } }, "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw=="], - - "@expo/metro/metro/metro-symbolicate": ["metro-symbolicate@0.83.2", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-source-map": "0.83.2", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "vlq": "^1.0.0" }, "bin": { "metro-symbolicate": "src/index.js" } }, "sha512-KoU9BLwxxED6n33KYuQQuc5bXkIxF3fSwlc3ouxrrdLWwhu64muYZNQrukkWzhVKRNFIXW7X2iM8JXpi2heIPw=="], - - "@expo/metro/metro/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, ""], - - "@expo/metro/metro/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, ""], - "@expo/metro/metro-source-map/metro-symbolicate": ["metro-symbolicate@0.83.2", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-source-map": "0.83.2", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "vlq": "^1.0.0" }, "bin": { "metro-symbolicate": "src/index.js" } }, "sha512-KoU9BLwxxED6n33KYuQQuc5bXkIxF3fSwlc3ouxrrdLWwhu64muYZNQrukkWzhVKRNFIXW7X2iM8JXpi2heIPw=="], "@expo/metro/metro-source-map/ob1": ["ob1@0.83.2", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-XlK3w4M+dwd1g1gvHzVbxiXEbUllRONEgcF2uEO0zm4nxa0eKlh41c6N65q1xbiDOeKKda1tvNOAD33fNjyvCg=="], @@ -3704,6 +3699,36 @@ "@react-native/babel-preset/@babel/plugin-transform-typescript/@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.25.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.24.8" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, ""], + "@react-native/community-cli-plugin/@react-native/dev-middleware/@react-native/debugger-frontend": ["@react-native/debugger-frontend@0.81.1", "", {}, "sha512-dwKv1EqKD+vONN4xsfyTXxn291CNl1LeBpaHhNGWASK1GO4qlyExMs4TtTjN57BnYHikR9PzqPWcUcfzpVRaLg=="], + + "@react-native/community-cli-plugin/@react-native/dev-middleware/open": ["open@7.4.2", "", { "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" } }, ""], + + "@react-native/community-cli-plugin/@react-native/dev-middleware/serve-static": ["serve-static@1.16.2", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "0.19.0" } }, "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw=="], + + "@react-native/community-cli-plugin/metro/ci-info": ["ci-info@2.0.0", "", {}, ""], + + "@react-native/community-cli-plugin/metro/hermes-parser": ["hermes-parser@0.32.0", "", { "dependencies": { "hermes-estree": "0.32.0" } }, "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw=="], + + "@react-native/community-cli-plugin/metro/metro-babel-transformer": ["metro-babel-transformer@0.83.3", "", { "dependencies": { "@babel/core": "^7.25.2", "flow-enums-runtime": "^0.0.6", "hermes-parser": "0.32.0", "nullthrows": "^1.1.1" } }, "sha512-1vxlvj2yY24ES1O5RsSIvg4a4WeL7PFXgKOHvXTXiW0deLvQr28ExXj6LjwCCDZ4YZLhq6HddLpZnX4dEdSq5g=="], + + "@react-native/community-cli-plugin/metro/metro-cache": ["metro-cache@0.83.3", "", { "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", "https-proxy-agent": "^7.0.5", "metro-core": "0.83.3" } }, "sha512-3jo65X515mQJvKqK3vWRblxDEcgY55Sk3w4xa6LlfEXgQ9g1WgMh9m4qVZVwgcHoLy0a2HENTPCCX4Pk6s8c8Q=="], + + "@react-native/community-cli-plugin/metro/metro-cache-key": ["metro-cache-key@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-59ZO049jKzSmvBmG/B5bZ6/dztP0ilp0o988nc6dpaDsU05Cl1c/lRf+yx8m9WW/JVgbmfO5MziBU559XjI5Zw=="], + + "@react-native/community-cli-plugin/metro/metro-file-map": ["metro-file-map@0.83.3", "", { "dependencies": { "debug": "^4.4.0", "fb-watchman": "^2.0.0", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "nullthrows": "^1.1.1", "walker": "^1.0.7" } }, "sha512-jg5AcyE0Q9Xbbu/4NAwwZkmQn7doJCKGW0SLeSJmzNB9Z24jBe0AL2PHNMy4eu0JiKtNWHz9IiONGZWq7hjVTA=="], + + "@react-native/community-cli-plugin/metro/metro-resolver": ["metro-resolver@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-0js+zwI5flFxb1ktmR///bxHYg7OLpRpWZlBBruYG8OKYxeMP7SV0xQ/o/hUelrEMdK4LJzqVtHAhBm25LVfAQ=="], + + "@react-native/community-cli-plugin/metro/metro-transform-plugins": ["metro-transform-plugins@0.83.3", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/template": "^7.25.0", "@babel/traverse": "^7.25.3", "flow-enums-runtime": "^0.0.6", "nullthrows": "^1.1.1" } }, "sha512-eRGoKJU6jmqOakBMH5kUB7VitEWiNrDzBHpYbkBXW7C5fUGeOd2CyqrosEzbMK5VMiZYyOcNFEphvxk3OXey2A=="], + + "@react-native/community-cli-plugin/metro/metro-transform-worker": ["metro-transform-worker@0.83.3", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/parser": "^7.25.3", "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", "metro": "0.83.3", "metro-babel-transformer": "0.83.3", "metro-cache": "0.83.3", "metro-cache-key": "0.83.3", "metro-minify-terser": "0.83.3", "metro-source-map": "0.83.3", "metro-transform-plugins": "0.83.3", "nullthrows": "^1.1.1" } }, "sha512-Ztekew9t/gOIMZX1tvJOgX7KlSLL5kWykl0Iwu2cL2vKMKVALRl1hysyhUw0vjpAvLFx+Kfq9VLjnHIkW32fPA=="], + + "@react-native/community-cli-plugin/metro/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, ""], + + "@react-native/community-cli-plugin/metro/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, ""], + + "@react-native/community-cli-plugin/metro-core/metro-resolver": ["metro-resolver@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-0js+zwI5flFxb1ktmR///bxHYg7OLpRpWZlBBruYG8OKYxeMP7SV0xQ/o/hUelrEMdK4LJzqVtHAhBm25LVfAQ=="], + "@react-native/dev-middleware/open/is-docker": ["is-docker@2.2.1", "", { "bin": "cli.js" }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="], "@react-native/dev-middleware/open/is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="], @@ -3756,6 +3781,8 @@ "babel-plugin-module-resolver/glob/minimatch": ["minimatch@8.0.4", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, ""], + "babel-plugin-syntax-hermes-parser/hermes-parser/hermes-estree": ["hermes-estree@0.28.1", "", {}, "sha512-w3nxl/RGM7LBae0v8LH2o36+8VqwOZGv9rX1wyoWT6YaKZLqpJZ0YQ5P0LVr3tuRpf7vCx0iIG4i/VmBJejxTQ=="], + "babel-preset-expo/@babel/plugin-transform-class-static-block/@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ=="], "babel-preset-expo/@react-native/babel-preset/@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.81.5", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@react-native/codegen": "0.81.5" } }, "sha512-oF71cIH6je3fSLi6VPjjC3Sgyyn57JLHXs+mHWc9MoCiJJcM4nqsS5J38zv1XQ8d3zOW2JtHro+LF0tagj2bfQ=="], @@ -3810,6 +3837,8 @@ "get-uri/debug/ms": ["ms@2.1.2", "", {}, ""], + "glob/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, ""], + "globby/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, ""], "http-proxy-agent/debug/ms": ["ms@2.1.2", "", {}, ""], @@ -3924,21 +3953,27 @@ "metro-babel-transformer/hermes-parser/hermes-estree": ["hermes-estree@0.32.0", "", {}, "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ=="], - "metro-transform-worker/metro/ci-info": ["ci-info@2.0.0", "", {}, ""], + "metro-config/metro/ci-info": ["ci-info@2.0.0", "", {}, ""], + + "metro-config/metro/hermes-parser": ["hermes-parser@0.32.0", "", { "dependencies": { "hermes-estree": "0.32.0" } }, "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw=="], + + "metro-config/metro/metro-babel-transformer": ["metro-babel-transformer@0.83.3", "", { "dependencies": { "@babel/core": "^7.25.2", "flow-enums-runtime": "^0.0.6", "hermes-parser": "0.32.0", "nullthrows": "^1.1.1" } }, "sha512-1vxlvj2yY24ES1O5RsSIvg4a4WeL7PFXgKOHvXTXiW0deLvQr28ExXj6LjwCCDZ4YZLhq6HddLpZnX4dEdSq5g=="], - "metro-transform-worker/metro/hermes-parser": ["hermes-parser@0.32.0", "", { "dependencies": { "hermes-estree": "0.32.0" } }, "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw=="], + "metro-config/metro/metro-cache-key": ["metro-cache-key@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-59ZO049jKzSmvBmG/B5bZ6/dztP0ilp0o988nc6dpaDsU05Cl1c/lRf+yx8m9WW/JVgbmfO5MziBU559XjI5Zw=="], - "metro-transform-worker/metro/metro-config": ["metro-config@0.83.2", "", { "dependencies": { "connect": "^3.6.5", "flow-enums-runtime": "^0.0.6", "jest-validate": "^29.7.0", "metro": "0.83.2", "metro-cache": "0.83.2", "metro-core": "0.83.2", "metro-runtime": "0.83.2", "yaml": "^2.6.1" } }, "sha512-1FjCcdBe3e3D08gSSiU9u3Vtxd7alGH3x/DNFqWDFf5NouX4kLgbVloDDClr1UrLz62c0fHh2Vfr9ecmrOZp+g=="], + "metro-config/metro/metro-file-map": ["metro-file-map@0.83.3", "", { "dependencies": { "debug": "^4.4.0", "fb-watchman": "^2.0.0", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "nullthrows": "^1.1.1", "walker": "^1.0.7" } }, "sha512-jg5AcyE0Q9Xbbu/4NAwwZkmQn7doJCKGW0SLeSJmzNB9Z24jBe0AL2PHNMy4eu0JiKtNWHz9IiONGZWq7hjVTA=="], - "metro-transform-worker/metro/metro-core": ["metro-core@0.83.2", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", "metro-resolver": "0.83.2" } }, "sha512-8DRb0O82Br0IW77cNgKMLYWUkx48lWxUkvNUxVISyMkcNwE/9ywf1MYQUE88HaKwSrqne6kFgCSA/UWZoUT0Iw=="], + "metro-config/metro/metro-resolver": ["metro-resolver@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-0js+zwI5flFxb1ktmR///bxHYg7OLpRpWZlBBruYG8OKYxeMP7SV0xQ/o/hUelrEMdK4LJzqVtHAhBm25LVfAQ=="], - "metro-transform-worker/metro/metro-runtime": ["metro-runtime@0.83.2", "", { "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" } }, "sha512-nnsPtgRvFbNKwemqs0FuyFDzXLl+ezuFsUXDbX8o0SXOfsOPijqiQrf3kuafO1Zx1aUWf4NOrKJMAQP5EEHg9A=="], + "metro-config/metro/metro-transform-plugins": ["metro-transform-plugins@0.83.3", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/template": "^7.25.0", "@babel/traverse": "^7.25.3", "flow-enums-runtime": "^0.0.6", "nullthrows": "^1.1.1" } }, "sha512-eRGoKJU6jmqOakBMH5kUB7VitEWiNrDzBHpYbkBXW7C5fUGeOd2CyqrosEzbMK5VMiZYyOcNFEphvxk3OXey2A=="], - "metro-transform-worker/metro/metro-symbolicate": ["metro-symbolicate@0.83.2", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-source-map": "0.83.2", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "vlq": "^1.0.0" }, "bin": { "metro-symbolicate": "src/index.js" } }, "sha512-KoU9BLwxxED6n33KYuQQuc5bXkIxF3fSwlc3ouxrrdLWwhu64muYZNQrukkWzhVKRNFIXW7X2iM8JXpi2heIPw=="], + "metro-config/metro/metro-transform-worker": ["metro-transform-worker@0.83.3", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/parser": "^7.25.3", "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", "metro": "0.83.3", "metro-babel-transformer": "0.83.3", "metro-cache": "0.83.3", "metro-cache-key": "0.83.3", "metro-minify-terser": "0.83.3", "metro-source-map": "0.83.3", "metro-transform-plugins": "0.83.3", "nullthrows": "^1.1.1" } }, "sha512-Ztekew9t/gOIMZX1tvJOgX7KlSLL5kWykl0Iwu2cL2vKMKVALRl1hysyhUw0vjpAvLFx+Kfq9VLjnHIkW32fPA=="], - "metro-transform-worker/metro/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, ""], + "metro-config/metro/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, ""], - "metro-transform-worker/metro/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, ""], + "metro-config/metro/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, ""], + + "metro-config/metro-core/metro-resolver": ["metro-resolver@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-0js+zwI5flFxb1ktmR///bxHYg7OLpRpWZlBBruYG8OKYxeMP7SV0xQ/o/hUelrEMdK4LJzqVtHAhBm25LVfAQ=="], "metro-transform-worker/metro-source-map/metro-symbolicate": ["metro-symbolicate@0.83.2", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-source-map": "0.83.2", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "vlq": "^1.0.0" }, "bin": { "metro-symbolicate": "src/index.js" } }, "sha512-KoU9BLwxxED6n33KYuQQuc5bXkIxF3fSwlc3ouxrrdLWwhu64muYZNQrukkWzhVKRNFIXW7X2iM8JXpi2heIPw=="], @@ -3946,7 +3981,7 @@ "metro/hermes-parser/hermes-estree": ["hermes-estree@0.32.0", "", {}, "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ=="], - "metro/metro-transform-worker/metro-minify-terser": ["metro-minify-terser@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "terser": "^5.15.0" } }, "sha512-O2BmfWj6FSfzBLrNCXt/rr2VYZdX5i6444QJU0fFoc7Ljg+Q+iqebwE3K0eTvkI6TRjELsXk1cjU+fXwAR4OjQ=="], + "metro/metro-source-map/ob1": ["ob1@0.83.2", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-XlK3w4M+dwd1g1gvHzVbxiXEbUllRONEgcF2uEO0zm4nxa0eKlh41c6N65q1xbiDOeKKda1tvNOAD33fNjyvCg=="], "metro/mime-types/mime-db": ["mime-db@1.52.0", "", {}, ""], @@ -3958,8 +3993,6 @@ "proxy-agent/debug/ms": ["ms@2.1.2", "", {}, ""], - "react-native-builder-bob/babel-plugin-syntax-hermes-parser/hermes-parser": ["hermes-parser@0.28.1", "", { "dependencies": { "hermes-estree": "0.28.1" } }, "sha512-nf8o+hE8g7UJWParnccljHumE9Vlq8F7MqIdeahl+4x0tvCUJYRrT0L7h0MMg/X9YJmkNwsfbaNNrzPtFXOscg=="], - "react-native-builder-bob/del/globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, ""], "react-native-builder-bob/del/is-path-cwd": ["is-path-cwd@2.2.0", "", {}, ""], @@ -3968,8 +4001,6 @@ "react-native-builder-bob/del/p-map": ["p-map@4.0.0", "", { "dependencies": { "aggregate-error": "^3.0.0" } }, "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ=="], - "react-native-builder-bob/glob/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, ""], - "react-native-quick-crypto-example/@types/react/csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], "react-native-quick-crypto/@types/node/undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="], @@ -4216,12 +4247,6 @@ "@babel/preset-env/@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, ""], - "@expo/cli/@react-native/dev-middleware/open/is-docker": ["is-docker@2.2.1", "", { "bin": "cli.js" }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="], - - "@expo/cli/@react-native/dev-middleware/open/is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="], - - "@expo/cli/@react-native/dev-middleware/serve-static/send": ["send@0.19.0", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "2.4.1", "range-parser": "~1.2.1", "statuses": "2.0.1" } }, "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw=="], - "@expo/cli/ora/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, ""], "@expo/cli/ora/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, ""], @@ -4240,10 +4265,6 @@ "@expo/metro-config/browserslist/update-browserslist-db/escalade": ["escalade@3.2.0", "", {}, ""], - "@expo/metro/metro/hermes-parser/hermes-estree": ["hermes-estree@0.32.0", "", {}, "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ=="], - - "@expo/metro/metro/mime-types/mime-db": ["mime-db@1.52.0", "", {}, ""], - "@expo/package-manager/ora/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, ""], "@expo/package-manager/ora/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, ""], @@ -4300,6 +4321,18 @@ "@react-native-community/cli-tools/ora/cli-cursor/restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, ""], + "@react-native/community-cli-plugin/@react-native/dev-middleware/open/is-docker": ["is-docker@2.2.1", "", { "bin": "cli.js" }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="], + + "@react-native/community-cli-plugin/@react-native/dev-middleware/open/is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="], + + "@react-native/community-cli-plugin/@react-native/dev-middleware/serve-static/send": ["send@0.19.0", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "2.4.1", "range-parser": "~1.2.1", "statuses": "2.0.1" } }, "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw=="], + + "@react-native/community-cli-plugin/metro/hermes-parser/hermes-estree": ["hermes-estree@0.32.0", "", {}, "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ=="], + + "@react-native/community-cli-plugin/metro/metro-transform-worker/metro-minify-terser": ["metro-minify-terser@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "terser": "^5.15.0" } }, "sha512-O2BmfWj6FSfzBLrNCXt/rr2VYZdX5i6444QJU0fFoc7Ljg+Q+iqebwE3K0eTvkI6TRjELsXk1cjU+fXwAR4OjQ=="], + + "@react-native/community-cli-plugin/metro/mime-types/mime-db": ["mime-db@1.52.0", "", {}, ""], + "@react-native/dev-middleware/serve-static/send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, ""], "@react-native/dev-middleware/serve-static/send/encodeurl": ["encodeurl@1.0.2", "", {}, ""], @@ -4418,9 +4451,11 @@ "logkitty/yargs/yargs-parser/camelcase": ["camelcase@5.3.1", "", {}, ""], - "metro-transform-worker/metro/hermes-parser/hermes-estree": ["hermes-estree@0.32.0", "", {}, "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ=="], + "metro-config/metro/hermes-parser/hermes-estree": ["hermes-estree@0.32.0", "", {}, "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ=="], + + "metro-config/metro/metro-transform-worker/metro-minify-terser": ["metro-minify-terser@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "terser": "^5.15.0" } }, "sha512-O2BmfWj6FSfzBLrNCXt/rr2VYZdX5i6444QJU0fFoc7Ljg+Q+iqebwE3K0eTvkI6TRjELsXk1cjU+fXwAR4OjQ=="], - "metro-transform-worker/metro/mime-types/mime-db": ["mime-db@1.52.0", "", {}, ""], + "metro-config/metro/mime-types/mime-db": ["mime-db@1.52.0", "", {}, ""], "pkg-dir/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, ""], @@ -4428,14 +4463,10 @@ "pkg-up/find-up/locate-path/path-exists": ["path-exists@3.0.0", "", {}, ""], - "react-native-builder-bob/babel-plugin-syntax-hermes-parser/hermes-parser/hermes-estree": ["hermes-estree@0.28.1", "", {}, "sha512-w3nxl/RGM7LBae0v8LH2o36+8VqwOZGv9rX1wyoWT6YaKZLqpJZ0YQ5P0LVr3tuRpf7vCx0iIG4i/VmBJejxTQ=="], - "react-native-builder-bob/del/globby/fast-glob": ["fast-glob@3.3.2", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, ""], "react-native-builder-bob/del/globby/ignore": ["ignore@5.3.2", "", {}, ""], - "react-native-builder-bob/glob/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, ""], - "react-native-vector-icons/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "react-native-vector-icons/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], @@ -4488,12 +4519,6 @@ "@babel/plugin-transform-runtime/@babel/helper-module-imports/@babel/traverse/debug/ms": ["ms@2.1.2", "", {}, ""], - "@expo/cli/@react-native/dev-middleware/serve-static/send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, ""], - - "@expo/cli/@react-native/dev-middleware/serve-static/send/encodeurl": ["encodeurl@1.0.2", "", {}, ""], - - "@expo/cli/@react-native/dev-middleware/serve-static/send/mime": ["mime@1.6.0", "", { "bin": "cli.js" }, ""], - "@expo/cli/ora/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, ""], "@expo/cli/ora/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, ""], @@ -4536,6 +4561,12 @@ "@jest/transform/@babel/core/@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, ""], + "@react-native/community-cli-plugin/@react-native/dev-middleware/serve-static/send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, ""], + + "@react-native/community-cli-plugin/@react-native/dev-middleware/serve-static/send/encodeurl": ["encodeurl@1.0.2", "", {}, ""], + + "@react-native/community-cli-plugin/@react-native/dev-middleware/serve-static/send/mime": ["mime@1.6.0", "", { "bin": "cli.js" }, ""], + "@react-native/dev-middleware/serve-static/send/debug/ms": ["ms@2.0.0", "", {}, ""], "@react-native/eslint-config/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@7.18.0", "", {}, ""], @@ -4564,6 +4595,8 @@ "ansi-fragments/slice-ansi/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, ""], + "babel-preset-expo/@react-native/babel-preset/@react-native/babel-plugin-codegen/@react-native/codegen/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, ""], + "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, ""], "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, ""], @@ -4612,8 +4645,6 @@ "react-native-builder-bob/del/globby/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, ""], - "@expo/cli/@react-native/dev-middleware/serve-static/send/debug/ms": ["ms@2.0.0", "", {}, ""], - "@expo/cli/ora/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, ""], "@expo/cli/ora/cli-cursor/restore-cursor/onetime/mimic-fn": ["mimic-fn@1.2.0", "", {}, "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="], @@ -4632,6 +4663,8 @@ "@jest/reporters/istanbul-lib-instrument/@babel/core/@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, ""], + "@react-native/community-cli-plugin/@react-native/dev-middleware/serve-static/send/debug/ms": ["ms@2.0.0", "", {}, ""], + "@react-native/eslint-config/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/globby/fast-glob": ["fast-glob@3.3.2", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, ""], "@react-native/eslint-config/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, ""], diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 778c8247..95c65c45 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -2773,7 +2773,7 @@ SPEC CHECKSUMS: glog: 5683914934d5b6e4240e497e0f4a3b42d1854183 hermes-engine: 4f8246b1f6d79f625e0d99472d1f3a71da4d28ca NitroModules: 1715fe0e22defd9e2cdd48fb5e0dbfd01af54bec - QuickCrypto: a90c6474ef5d724d14cc452d8d23146621e0fb99 + QuickCrypto: d2f42ab176e55442e803586b4621d5c21e0f0f19 RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669 RCTDeprecation: c4b9e2fd0ab200e3af72b013ed6113187c607077 RCTRequired: e97dd5dafc1db8094e63bc5031e0371f092ae92a diff --git a/example/ios/QuickCryptoExample.xcodeproj/project.pbxproj b/example/ios/QuickCryptoExample.xcodeproj/project.pbxproj index 4e1fda34..67101771 100644 --- a/example/ios/QuickCryptoExample.xcodeproj/project.pbxproj +++ b/example/ios/QuickCryptoExample.xcodeproj/project.pbxproj @@ -398,7 +398,7 @@ "-DFOLLY_HAVE_CLOCK_GETTIME=1", ); OTHER_LDFLAGS = "$(inherited)"; - REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; SWIFT_ENABLE_EXPLICIT_MODULES = NO; diff --git a/example/package.json b/example/package.json index 0d422a54..13fed5fd 100644 --- a/example/package.json +++ b/example/package.json @@ -40,7 +40,7 @@ "react-native-fast-encoder": "0.3.1", "react-native-nitro-modules": "0.29.1", "react-native-quick-base64": "2.2.2", - "react-native-quick-crypto": "1.0.7", + "react-native-quick-crypto": "workspace:*", "react-native-safe-area-context": "5.6.2", "react-native-screens": "4.18.0", "react-native-vector-icons": "10.3.0", diff --git a/example/src/benchmarks/ecdh/ecdh.ts b/example/src/benchmarks/ecdh/ecdh.ts index ab2ab2af..e7913336 100644 --- a/example/src/benchmarks/ecdh/ecdh.ts +++ b/example/src/benchmarks/ecdh/ecdh.ts @@ -17,7 +17,9 @@ const ecdh_p256_genKeys: BenchFn = () => { ecdh.generateKeys(); }) .add('@noble/curves', () => { - p256.utils.randomPrivateKey(); + // Generate private key and derive public key for fair comparison + const priv = p256.utils.randomPrivateKey(); + p256.getPublicKey(priv); }); bench.warmupTime = 100; diff --git a/example/src/tests/dh/dh_tests.ts b/example/src/tests/dh/dh_tests.ts index d283a1ed..16648b74 100644 --- a/example/src/tests/dh/dh_tests.ts +++ b/example/src/tests/dh/dh_tests.ts @@ -5,64 +5,74 @@ import { assert } from 'chai'; const SUITE = 'dh'; -test(SUITE, 'should create DiffieHellman with size', () => { - const dh = crypto.createDiffieHellman(512); - const prime = dh.getPrime(); - assert.isOk(prime); - // Size check approx - assert.isAtLeast(prime.length, 64); -}); +// RFC 3526 MODP Group 14 prime (2048-bit) for testing with explicit prime +const MODP14_PRIME = + 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1' + + '29024E088A67CC74020BBEA63B139B22514A08798E3404DD' + + 'EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245' + + 'E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' + + 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D' + + 'C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F' + + '83655D23DCA3AD961C62F356208552BB9ED529077096966D' + + '670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' + + 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9' + + 'DE2BCBF6955817183995497CEA956AE515D2261898FA0510' + + '15728E5A8AACAA68FFFFFFFFFFFFFFFF'; -test(SUITE, 'should create DiffieHellman with prime', () => { - // 512-bit prime (Group 1 from RFC 2409) - const prime = Buffer.from( - 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1' + - '29024E088A67CC74020BBEA63B139B22514A08798E3404DD' + - 'EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245' + - 'E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' + - 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381' + - 'FFFFFFFFFFFFFFFF', - 'hex', - ); - const generator = Buffer.from([2]); - const dh = crypto.createDiffieHellman(prime, generator); +test( + SUITE, + 'should create DiffieHellman with prime and numeric generator', + () => { + const prime = Buffer.from(MODP14_PRIME, 'hex'); + const dh = crypto.createDiffieHellman(prime, 2); - assert.strictEqual(dh.getPrime('hex'), prime.toString('hex').toLowerCase()); - assert.strictEqual( - dh.getGenerator('hex'), - generator.toString('hex').toLowerCase(), - ); -}); + assert.strictEqual(dh.getPrime('hex'), prime.toString('hex').toLowerCase()); + assert.strictEqual(dh.getGenerator('hex'), '02'); + }, +); + +test( + SUITE, + 'should create DiffieHellman with prime and Buffer generator', + () => { + const prime = Buffer.from(MODP14_PRIME, 'hex'); + const generator = Buffer.from([2]); + const dh = crypto.createDiffieHellman(prime, generator); + + assert.strictEqual(dh.getPrime('hex'), prime.toString('hex').toLowerCase()); + assert.strictEqual( + dh.getGenerator('hex'), + generator.toString('hex').toLowerCase(), + ); + }, +); test(SUITE, 'should compute shared secret', () => { - const alice = crypto.createDiffieHellman(512); - const aliceKeys = alice.generateKeys(); + const alice = crypto.getDiffieHellman('modp14'); + alice.generateKeys(); - const bob = crypto.createDiffieHellman( - alice.getPrime(), - alice.getGenerator(), - ); - const bobKeys = bob.generateKeys(); + const bob = crypto.getDiffieHellman('modp14'); + bob.generateKeys(); - const aliceSecret = alice.computeSecret(bobKeys); - const bobSecret = bob.computeSecret(aliceKeys); + const aliceSecret = alice.computeSecret(bob.getPublicKey()); + const bobSecret = bob.computeSecret(alice.getPublicKey()); assert.strictEqual(aliceSecret.toString('hex'), bobSecret.toString('hex')); }); test(SUITE, 'should set keys', () => { - const alice = crypto.createDiffieHellman(512); + const alice = crypto.getDiffieHellman('modp14'); alice.generateKeys(); - const dh2 = crypto.createDiffieHellman( + const bob = crypto.createDiffieHellman( alice.getPrime(), alice.getGenerator(), ); - dh2.setPublicKey(alice.getPublicKey()); - dh2.setPrivateKey(alice.getPrivateKey()); + bob.setPublicKey(alice.getPublicKey()); + bob.setPrivateKey(alice.getPrivateKey()); - assert.strictEqual(dh2.getPublicKey('hex'), alice.getPublicKey('hex')); - assert.strictEqual(dh2.getPrivateKey('hex'), alice.getPrivateKey('hex')); + assert.strictEqual(bob.getPublicKey('hex'), alice.getPublicKey('hex')); + assert.strictEqual(bob.getPrivateKey('hex'), alice.getPrivateKey('hex')); }); test(SUITE, 'should create DiffieHellman from standard group', () => { @@ -70,7 +80,12 @@ test(SUITE, 'should create DiffieHellman from standard group', () => { assert.isOk(dh); const prime = dh.getPrime(); assert.isTrue(Buffer.isBuffer(prime)); - // modp14 is 2048-bit group assert.strictEqual(prime.length, 256); assert.strictEqual(dh.getGenerator('hex'), '02'); }); + +test(SUITE, 'should reject prime length below 2048 bits', () => { + assert.throws(() => { + crypto.createDiffieHellman(512); + }, /prime length must be at least 2048 bits/); +}); diff --git a/example/src/tests/ecdh/ecdh_tests.ts b/example/src/tests/ecdh/ecdh_tests.ts index 7a1acf83..19165310 100644 --- a/example/src/tests/ecdh/ecdh_tests.ts +++ b/example/src/tests/ecdh/ecdh_tests.ts @@ -53,10 +53,6 @@ test(SUITE, 'should set private key', () => { const alice2 = crypto.createECDH('prime256v1'); alice2.setPrivateKey(priv); - // Public key should be derived/set (depending on impl, but usually settable) - // In our implementation setPrivateKey derives public key? - // Let's check consistency. - // If setPrivateKey derives public key, we can check it matches const pub1 = alice.getPublicKey(); const pub2 = alice2.getPublicKey();