From 2bf497d3a92a6669b71ca17174d6864ba052d7f7 Mon Sep 17 00:00:00 2001 From: Brian Yeh Date: Tue, 31 Mar 2026 09:44:08 +0800 Subject: [PATCH 1/8] chore: add log to dump buffer info --- .../cpp/utils/QuickCryptoUtils.cpp | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/packages/react-native-quick-crypto/cpp/utils/QuickCryptoUtils.cpp b/packages/react-native-quick-crypto/cpp/utils/QuickCryptoUtils.cpp index 9e828952..b20ecf2d 100644 --- a/packages/react-native-quick-crypto/cpp/utils/QuickCryptoUtils.cpp +++ b/packages/react-native-quick-crypto/cpp/utils/QuickCryptoUtils.cpp @@ -1,13 +1,57 @@ #include "QuickCryptoUtils.hpp" +#include #include #include +#include +#include #include #include +#include #include +#include namespace margelo::nitro::crypto { +static std::string getOpenSslErrors() { + std::ostringstream oss; + bool first = true; + unsigned long errCode = ERR_get_error(); + while (errCode != 0) { + char buf[256]; + ERR_error_string_n(errCode, buf, sizeof(buf)); + if (!first) + oss << " | "; + oss << buf; + first = false; + errCode = ERR_get_error(); + } + return first ? "none" : oss.str(); +} + +static std::string toHexByte(uint8_t b) { + std::ostringstream oss; + oss << "0x" << std::hex << std::setw(2) << std::setfill('0') << static_cast(b); + return oss.str(); +} + EVP_PKEY* createEcEvpPkey(const char* group_name, const uint8_t* pub_oct, size_t pub_len, const BIGNUM* priv_bn) { + // Clear stale OpenSSL errors before entering this routine. + ERR_clear_error(); + + int nid = OBJ_txt2nid(group_name); + bool pointDecodeOk = false; + if (nid != NID_undef && pub_oct != nullptr && pub_len > 0) { + EC_GROUP* group = EC_GROUP_new_by_curve_name(nid); + if (group != nullptr) { + EC_POINT* point = EC_POINT_new(group); + if (point != nullptr) { + pointDecodeOk = (EC_POINT_oct2point(group, point, pub_oct, pub_len, nullptr) == 1); + EC_POINT_free(point); + } + EC_GROUP_free(group); + } + } + OSSL_PARAM_BLD* bld = OSSL_PARAM_BLD_new(); if (!bld) throw std::runtime_error("Failed to create OSSL_PARAM_BLD"); @@ -31,9 +75,15 @@ EVP_PKEY* createEcEvpPkey(const char* group_name, const uint8_t* pub_oct, size_t int selection = priv_bn ? EVP_PKEY_KEYPAIR : EVP_PKEY_PUBLIC_KEY; EVP_PKEY* pkey = nullptr; if (EVP_PKEY_fromdata_init(ctx) <= 0 || EVP_PKEY_fromdata(ctx, &pkey, selection, params) <= 0) { + std::string errors = getOpenSslErrors(); + std::ostringstream message; + message << "Failed to create EVP_PKEY from EC parameters" + << " (group=" << (group_name ? group_name : "null") << ", pub_len=" << pub_len + << ", pub_first=" << ((pub_oct != nullptr && pub_len > 0) ? toHexByte(pub_oct[0]) : "n/a") + << ", point_decode_ok=" << (pointDecodeOk ? "true" : "false") << ", openssl_errors=" << errors << ")"; EVP_PKEY_CTX_free(ctx); OSSL_PARAM_free(params); - throw std::runtime_error("Failed to create EVP_PKEY from EC parameters"); + throw std::runtime_error(message.str()); } EVP_PKEY_CTX_free(ctx); From afdaeacc04885a5b8d3edeecba249716923e3c29 Mon Sep 17 00:00:00 2001 From: Brian Yeh Date: Tue, 31 Mar 2026 10:43:51 +0800 Subject: [PATCH 2/8] fix: android build failed - Could not read script 'node_modules/react-native-vector-icons/fonts.gradle' --- example/android/app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 8a721f25..693bcbc9 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -129,7 +129,7 @@ dependencies { } project.ext.vectoricons = [ - iconFontsDir: "../../node_modules/react-native-vector-icons/Fonts", + iconFontsDir: "../../../node_modules/react-native-vector-icons/Fonts", iconFontNames: [ 'MaterialCommunityIcons.ttf' ] ] -apply from: file("../../node_modules/react-native-vector-icons/fonts.gradle") +apply from: file("../../../node_modules/react-native-vector-icons/fonts.gradle") From 9f8d7a260e09bfd20ddee6061a80f637c8552259 Mon Sep 17 00:00:00 2001 From: Brian Yeh Date: Tue, 31 Mar 2026 12:22:05 +0800 Subject: [PATCH 3/8] test: add failing tests --- example/src/tests/ecdh/ecdh_tests.ts | 38 ++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/example/src/tests/ecdh/ecdh_tests.ts b/example/src/tests/ecdh/ecdh_tests.ts index 884891fb..75902fe8 100644 --- a/example/src/tests/ecdh/ecdh_tests.ts +++ b/example/src/tests/ecdh/ecdh_tests.ts @@ -132,6 +132,44 @@ test(SUITE, 'should set private key and compute secret for secp256k1', () => { assert.strictEqual(secret1.toString('hex'), secret2.toString('hex')); }); +test(SUITE, 'should compute secret with sliced public key buffer', () => { + const alice = crypto.createECDH('secp256k1'); + alice.generateKeys(); + + const bob = crypto.createECDH('secp256k1'); + bob.generateKeys(); + + const bobPub = bob.getPublicKey() as Buffer; + assert.isTrue(Buffer.isBuffer(bobPub), 'public key should be a Buffer'); + + // Force non-zero byteOffset by slicing from a larger packet. + const packet = Buffer.concat([ + Buffer.from([0xaa, 0xbb]), + bobPub, + Buffer.from([0xcc]), + ]); + const bobPubSlice = packet.slice(2, 2 + bobPub.length); + assert.strictEqual( + bobPubSlice.length, + bobPub.length, + 'slice length should match key length', + ); + assert.isAbove( + bobPubSlice.byteOffset, + 0, + 'slice should have non-zero byteOffset', + ); + + const secretFromOriginal = alice.computeSecret(bobPub); + const secretFromSlice = alice.computeSecret(bobPubSlice); + + assert.strictEqual( + secretFromSlice.toString('hex'), + secretFromOriginal.toString('hex'), + 'sliced public key should derive the same shared secret', + ); +}); + test(SUITE, 'getCurves - should return array of supported curves', () => { const curves = getCurves(); assert.isArray(curves); From fc8d7494090f21cc8407a9df4c888acc3c4fc653 Mon Sep 17 00:00:00 2001 From: Brian Yeh Date: Tue, 31 Mar 2026 12:25:09 +0800 Subject: [PATCH 4/8] fix: handle Buffer view slices correctly in ECDH/DH bridge --- .../src/diffie-hellman.ts | 17 ++++++++++++----- packages/react-native-quick-crypto/src/ecdh.ts | 15 +++++++++++---- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/packages/react-native-quick-crypto/src/diffie-hellman.ts b/packages/react-native-quick-crypto/src/diffie-hellman.ts index 84ea8e68..dee99656 100644 --- a/packages/react-native-quick-crypto/src/diffie-hellman.ts +++ b/packages/react-native-quick-crypto/src/diffie-hellman.ts @@ -3,6 +3,13 @@ import type { DiffieHellman as DiffieHellmanInterface } from './specs/diffie-hel import { Buffer } from '@craftzdog/react-native-buffer'; import { DH_GROUPS } from './dh-groups'; +function toArrayBufferExact(buf: Buffer): ArrayBuffer { + return buf.buffer.slice( + buf.byteOffset, + buf.byteOffset + buf.byteLength, + ) as ArrayBuffer; +} + export class DiffieHellman { private _hybrid: DiffieHellmanInterface; @@ -37,8 +44,8 @@ export class DiffieHellman { } this._hybrid.init( - primeBuf.buffer as ArrayBuffer, - genBuf.buffer as ArrayBuffer, + toArrayBufferExact(primeBuf), + toArrayBufferExact(genBuf), ); } } @@ -62,7 +69,7 @@ export class DiffieHellman { } const secret = Buffer.from( - this._hybrid.computeSecret(keyBuf.buffer as ArrayBuffer), + this._hybrid.computeSecret(toArrayBufferExact(keyBuf)), ); if (outputEncoding) return secret.toString(outputEncoding); return secret; @@ -99,7 +106,7 @@ export class DiffieHellman { } else { keyBuf = Buffer.from(publicKey, encoding); } - this._hybrid.setPublicKey(keyBuf.buffer as ArrayBuffer); + this._hybrid.setPublicKey(toArrayBufferExact(keyBuf)); } setPrivateKey(privateKey: Buffer | string, encoding?: BufferEncoding): void { @@ -109,7 +116,7 @@ export class DiffieHellman { } else { keyBuf = Buffer.from(privateKey, encoding); } - this._hybrid.setPrivateKey(keyBuf.buffer as ArrayBuffer); + this._hybrid.setPrivateKey(toArrayBufferExact(keyBuf)); } get verifyError(): number { diff --git a/packages/react-native-quick-crypto/src/ecdh.ts b/packages/react-native-quick-crypto/src/ecdh.ts index 677d38a5..3aee0c60 100644 --- a/packages/react-native-quick-crypto/src/ecdh.ts +++ b/packages/react-native-quick-crypto/src/ecdh.ts @@ -6,6 +6,13 @@ const POINT_CONVERSION_COMPRESSED = 2; const POINT_CONVERSION_UNCOMPRESSED = 4; const POINT_CONVERSION_HYBRID = 6; +function toArrayBufferExact(buf: Buffer): ArrayBuffer { + return buf.buffer.slice( + buf.byteOffset, + buf.byteOffset + buf.byteLength, + ) as ArrayBuffer; +} + export class ECDH { private static _convertKeyHybrid: ECDHInterface | undefined; private static get convertKeyHybrid(): ECDHInterface { @@ -43,7 +50,7 @@ export class ECDH { } // ECDH.computeSecret in Node.js returns Buffer - const secret = this._hybrid.computeSecret(keyBuf.buffer as ArrayBuffer); + const secret = this._hybrid.computeSecret(toArrayBufferExact(keyBuf)); return Buffer.from(secret); } @@ -58,7 +65,7 @@ export class ECDH { } else { keyBuf = Buffer.from(privateKey, encoding); } - this._hybrid.setPrivateKey(keyBuf.buffer as ArrayBuffer); + this._hybrid.setPrivateKey(toArrayBufferExact(keyBuf)); } getPublicKey(encoding?: BufferEncoding): Buffer | string { @@ -80,7 +87,7 @@ export class ECDH { } else { keyBuf = Buffer.from(publicKey, encoding); } - this._hybrid.setPublicKey(keyBuf.buffer as ArrayBuffer); + this._hybrid.setPublicKey(toArrayBufferExact(keyBuf)); } static convertKey( @@ -117,7 +124,7 @@ export class ECDH { const result = Buffer.from( ECDH.convertKeyHybrid.convertKey( - keyBuf.buffer as ArrayBuffer, + toArrayBufferExact(keyBuf), curve, formatNum, ), From 2300f31dbc882f785b27ad501a6c13cc7808a079 Mon Sep 17 00:00:00 2001 From: Brian Yeh Date: Tue, 31 Mar 2026 12:26:48 +0800 Subject: [PATCH 5/8] refactor: move toArrayBufferExact function to utils for better code organization --- packages/react-native-quick-crypto/src/diffie-hellman.ts | 8 +------- packages/react-native-quick-crypto/src/ecdh.ts | 8 +------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/packages/react-native-quick-crypto/src/diffie-hellman.ts b/packages/react-native-quick-crypto/src/diffie-hellman.ts index dee99656..f4b6fafd 100644 --- a/packages/react-native-quick-crypto/src/diffie-hellman.ts +++ b/packages/react-native-quick-crypto/src/diffie-hellman.ts @@ -2,13 +2,7 @@ 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'; - -function toArrayBufferExact(buf: Buffer): ArrayBuffer { - return buf.buffer.slice( - buf.byteOffset, - buf.byteOffset + buf.byteLength, - ) as ArrayBuffer; -} +import { toArrayBuffer as toArrayBufferExact } from './utils/conversion'; export class DiffieHellman { private _hybrid: DiffieHellmanInterface; diff --git a/packages/react-native-quick-crypto/src/ecdh.ts b/packages/react-native-quick-crypto/src/ecdh.ts index 3aee0c60..29451b02 100644 --- a/packages/react-native-quick-crypto/src/ecdh.ts +++ b/packages/react-native-quick-crypto/src/ecdh.ts @@ -1,18 +1,12 @@ import { NitroModules } from 'react-native-nitro-modules'; import type { ECDH as ECDHInterface } from './specs/ecdh.nitro'; import { Buffer } from '@craftzdog/react-native-buffer'; +import { toArrayBuffer as toArrayBufferExact } from './utils/conversion'; const POINT_CONVERSION_COMPRESSED = 2; const POINT_CONVERSION_UNCOMPRESSED = 4; const POINT_CONVERSION_HYBRID = 6; -function toArrayBufferExact(buf: Buffer): ArrayBuffer { - return buf.buffer.slice( - buf.byteOffset, - buf.byteOffset + buf.byteLength, - ) as ArrayBuffer; -} - export class ECDH { private static _convertKeyHybrid: ECDHInterface | undefined; private static get convertKeyHybrid(): ECDHInterface { From 3be16325bc7d0e879946e35d25bcd70424629f41 Mon Sep 17 00:00:00 2001 From: Brian Yeh Date: Tue, 31 Mar 2026 12:41:47 +0800 Subject: [PATCH 6/8] revert: "chore: add log to dump buffer info" This reverts commit 2bf497d3a92a6669b71ca17174d6864ba052d7f7. --- .../cpp/utils/QuickCryptoUtils.cpp | 52 +------------------ 1 file changed, 1 insertion(+), 51 deletions(-) diff --git a/packages/react-native-quick-crypto/cpp/utils/QuickCryptoUtils.cpp b/packages/react-native-quick-crypto/cpp/utils/QuickCryptoUtils.cpp index b20ecf2d..9e828952 100644 --- a/packages/react-native-quick-crypto/cpp/utils/QuickCryptoUtils.cpp +++ b/packages/react-native-quick-crypto/cpp/utils/QuickCryptoUtils.cpp @@ -1,57 +1,13 @@ #include "QuickCryptoUtils.hpp" -#include #include #include -#include -#include #include #include -#include #include -#include namespace margelo::nitro::crypto { -static std::string getOpenSslErrors() { - std::ostringstream oss; - bool first = true; - unsigned long errCode = ERR_get_error(); - while (errCode != 0) { - char buf[256]; - ERR_error_string_n(errCode, buf, sizeof(buf)); - if (!first) - oss << " | "; - oss << buf; - first = false; - errCode = ERR_get_error(); - } - return first ? "none" : oss.str(); -} - -static std::string toHexByte(uint8_t b) { - std::ostringstream oss; - oss << "0x" << std::hex << std::setw(2) << std::setfill('0') << static_cast(b); - return oss.str(); -} - EVP_PKEY* createEcEvpPkey(const char* group_name, const uint8_t* pub_oct, size_t pub_len, const BIGNUM* priv_bn) { - // Clear stale OpenSSL errors before entering this routine. - ERR_clear_error(); - - int nid = OBJ_txt2nid(group_name); - bool pointDecodeOk = false; - if (nid != NID_undef && pub_oct != nullptr && pub_len > 0) { - EC_GROUP* group = EC_GROUP_new_by_curve_name(nid); - if (group != nullptr) { - EC_POINT* point = EC_POINT_new(group); - if (point != nullptr) { - pointDecodeOk = (EC_POINT_oct2point(group, point, pub_oct, pub_len, nullptr) == 1); - EC_POINT_free(point); - } - EC_GROUP_free(group); - } - } - OSSL_PARAM_BLD* bld = OSSL_PARAM_BLD_new(); if (!bld) throw std::runtime_error("Failed to create OSSL_PARAM_BLD"); @@ -75,15 +31,9 @@ EVP_PKEY* createEcEvpPkey(const char* group_name, const uint8_t* pub_oct, size_t int selection = priv_bn ? EVP_PKEY_KEYPAIR : EVP_PKEY_PUBLIC_KEY; EVP_PKEY* pkey = nullptr; if (EVP_PKEY_fromdata_init(ctx) <= 0 || EVP_PKEY_fromdata(ctx, &pkey, selection, params) <= 0) { - std::string errors = getOpenSslErrors(); - std::ostringstream message; - message << "Failed to create EVP_PKEY from EC parameters" - << " (group=" << (group_name ? group_name : "null") << ", pub_len=" << pub_len - << ", pub_first=" << ((pub_oct != nullptr && pub_len > 0) ? toHexByte(pub_oct[0]) : "n/a") - << ", point_decode_ok=" << (pointDecodeOk ? "true" : "false") << ", openssl_errors=" << errors << ")"; EVP_PKEY_CTX_free(ctx); OSSL_PARAM_free(params); - throw std::runtime_error(message.str()); + throw std::runtime_error("Failed to create EVP_PKEY from EC parameters"); } EVP_PKEY_CTX_free(ctx); From 3d68751924e8d97b461fae2202ee009b948eee6c Mon Sep 17 00:00:00 2001 From: Brian Yeh Date: Tue, 31 Mar 2026 13:05:00 +0800 Subject: [PATCH 7/8] refactor: use original name toArrayBuffer --- .../src/diffie-hellman.ts | 13 +++++-------- packages/react-native-quick-crypto/src/ecdh.ts | 14 +++++--------- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/packages/react-native-quick-crypto/src/diffie-hellman.ts b/packages/react-native-quick-crypto/src/diffie-hellman.ts index f4b6fafd..308f6071 100644 --- a/packages/react-native-quick-crypto/src/diffie-hellman.ts +++ b/packages/react-native-quick-crypto/src/diffie-hellman.ts @@ -2,7 +2,7 @@ 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'; -import { toArrayBuffer as toArrayBufferExact } from './utils/conversion'; +import { toArrayBuffer } from './utils/conversion'; export class DiffieHellman { private _hybrid: DiffieHellmanInterface; @@ -37,10 +37,7 @@ export class DiffieHellman { genBuf = Buffer.from(generator, encoding as BufferEncoding); } - this._hybrid.init( - toArrayBufferExact(primeBuf), - toArrayBufferExact(genBuf), - ); + this._hybrid.init(toArrayBuffer(primeBuf), toArrayBuffer(genBuf)); } } @@ -63,7 +60,7 @@ export class DiffieHellman { } const secret = Buffer.from( - this._hybrid.computeSecret(toArrayBufferExact(keyBuf)), + this._hybrid.computeSecret(toArrayBuffer(keyBuf)), ); if (outputEncoding) return secret.toString(outputEncoding); return secret; @@ -100,7 +97,7 @@ export class DiffieHellman { } else { keyBuf = Buffer.from(publicKey, encoding); } - this._hybrid.setPublicKey(toArrayBufferExact(keyBuf)); + this._hybrid.setPublicKey(toArrayBuffer(keyBuf)); } setPrivateKey(privateKey: Buffer | string, encoding?: BufferEncoding): void { @@ -110,7 +107,7 @@ export class DiffieHellman { } else { keyBuf = Buffer.from(privateKey, encoding); } - this._hybrid.setPrivateKey(toArrayBufferExact(keyBuf)); + this._hybrid.setPrivateKey(toArrayBuffer(keyBuf)); } get verifyError(): number { diff --git a/packages/react-native-quick-crypto/src/ecdh.ts b/packages/react-native-quick-crypto/src/ecdh.ts index 29451b02..5136a3be 100644 --- a/packages/react-native-quick-crypto/src/ecdh.ts +++ b/packages/react-native-quick-crypto/src/ecdh.ts @@ -1,7 +1,7 @@ import { NitroModules } from 'react-native-nitro-modules'; import type { ECDH as ECDHInterface } from './specs/ecdh.nitro'; import { Buffer } from '@craftzdog/react-native-buffer'; -import { toArrayBuffer as toArrayBufferExact } from './utils/conversion'; +import { toArrayBuffer } from './utils/conversion'; const POINT_CONVERSION_COMPRESSED = 2; const POINT_CONVERSION_UNCOMPRESSED = 4; @@ -44,7 +44,7 @@ export class ECDH { } // ECDH.computeSecret in Node.js returns Buffer - const secret = this._hybrid.computeSecret(toArrayBufferExact(keyBuf)); + const secret = this._hybrid.computeSecret(toArrayBuffer(keyBuf)); return Buffer.from(secret); } @@ -59,7 +59,7 @@ export class ECDH { } else { keyBuf = Buffer.from(privateKey, encoding); } - this._hybrid.setPrivateKey(toArrayBufferExact(keyBuf)); + this._hybrid.setPrivateKey(toArrayBuffer(keyBuf)); } getPublicKey(encoding?: BufferEncoding): Buffer | string { @@ -81,7 +81,7 @@ export class ECDH { } else { keyBuf = Buffer.from(publicKey, encoding); } - this._hybrid.setPublicKey(toArrayBufferExact(keyBuf)); + this._hybrid.setPublicKey(toArrayBuffer(keyBuf)); } static convertKey( @@ -117,11 +117,7 @@ export class ECDH { } const result = Buffer.from( - ECDH.convertKeyHybrid.convertKey( - toArrayBufferExact(keyBuf), - curve, - formatNum, - ), + ECDH.convertKeyHybrid.convertKey(toArrayBuffer(keyBuf), curve, formatNum), ); if (outputEncoding) { From ed49310f27301e5836811d179f973efcea4ff34f Mon Sep 17 00:00:00 2001 From: Brian Yeh Date: Tue, 31 Mar 2026 15:08:26 +0800 Subject: [PATCH 8/8] revert: "fix: android build failed - Could not read script 'node_modules/react-native-vector-icons/fonts.gradle'" This reverts commit afdaeacc04885a5b8d3edeecba249716923e3c29. --- example/android/app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 693bcbc9..8a721f25 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -129,7 +129,7 @@ dependencies { } project.ext.vectoricons = [ - iconFontsDir: "../../../node_modules/react-native-vector-icons/Fonts", + iconFontsDir: "../../node_modules/react-native-vector-icons/Fonts", iconFontNames: [ 'MaterialCommunityIcons.ttf' ] ] -apply from: file("../../../node_modules/react-native-vector-icons/fonts.gradle") +apply from: file("../../node_modules/react-native-vector-icons/fonts.gradle")