From b98a1a79e3503ee7318532944c196770641c7bda Mon Sep 17 00:00:00 2001 From: Prathamesh More Date: Sat, 6 Sep 2025 23:53:07 +0530 Subject: [PATCH 1/3] feat: add jsi_crypto_box_beforenm function for shared secret generation --- cpp/react-native-libsodium.cpp | 43 ++++++++++++++++++++++++++++++++++ src/lib.native.ts | 12 ++++++++++ 2 files changed, 55 insertions(+) diff --git a/cpp/react-native-libsodium.cpp b/cpp/react-native-libsodium.cpp index a97e74b..30992aa 100644 --- a/cpp/react-native-libsodium.cpp +++ b/cpp/react-native-libsodium.cpp @@ -1122,6 +1122,49 @@ namespace ReactNativeLibsodium jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_box_seal_open", std::move(jsi_crypto_box_seal_open)); + auto jsi_crypto_box_beforenm = jsi::Function::createFromHostFunction( + jsiRuntime, + jsi::PropNameID::forUtf8(jsiRuntime, "jsi_crypto_box_beforenm"), + 2, + [](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *arguments, size_t count) -> jsi::Value + { + const std::string functionName = "crypto_box_beforenm"; + + std::string publicKeyArgumentName = "publicKey"; + unsigned int publicKeyArgumentPosition = 0; + JsiArgType publicKeyArgType = validateIsStringOrArrayBuffer(functionName, runtime, arguments[publicKeyArgumentPosition], publicKeyArgumentName, true); + + std::string privateKeyArgumentName = "privateKey"; + unsigned int privateKeyArgumentPosition = 1; + JsiArgType privateKeyArgType = validateIsStringOrArrayBuffer(functionName, runtime, arguments[privateKeyArgumentPosition], privateKeyArgumentName, true); + + auto publicKey = arguments[publicKeyArgumentPosition].asObject(runtime).getArrayBuffer(runtime); + auto privateKey = arguments[privateKeyArgumentPosition].asObject(runtime).getArrayBuffer(runtime); + + if (publicKey.length(runtime) != crypto_box_PUBLICKEYBYTES) + { + throw jsi::JSError(runtime, "invalid publicKey length"); + } + + if (privateKey.length(runtime) != crypto_box_SECRETKEYBYTES) + { + throw jsi::JSError(runtime, "invalid privateKey length"); + } + + // Allocate memory for the shared secret + std::vector sharedSecret(crypto_box_BEFORENMBYTES); + + int result = crypto_box_beforenm( + sharedSecret.data(), + publicKey.data(runtime), + privateKey.data(runtime)); + + throwOnBadResult(functionName, runtime, result); + return arrayBufferAsObject(runtime, sharedSecret); + }); + + jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_box_beforenm", std::move(jsi_crypto_box_beforenm)); + auto jsi_crypto_pwhash = jsi::Function::createFromHostFunction( jsiRuntime, jsi::PropNameID::forUtf8(jsiRuntime, "jsi_crypto_pwhash"), diff --git a/src/lib.native.ts b/src/lib.native.ts index 134678d..d03194b 100644 --- a/src/lib.native.ts +++ b/src/lib.native.ts @@ -133,6 +133,10 @@ declare global { publicKey: ArrayBuffer, secretKey: ArrayBuffer ): ArrayBuffer; + function jsi_crypto_box_beforenm( + message: ArrayBuffer, + publicKey: ArrayBuffer + ): ArrayBuffer; function jsi_crypto_generichash( hashLength: number, message: string | ArrayBuffer, @@ -621,6 +625,13 @@ export function crypto_box_seal_open( return convertToOutputFormat(result, outputFormat); } +export function crypto_box_beforenm( + publicKey: Uint8Array, + privateKey: Uint8Array +) { + return global.jsi_crypto_box_beforenm(publicKey.buffer, privateKey.buffer); +} + export function crypto_generichash( hash_length: number, message: string | Uint8Array, @@ -860,6 +871,7 @@ export default { crypto_box_open_easy, crypto_box_PUBLICKEYBYTES, crypto_box_SECRETKEYBYTES, + crypto_box_beforenm, crypto_generichash, crypto_generichash_BYTES, crypto_generichash_BYTES_MIN, From e515042b6640c38af003eacb56aaa9c87bb74105 Mon Sep 17 00:00:00 2001 From: Prathamesh More Date: Thu, 11 Dec 2025 22:35:11 +0530 Subject: [PATCH 2/3] feat: add tests for crypto_box_beforenm function --- example/src/components/TestResults.tsx | 1 + example/src/tests/crypto_box_beforenm_test.ts | 41 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 example/src/tests/crypto_box_beforenm_test.ts diff --git a/example/src/components/TestResults.tsx b/example/src/components/TestResults.tsx index 806ca22..b0fa223 100644 --- a/example/src/components/TestResults.tsx +++ b/example/src/components/TestResults.tsx @@ -9,6 +9,7 @@ import '../tests/crypto_aead_xchacha20poly1305_ietf_keygen_test'; import '../tests/crypto_auth_keygen_test'; import '../tests/crypto_auth_test'; import '../tests/crypto_auth_verify_test'; +import '../tests/crypto_box_beforenm_test'; import '../tests/crypto_box_easy_test'; import '../tests/crypto_box_keypair_test'; import '../tests/crypto_box_open_easy_test'; diff --git a/example/src/tests/crypto_box_beforenm_test.ts b/example/src/tests/crypto_box_beforenm_test.ts new file mode 100644 index 0000000..33c4f87 --- /dev/null +++ b/example/src/tests/crypto_box_beforenm_test.ts @@ -0,0 +1,41 @@ +import { + crypto_box_BEFORENMBYTES, + crypto_box_beforenm, + crypto_box_keypair, +} from 'react-native-libsodium'; +import { expect, test } from '../utils/testRunner'; + +test('crypto_box_beforenm', () => { + const alice = crypto_box_keypair(); + const bob = crypto_box_keypair(); + + // produces symmetric shared keys + const sharedAlice = crypto_box_beforenm(bob.publicKey, alice.privateKey); + const sharedBob = crypto_box_beforenm(alice.publicKey, bob.privateKey); + + expect(sharedAlice.length).toEqual(crypto_box_BEFORENMBYTES); + expect(sharedBob.length).toEqual(crypto_box_BEFORENMBYTES); + expect(sharedAlice).toEqual(sharedBob); + + // validates its inputs + expect(() => { + crypto_box_beforenm( + alice.publicKey.slice(0, alice.publicKey.length - 1), + alice.privateKey + ); + }).toThrow(); + + expect(() => { + crypto_box_beforenm( + alice.publicKey, + alice.privateKey.slice(0, alice.privateKey.length - 1) + ); + }).toThrow(); + + expect(() => { + crypto_box_beforenm( + new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]), + alice.privateKey + ); + }).toThrow(); +}); From 8547df6435302e0118dfad648e0dc38f48a9ca65 Mon Sep 17 00:00:00 2001 From: Prathamesh More Date: Thu, 11 Dec 2025 23:19:42 +0530 Subject: [PATCH 3/3] feat: add crypto_box_BEFORENMBYTES constant and update related functionality --- README.md | 2 ++ cpp/react-native-libsodium.cpp | 1 + example/src/tests/constants_test.ts | 2 ++ src/lib.native.ts | 14 ++++++++++---- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c0aec0f..b7faba4 100644 --- a/README.md +++ b/README.md @@ -52,10 +52,12 @@ import { crypto_aead_xchacha20poly1305_ietf_KEYBYTES, crypto_aead_xchacha20poly1305_ietf_NPUBBYTES, crypto_aead_xchacha20poly1305_ietf_keygen, + crypto_box_beforenm, crypto_box_easy, crypto_box_keypair, crypto_box_seed_keypair, crypto_box_open_easy, + crypto_box_BEFORENMBYTES, crypto_box_PUBLICKEYBYTES, crypto_box_SECRETKEYBYTES, crypto_box_SEEDBYTES, diff --git a/cpp/react-native-libsodium.cpp b/cpp/react-native-libsodium.cpp index 30992aa..ba87d29 100644 --- a/cpp/react-native-libsodium.cpp +++ b/cpp/react-native-libsodium.cpp @@ -192,6 +192,7 @@ namespace ReactNativeLibsodium jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_pwhash_OPSLIMIT_INTERACTIVE", static_cast(crypto_pwhash_OPSLIMIT_INTERACTIVE)); jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_pwhash_MEMLIMIT_INTERACTIVE", static_cast(crypto_pwhash_MEMLIMIT_INTERACTIVE)); jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_box_PUBLICKEYBYTES", static_cast(crypto_box_PUBLICKEYBYTES)); + jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_box_BEFORENMBYTES", static_cast(crypto_box_BEFORENMBYTES)); jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_box_SECRETKEYBYTES", static_cast(crypto_box_SECRETKEYBYTES)); jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_box_NONCEBYTES", static_cast(crypto_box_NONCEBYTES)); jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_box_SEEDBYTES", static_cast(crypto_box_SEEDBYTES)); diff --git a/example/src/tests/constants_test.ts b/example/src/tests/constants_test.ts index 5110369..9370dd0 100644 --- a/example/src/tests/constants_test.ts +++ b/example/src/tests/constants_test.ts @@ -5,6 +5,7 @@ import { crypto_aead_xchacha20poly1305_ietf_KEYBYTES, crypto_auth_BYTES, crypto_auth_KEYBYTES, + crypto_box_BEFORENMBYTES, crypto_box_PUBLICKEYBYTES, crypto_box_SECRETKEYBYTES, crypto_box_SEEDBYTES, @@ -22,6 +23,7 @@ test('constants', () => { expect(crypto_auth_KEYBYTES).toEqual(32); expect(crypto_secretbox_KEYBYTES).toEqual(32); expect(crypto_secretbox_NONCEBYTES).toEqual(24); + expect(crypto_box_BEFORENMBYTES).toEqual(32); expect(crypto_box_PUBLICKEYBYTES).toEqual(32); expect(crypto_box_SECRETKEYBYTES).toEqual(32); expect(crypto_box_SEEDBYTES).toEqual(32); diff --git a/src/lib.native.ts b/src/lib.native.ts index d03194b..883bb4d 100644 --- a/src/lib.native.ts +++ b/src/lib.native.ts @@ -25,6 +25,7 @@ if (Libsodium && typeof Libsodium.install === 'function') { declare global { var jsi_crypto_auth_BYTES: number; var jsi_crypto_auth_KEYBYTES: number; + var jsi_crypto_box_BEFORENMBYTES: number; var jsi_crypto_secretbox_KEYBYTES: number; var jsi_crypto_secretbox_NONCEBYTES: number; var jsi_crypto_box_PUBLICKEYBYTES: number; @@ -134,8 +135,8 @@ declare global { secretKey: ArrayBuffer ): ArrayBuffer; function jsi_crypto_box_beforenm( - message: ArrayBuffer, - publicKey: ArrayBuffer + publicKey: ArrayBuffer, + privateKey: ArrayBuffer ): ArrayBuffer; function jsi_crypto_generichash( hashLength: number, @@ -190,6 +191,7 @@ declare global { export const crypto_auth_BYTES = global.jsi_crypto_auth_BYTES; export const crypto_auth_KEYBYTES = global.jsi_crypto_auth_KEYBYTES; +export const crypto_box_BEFORENMBYTES = global.jsi_crypto_box_BEFORENMBYTES; export const crypto_secretbox_KEYBYTES = global.jsi_crypto_secretbox_KEYBYTES; export const crypto_secretbox_NONCEBYTES = global.jsi_crypto_secretbox_NONCEBYTES; @@ -627,9 +629,12 @@ export function crypto_box_seal_open( export function crypto_box_beforenm( publicKey: Uint8Array, - privateKey: Uint8Array + privateKey: Uint8Array, + outputFormat: OutputFormat ) { - return global.jsi_crypto_box_beforenm(publicKey.buffer, privateKey.buffer); + let result: ArrayBuffer; + result = global.jsi_crypto_box_beforenm(publicKey.buffer, privateKey.buffer); + return convertToOutputFormat(result, outputFormat); } export function crypto_generichash( @@ -869,6 +874,7 @@ export default { crypto_box_keypair, crypto_box_NONCEBYTES, crypto_box_open_easy, + crypto_box_BEFORENMBYTES, crypto_box_PUBLICKEYBYTES, crypto_box_SECRETKEYBYTES, crypto_box_beforenm,