From 898fa0b9c9bcf0906f3c54c15ecec9f5c18267ad Mon Sep 17 00:00:00 2001 From: Nik Graf Date: Tue, 11 Jun 2024 12:33:44 +0200 Subject: [PATCH] feat: add crypto_box_seed_keypair --- README.md | 2 + cpp/react-native-libsodium.cpp | 33 ++++++++++++++ example/src/components/TestResults.tsx | 3 +- example/src/tests/constants_test.ts | 2 + .../src/tests/crypto_box_seed_keypair_test.ts | 45 +++++++++++++++++++ src/lib.native.ts | 27 +++++++++++ src/lib.ts | 5 ++- 7 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 example/src/tests/crypto_box_seed_keypair_test.ts diff --git a/README.md b/README.md index f21861e..c0aec0f 100644 --- a/README.md +++ b/README.md @@ -54,9 +54,11 @@ import { crypto_aead_xchacha20poly1305_ietf_keygen, crypto_box_easy, crypto_box_keypair, + crypto_box_seed_keypair, crypto_box_open_easy, crypto_box_PUBLICKEYBYTES, crypto_box_SECRETKEYBYTES, + crypto_box_SEEDBYTES, crypto_box_seal, crypto_box_seal_open, crypto_kdf_CONTEXTBYTES, diff --git a/cpp/react-native-libsodium.cpp b/cpp/react-native-libsodium.cpp index 3859fc1..ddc22c2 100644 --- a/cpp/react-native-libsodium.cpp +++ b/cpp/react-native-libsodium.cpp @@ -194,6 +194,7 @@ namespace ReactNativeLibsodium jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_box_PUBLICKEYBYTES", static_cast(crypto_box_PUBLICKEYBYTES)); 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)); jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_aead_xchacha20poly1305_ietf_KEYBYTES", static_cast(crypto_aead_xchacha20poly1305_ietf_KEYBYTES)); jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_aead_xchacha20poly1305_ietf_NPUBBYTES", static_cast(crypto_aead_xchacha20poly1305_ietf_NPUBBYTES)); jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_kdf_KEYBYTES", static_cast(crypto_kdf_KEYBYTES)); @@ -469,6 +470,38 @@ namespace ReactNativeLibsodium jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_box_keypair", std::move(jsi_crypto_box_keypair)); + auto jsi_crypto_box_seed_keypair = jsi::Function::createFromHostFunction( + jsiRuntime, + jsi::PropNameID::forUtf8(jsiRuntime, "crypto_box_seed_keypair"), + 1, + [](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *arguments, size_t count) -> jsi::Value + { + const std::string functionName = "crypto_box_seed_keypair"; + + std::string seedArgumentName = "seed"; + unsigned int seedArgumentPosition = 0; + validateIsArrayBuffer(functionName, runtime, arguments[seedArgumentPosition], seedArgumentName, true); + + auto seedDataArrayBuffer = + arguments[seedArgumentPosition].asObject(runtime).getArrayBuffer(runtime); + + unsigned long long publickeyLength = crypto_box_PUBLICKEYBYTES; + unsigned long long secretkeyLength = crypto_box_SECRETKEYBYTES; + std::vector publickey(publickeyLength); + std::vector secretkey(secretkeyLength); + crypto_box_seed_keypair(publickey.data(), secretkey.data(), seedDataArrayBuffer.data(runtime)); + + jsi::Object returnPublicKeyBufferAsObject = arrayBufferAsObject(runtime, publickey); + jsi::Object returnSecretKeyBufferAsObject = arrayBufferAsObject(runtime, secretkey); + + auto object = jsi::Object(runtime); + object.setProperty(runtime, "publicKey", returnPublicKeyBufferAsObject); + object.setProperty(runtime, "secretKey", returnSecretKeyBufferAsObject); + return object; + }); + + jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_box_seed_keypair", std::move(jsi_crypto_box_seed_keypair)); + auto jsi_crypto_sign_keypair = jsi::Function::createFromHostFunction( jsiRuntime, jsi::PropNameID::forUtf8(jsiRuntime, "crypto_sign_keypair"), diff --git a/example/src/components/TestResults.tsx b/example/src/components/TestResults.tsx index 2b2de25..b38c3b4 100644 --- a/example/src/components/TestResults.tsx +++ b/example/src/components/TestResults.tsx @@ -10,9 +10,10 @@ 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_box_seal_test'; +import '../tests/crypto_box_seed_keypair_test'; import '../tests/crypto_generichash_test'; import '../tests/crypto_kdf_derive_from_key_test'; import '../tests/crypto_kdf_keygen_test'; diff --git a/example/src/tests/constants_test.ts b/example/src/tests/constants_test.ts index 7d25122..f53f83c 100644 --- a/example/src/tests/constants_test.ts +++ b/example/src/tests/constants_test.ts @@ -7,6 +7,7 @@ import { crypto_auth_KEYBYTES, crypto_box_PUBLICKEYBYTES, crypto_box_SECRETKEYBYTES, + crypto_box_SEEDBYTES, crypto_kdf_CONTEXTBYTES, crypto_kdf_KEYBYTES, crypto_secretbox_KEYBYTES, @@ -22,6 +23,7 @@ test('constants', () => { expect(crypto_secretbox_NONCEBYTES).toEqual(24); expect(crypto_box_PUBLICKEYBYTES).toEqual(32); expect(crypto_box_SECRETKEYBYTES).toEqual(32); + expect(crypto_box_SEEDBYTES).toEqual(32); expect(crypto_aead_xchacha20poly1305_ietf_KEYBYTES).toEqual(32); expect(crypto_kdf_KEYBYTES).toEqual(32); expect(crypto_kdf_CONTEXTBYTES).toEqual(8); diff --git a/example/src/tests/crypto_box_seed_keypair_test.ts b/example/src/tests/crypto_box_seed_keypair_test.ts new file mode 100644 index 0000000..52ad83b --- /dev/null +++ b/example/src/tests/crypto_box_seed_keypair_test.ts @@ -0,0 +1,45 @@ +import { + crypto_box_PUBLICKEYBYTES, + crypto_box_SECRETKEYBYTES, + crypto_box_seed_keypair, + from_base64, +} from 'react-native-libsodium'; +import { expect, test } from '../utils/testRunner'; + +test('crypto_box_seed_keypair', () => { + const seed = from_base64('KI70zL1z1j7-IRjn1YG-qgkbbR0QCFggiqWcAA5bXIk'); + + const keyPair = crypto_box_seed_keypair(seed); + const keyPairBase64 = crypto_box_seed_keypair(seed, 'base64'); + + expect(keyPair.keyType).toEqual('x25519'); + expect(keyPair.publicKey.length).toEqual(crypto_box_PUBLICKEYBYTES); + expect(typeof keyPair.publicKey).toEqual('object'); + expect(keyPair.publicKey).toEqual( + new Uint8Array([ + 108, 139, 52, 190, 205, 39, 174, 21, 111, 62, 10, 12, 133, 182, 39, 113, + 221, 51, 135, 183, 139, 101, 52, 64, 119, 21, 133, 7, 85, 73, 93, 7, + ]) + ); + + expect(keyPair.privateKey.length).toEqual(crypto_box_SECRETKEYBYTES); + expect(typeof keyPair.privateKey).toEqual('object'); + expect(keyPair.privateKey).toEqual( + new Uint8Array([ + 28, 193, 69, 156, 167, 29, 242, 149, 39, 5, 162, 42, 15, 246, 31, 73, 182, + 214, 112, 23, 214, 0, 1, 101, 65, 125, 229, 229, 10, 180, 106, 124, + ]) + ); + + expect(keyPairBase64.keyType).toEqual('x25519'); + expect(keyPairBase64.publicKey.length).toEqual(43); + expect(typeof keyPairBase64.publicKey).toEqual('string'); + expect(keyPairBase64.publicKey).toEqual( + 'bIs0vs0nrhVvPgoMhbYncd0zh7eLZTRAdxWFB1VJXQc' + ); + expect(keyPairBase64.privateKey.length).toEqual(43); + expect(typeof keyPairBase64.privateKey).toEqual('string'); + expect(keyPairBase64.privateKey).toEqual( + 'HMFFnKcd8pUnBaIqD_YfSbbWcBfWAAFlQX3l5Qq0anw' + ); +}); diff --git a/src/lib.native.ts b/src/lib.native.ts index 54359e3..7eb0699 100644 --- a/src/lib.native.ts +++ b/src/lib.native.ts @@ -30,6 +30,7 @@ declare global { var jsi_crypto_box_PUBLICKEYBYTES: number; var jsi_crypto_box_SECRETKEYBYTES: number; var jsi_crypto_box_NONCEBYTES: number; + var jsi_crypto_box_SEEDBYTES: number; var jsi_crypto_aead_xchacha20poly1305_ietf_KEYBYTES: number; var jsi_crypto_aead_xchacha20poly1305_ietf_NPUBBYTES: number; var jsi_crypto_kdf_KEYBYTES: number; @@ -79,6 +80,10 @@ declare global { publicKey: ArrayBuffer; secretKey: ArrayBuffer; }; + function jsi_crypto_box_seed_keypair(seed: ArrayBuffer): { + publicKey: ArrayBuffer; + secretKey: ArrayBuffer; + }; function jsi_crypto_sign_keypair(): { publicKey: ArrayBuffer; secretKey: ArrayBuffer; @@ -183,6 +188,7 @@ export const crypto_secretbox_NONCEBYTES = export const crypto_box_PUBLICKEYBYTES = global.jsi_crypto_box_PUBLICKEYBYTES; export const crypto_box_SECRETKEYBYTES = global.jsi_crypto_box_SECRETKEYBYTES; export const crypto_box_NONCEBYTES = global.jsi_crypto_box_NONCEBYTES; +export const crypto_box_SEEDBYTES = global.jsi_crypto_box_SEEDBYTES; export const crypto_aead_xchacha20poly1305_ietf_KEYBYTES = global.jsi_crypto_aead_xchacha20poly1305_ietf_KEYBYTES; export const crypto_aead_xchacha20poly1305_ietf_NPUBBYTES = @@ -345,6 +351,26 @@ export function crypto_box_keypair(outputFormat: OutputFormat): unknown { }; } +export function crypto_box_seed_keypair( + seed: Uint8Array, + outputFormat?: Uint8ArrayOutputFormat | null +): KeyPair; +export function crypto_box_seed_keypair( + seed: Uint8Array, + outputFormat: StringOutputFormat +): StringKeyPair; +export function crypto_box_seed_keypair( + seed: Uint8Array, + outputFormat: OutputFormat +): unknown { + const result = global.jsi_crypto_box_seed_keypair(seed.buffer); + return { + keyType: 'x25519', + publicKey: convertToOutputFormat(result.publicKey, outputFormat), + privateKey: convertToOutputFormat(result.secretKey, outputFormat), + }; +} + export function crypto_sign_keypair( outputFormat?: Uint8ArrayOutputFormat | null ): KeyPair; @@ -813,6 +839,7 @@ export default { crypto_aead_xchacha20poly1305_ietf_keygen, crypto_aead_xchacha20poly1305_ietf_NPUBBYTES, crypto_box_easy, + crypto_box_seed_keypair, crypto_box_keypair, crypto_box_NONCEBYTES, crypto_box_open_easy, diff --git a/src/lib.ts b/src/lib.ts index f9c0e9c..2b00110 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -14,6 +14,8 @@ export type { StringSecretBox, Uint8ArrayOutputFormat, } from 'libsodium-wrappers'; +import * as hkdf from '@noble/hashes/hkdf'; +import { sha256 } from '@noble/hashes/sha256'; import type { CryptoBox, CryptoKX, @@ -30,8 +32,6 @@ import type { StringSecretBox, Uint8ArrayOutputFormat, } from 'libsodium-wrappers'; -import * as hkdf from '@noble/hashes/hkdf'; -import { sha256 } from '@noble/hashes/sha256'; let isLoadSumoVersion = false; @@ -153,6 +153,7 @@ export const ready = new Promise(async (resolve) => { crypto_box_easy = lib.crypto_box_easy; crypto_box_easy_afternm = lib.crypto_box_easy_afternm; crypto_box_keypair = lib.crypto_box_keypair; + crypto_box_seed_keypair = lib.crypto_box_seed_keypair; crypto_box_open_detached = lib.crypto_box_open_detached; crypto_box_open_easy = lib.crypto_box_open_easy; crypto_box_open_easy_afternm = lib.crypto_box_open_easy_afternm;