diff --git a/cpp/react-native-libsodium.cpp b/cpp/react-native-libsodium.cpp index 780ec76..3859fc1 100644 --- a/cpp/react-native-libsodium.cpp +++ b/cpp/react-native-libsodium.cpp @@ -968,6 +968,126 @@ namespace ReactNativeLibsodium jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_box_open_easy", std::move(jsi_crypto_box_open_easy)); + auto jsi_crypto_box_seal = jsi::Function::createFromHostFunction( + jsiRuntime, + jsi::PropNameID::forUtf8(jsiRuntime, "jsi_crypto_box_seal"), + 2, + [](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *arguments, size_t count) -> jsi::Value + { + const std::string functionName = "crypto_box_seal"; + + std::string messageArgumentName = "message"; + unsigned int messageArgumentPosition = 0; + JsiArgType messageArgType = validateIsStringOrArrayBuffer(functionName, runtime, arguments[messageArgumentPosition], messageArgumentName, true); + + std::string publicKeyArgumentName = "publicKey"; + unsigned int publicKeyArgumentPosition = 1; + validateIsArrayBuffer(functionName, runtime, arguments[publicKeyArgumentPosition], publicKeyArgumentName, true); + + auto publicKey = arguments[publicKeyArgumentPosition].asObject(runtime).getArrayBuffer(runtime); + + if (publicKey.length(runtime) != crypto_box_PUBLICKEYBYTES) + { + throw jsi::JSError(runtime, "invalid publicKey length"); + } + + std::vector ciphertext; + int result = -1; + + if (messageArgType == JsiArgType::string) + { + std::string messageString = arguments[messageArgumentPosition].asString(runtime).utf8(runtime); + ciphertext.resize(messageString.length() + crypto_box_SEALBYTES); + result = crypto_box_seal( + ciphertext.data(), + reinterpret_cast(messageString.data()), + messageString.length(), + publicKey.data(runtime)); + } + else + { + auto messageArrayBuffer = arguments[messageArgumentPosition].asObject(runtime).getArrayBuffer(runtime); + ciphertext.resize(messageArrayBuffer.length(runtime) + crypto_box_SEALBYTES); + result = crypto_box_seal( + ciphertext.data(), + messageArrayBuffer.data(runtime), + messageArrayBuffer.length(runtime), + publicKey.data(runtime)); + } + + throwOnBadResult(functionName, runtime, result); + return arrayBufferAsObject(runtime, ciphertext); + }); + + jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_box_seal", std::move(jsi_crypto_box_seal)); + + auto jsi_crypto_box_seal_open = jsi::Function::createFromHostFunction( + jsiRuntime, + jsi::PropNameID::forUtf8(jsiRuntime, "jsi_crypto_box_seal_open"), + 3, + [](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *arguments, size_t count) -> jsi::Value + { + const std::string functionName = "crypto_box_seal_open"; + + std::string ciphertextArgumentName = "ciphertext"; + unsigned int ciphertextArgumentPosition = 0; + JsiArgType ciphertextArgType = validateIsStringOrArrayBuffer(functionName, runtime, arguments[ciphertextArgumentPosition], ciphertextArgumentName, true); + + std::string publicKeyArgumentName = "publicKey"; + unsigned int publicKeyArgumentPosition = 1; + validateIsArrayBuffer(functionName, runtime, arguments[publicKeyArgumentPosition], publicKeyArgumentName, true); + + std::string secretKeyArgumentName = "secretKey"; + unsigned int secretKeyArgumentPosition = 2; + validateIsArrayBuffer(functionName, runtime, arguments[secretKeyArgumentPosition], secretKeyArgumentName, true); + + auto publicKeyArrayBuffer = + arguments[1].asObject(runtime).getArrayBuffer(runtime); + + auto secretKeyArrayBuffer = + arguments[2].asObject(runtime).getArrayBuffer(runtime); + + if (publicKeyArrayBuffer.length(runtime) != crypto_box_PUBLICKEYBYTES) + { + throw jsi::JSError(runtime, "invalid publicKey length"); + } + if (secretKeyArrayBuffer.length(runtime) != crypto_box_SECRETKEYBYTES) + { + throw jsi::JSError(runtime, "invalid privateKey length"); + } + + std::vector message; + int result = -1; + + if (ciphertextArgType == JsiArgType::string) + { + std::string ciphertextString = arguments[ciphertextArgumentPosition].asString(runtime).utf8(runtime); + message.resize(ciphertextString.length() - crypto_box_SEALBYTES); + result = crypto_box_seal_open( + message.data(), + reinterpret_cast(ciphertextString.data()), + ciphertextString.length(), + publicKeyArrayBuffer.data(runtime), + secretKeyArrayBuffer.data(runtime)); + } + else + { + auto ciphertextArrayBuffer = arguments[ciphertextArgumentPosition].asObject(runtime).getArrayBuffer(runtime); + message.resize(ciphertextArrayBuffer.length(runtime) - crypto_box_SEALBYTES); + result = crypto_box_seal_open( + message.data(), + ciphertextArrayBuffer.data(runtime), + ciphertextArrayBuffer.length(runtime), + publicKeyArrayBuffer.data(runtime), + secretKeyArrayBuffer.data(runtime)); + } + + throwOnBadResult(functionName, runtime, result); + return arrayBufferAsObject(runtime, message); + }); + + jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_box_seal_open", std::move(jsi_crypto_box_seal_open)); + auto jsi_crypto_pwhash = jsi::Function::createFromHostFunction( jsiRuntime, jsi::PropNameID::forUtf8(jsiRuntime, "jsi_crypto_pwhash"), diff --git a/example/src/components/TestResults.tsx b/example/src/components/TestResults.tsx index 9237309..2b2de25 100644 --- a/example/src/components/TestResults.tsx +++ b/example/src/components/TestResults.tsx @@ -10,6 +10,7 @@ import '../tests/crypto_auth_keygen_test'; import '../tests/crypto_auth_test'; import '../tests/crypto_auth_verify_test'; import '../tests/crypto_box_easy_test'; +import '../tests/crypto_box_seal_test'; import '../tests/crypto_box_keypair_test'; import '../tests/crypto_box_open_easy_test'; import '../tests/crypto_generichash_test'; diff --git a/example/src/tests/crypto_box_seal_test.ts b/example/src/tests/crypto_box_seal_test.ts new file mode 100644 index 0000000..1ec8ac4 --- /dev/null +++ b/example/src/tests/crypto_box_seal_test.ts @@ -0,0 +1,53 @@ +import { crypto_box_seal, crypto_box_seal_open } from 'react-native-libsodium'; +import { expect, test } from '../utils/testRunner'; + +test('crypto_box_seal', () => { + const receiverKeyPair = { + keyType: 'x25519', + privateKey: new Uint8Array([ + 232, 167, 21, 228, 54, 165, 143, 50, 85, 27, 167, 176, 163, 211, 176, 7, + 159, 111, 77, 250, 19, 16, 169, 199, 109, 135, 21, 253, 184, 239, 207, + 172, + ]), + publicKey: new Uint8Array([ + 142, 127, 163, 61, 43, 54, 5, 80, 178, 86, 18, 245, 253, 136, 7, 152, 90, + 152, 194, 31, 23, 253, 243, 87, 53, 66, 15, 42, 18, 238, 19, 12, + ]), + }; + + const message = 'Hello, world!'; + const messageSealed = crypto_box_seal(message, receiverKeyPair.publicKey); + const messageOpen = crypto_box_seal_open( + messageSealed, + receiverKeyPair.publicKey, + receiverKeyPair.privateKey + ); + expect(messageOpen).toEqual( + new Uint8Array([ + 72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33, + ]) + ); + + const message2 = new Uint8Array([ + 8, 231, 240, 41, 106, 138, 234, 14, 38, 102, 70, 86, 168, 115, 93, 238, 3, + 95, 224, 157, 125, 40, 151, 150, 147, 223, 7, 153, 132, 32, 92, 36, + ]); + const message2Sealed = crypto_box_seal(message2, receiverKeyPair.publicKey); + const message2Open = crypto_box_seal_open( + message2Sealed, + receiverKeyPair.publicKey, + receiverKeyPair.privateKey + ); + expect(message2Open).toEqual(message2); + + expect(() => { + crypto_box_seal( + message, + new Uint8Array([ + 232, 167, 21, 228, 54, 165, 143, 50, 85, 27, 167, 176, 163, 211, 176, 7, + 159, 111, 77, 250, 19, 16, 169, 199, 109, 135, 21, 253, 184, 239, 207, + 172, 100, + ]) + ); + }).toThrow(); +}); diff --git a/src/lib.native.ts b/src/lib.native.ts index ae1d6f8..54359e3 100644 --- a/src/lib.native.ts +++ b/src/lib.native.ts @@ -118,6 +118,15 @@ declare global { publicKey: ArrayBuffer, secretKey: ArrayBuffer ): ArrayBuffer; + function jsi_crypto_box_seal( + message: string | ArrayBuffer, + publicKey: ArrayBuffer + ): ArrayBuffer; + function jsi_crypto_box_seal_open( + ciphertext: string | ArrayBuffer, + publicKey: ArrayBuffer, + secretKey: ArrayBuffer + ): ArrayBuffer; function jsi_crypto_generichash( hashLength: number, message: string | ArrayBuffer, @@ -529,6 +538,57 @@ export function crypto_box_open_easy( return convertToOutputFormat(result, outputFormat); } +export function crypto_box_seal( + ciphertext: string | Uint8Array, + publicKey: Uint8Array, + outputFormat?: Uint8ArrayOutputFormat | null +): Uint8Array; +export function crypto_box_seal( + ciphertext: string | Uint8Array, + publicKey: Uint8Array, + outputFormat: StringOutputFormat +): string; +export function crypto_box_seal( + ciphertext: string | Uint8Array, + publicKey: Uint8Array, + outputFormat: OutputFormat +) { + let result: ArrayBuffer; + const ciphertextParam = + typeof ciphertext === 'string' ? ciphertext : ciphertext.buffer; + result = global.jsi_crypto_box_seal(ciphertextParam, publicKey.buffer); + return convertToOutputFormat(result, outputFormat); +} + +export function crypto_box_seal_open( + ciphertext: string | Uint8Array, + publicKey: Uint8Array, + privateKey: Uint8Array, + outputFormat?: Uint8ArrayOutputFormat | null +): Uint8Array; +export function crypto_box_seal_open( + ciphertext: string | Uint8Array, + publicKey: Uint8Array, + privateKey: Uint8Array, + outputFormat: StringOutputFormat +): string; +export function crypto_box_seal_open( + ciphertext: string | Uint8Array, + publicKey: Uint8Array, + privateKey: Uint8Array, + outputFormat: OutputFormat +) { + let result: ArrayBuffer; + const ciphertextParam = + typeof ciphertext === 'string' ? ciphertext : ciphertext.buffer; + result = global.jsi_crypto_box_seal_open( + ciphertextParam, + publicKey.buffer, + privateKey.buffer + ); + return convertToOutputFormat(result, outputFormat); +} + export function crypto_generichash( hash_length: number, message: string | Uint8Array,