diff --git a/README.md b/README.md index be4385f9..41612eb8 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Creating a Wallet took 289 ms ```sh -yarn add react-native-quick-crypto +bun add react-native-quick-crypto react-native-nitro-modules cd ios && pod install ``` diff --git a/bun.lockb b/bun.lockb index f9de01b6..1bd2c97f 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 51fedc84..8747dc43 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -7,7 +7,7 @@ PODS: - hermes-engine (0.76.1): - hermes-engine/Pre-built (= 0.76.1) - hermes-engine/Pre-built (0.76.1) - - NitroModules (0.18.1): + - NitroModules (0.18.2): - DoubleConversion - glog - hermes-engine @@ -1938,7 +1938,7 @@ SPEC CHECKSUMS: fmt: 10c6e61f4be25dc963c36bd73fc7b1705fe975be glog: 08b301085f15bcbb6ff8632a8ebaf239aae04e6a hermes-engine: 46f1ffbf0297f4298862068dd4c274d4ac17a1fd - NitroModules: 55f64932b4581a7d02103bc35b84c7bd3204106b + NitroModules: 47399393665e69228b29a17f501c7b453679ccc0 OpenSSL-Universal: b60a3702c9fea8b3145549d421fdb018e53ab7b4 QuickCrypto: e68316432823f70bdae0bf1486dc5e9afdfdff4d RCT-Folly: 84578c8756030547307e4572ab1947de1685c599 diff --git a/example/package.json b/example/package.json index f0b6cf6d..6ceeb482 100644 --- a/example/package.json +++ b/example/package.json @@ -34,7 +34,7 @@ "react": "18.3.1", "react-native": "0.76.1", "react-native-bouncy-checkbox": "4.0.1", - "react-native-nitro-modules": "0.18.1", + "react-native-nitro-modules": "0.18.2", "react-native-quick-base64": "2.1.2", "react-native-quick-crypto": "workspace:*", "react-native-safe-area-context": "4.14.0", diff --git a/example/src/navigators/children/TestDetailsScreen.tsx b/example/src/navigators/children/TestDetailsScreen.tsx index 90e9d8e4..caab031f 100644 --- a/example/src/navigators/children/TestDetailsScreen.tsx +++ b/example/src/navigators/children/TestDetailsScreen.tsx @@ -30,6 +30,7 @@ export const TestDetailsScreen = ({ route }) => { onPress={() => setShowFailed(!showFailed)} disableText={true} fillColor="red" + style={styles.checkbox} /> Show Failed @@ -39,6 +40,7 @@ export const TestDetailsScreen = ({ route }) => { onPress={() => setShowPassed(!showPassed)} disableText={true} fillColor={colors.green} + style={styles.checkbox} /> Show Passed @@ -101,4 +103,7 @@ const styles = StyleSheet.create({ scrollContent: { paddingHorizontal: 5, }, + checkbox: { + transform: [{ scaleX: 0.8 }, { scaleY: 0.8 }], + }, }); diff --git a/example/src/tests/ed25519/ed25519_tests.ts b/example/src/tests/ed25519/ed25519_tests.ts index d498e108..e27f8241 100644 --- a/example/src/tests/ed25519/ed25519_tests.ts +++ b/example/src/tests/ed25519/ed25519_tests.ts @@ -46,17 +46,17 @@ types.map((type) => { }); */ +const data1 = Buffer.from('hello world'); + test(SUITE, 'sign/verify - round trip happy', async () => { - const data = Buffer.from('hello world'); const ed = new Ed('ed25519', {}); await ed.generateKeyPair(); - const signature = await ed.sign(data.buffer); - const verified = await ed.verify(signature, data.buffer); + const signature = await ed.sign(data1.buffer); + const verified = await ed.verify(signature, data1.buffer); expect(verified).to.be.true; }); test(SUITE, 'sign/verify - round trip sad', async () => { - const data1 = Buffer.from('hello world'); const data2 = Buffer.from('goodbye cruel world'); const ed = new Ed('ed25519', {}); await ed.generateKeyPair(); @@ -66,12 +66,45 @@ test(SUITE, 'sign/verify - round trip sad', async () => { }); test(SUITE, 'sign/verify - bad signature does not verify', async () => { - const data = Buffer.from('hello world'); const ed = new Ed('ed25519', {}); await ed.generateKeyPair(); - const signature = await ed.sign(data.buffer); + const signature = await ed.sign(data1.buffer); const signature2 = randomBytes(64).buffer; expect(ab2str(signature2)).not.to.equal(ab2str(signature)); - const verified = await ed.verify(signature2, data.buffer); + const verified = await ed.verify(signature2, data1.buffer); expect(verified).to.be.false; }); + +test( + SUITE, + 'sign/verify with non-internally generated private key', + async () => { + let ed1: Ed | null = new Ed('ed25519', {}); + await ed1.generateKeyPair(); + const priv = ed1.getPrivateKey(); + ed1 = null; + + const ed2 = new Ed('ed25519', {}); + const signature = await ed2.sign(data1.buffer, priv); + const verified = await ed2.verify(signature, data1.buffer, priv); + expect(verified).to.be.true; + }, +); + +test( + SUITE, + 'sign/verify with bad non-internally generated private key', + async () => { + let ed1: Ed | null = new Ed('ed25519', {}); + await ed1.generateKeyPair(); + const priv = ed1.getPrivateKey(); + ed1 = null; + + const ed2 = new Ed('ed25519', {}); + const signature = await ed2.sign(data1.buffer, priv); + const signature2 = randomBytes(64).buffer; + expect(ab2str(signature2)).not.to.equal(ab2str(signature)); + const verified = await ed2.verify(signature2, data1.buffer, priv); + expect(verified).to.be.false; + }, +); diff --git a/packages/react-native-quick-crypto/cpp/ed25519/HybridEdKeyPair.cpp b/packages/react-native-quick-crypto/cpp/ed25519/HybridEdKeyPair.cpp index 2db3f6a2..6d9f8a32 100644 --- a/packages/react-native-quick-crypto/cpp/ed25519/HybridEdKeyPair.cpp +++ b/packages/react-native-quick-crypto/cpp/ed25519/HybridEdKeyPair.cpp @@ -72,28 +72,36 @@ HybridEdKeyPair::generateKeyPairSync( std::shared_ptr>> HybridEdKeyPair::sign( - const std::shared_ptr& message + const std::shared_ptr& message, + const std::optional>& key ) { // get owned NativeArrayBuffer before passing to sync function auto nativeMessage = ToNativeArrayBuffer(message); + std::optional> nativeKey = std::nullopt; + if (key.has_value()) { + nativeKey = ToNativeArrayBuffer(key.value()); + } - return Promise>::async([this, nativeMessage]() { - return this->signSync(nativeMessage); + return Promise>::async([this, nativeMessage, nativeKey]() { + return this->signSync(nativeMessage, nativeKey); } ); } std::shared_ptr HybridEdKeyPair::signSync( - const std::shared_ptr& message + const std::shared_ptr& message, + const std::optional>& key ) { - this->checkKeyPair(); size_t sig_len = 0; uint8_t* sig = NULL; EVP_MD_CTX* md_ctx = nullptr; EVP_PKEY_CTX* pkey_ctx = nullptr; + // get key to use for signing + EVP_PKEY* pkey = this->importPrivateKey(key); + // key context md_ctx = EVP_MD_CTX_new(); if (md_ctx == nullptr) { @@ -107,7 +115,7 @@ HybridEdKeyPair::signSync( throw std::runtime_error("Error creating signing context: " + this->curve); } - if (EVP_DigestSignInit(md_ctx, &pkey_ctx, NULL, NULL, this->pkey) <= 0) { + if (EVP_DigestSignInit(md_ctx, &pkey_ctx, NULL, NULL, pkey) <= 0) { EVP_MD_CTX_free(md_ctx); char* err = ERR_error_string(ERR_get_error(), NULL); throw std::runtime_error("Failed to initialize signing: " + std::string(err)); @@ -142,14 +150,19 @@ HybridEdKeyPair::signSync( std::shared_ptr> HybridEdKeyPair::verify( const std::shared_ptr& signature, - const std::shared_ptr& message + const std::shared_ptr& message, + const std::optional>& key ) { // get owned NativeArrayBuffers before passing to sync function auto nativeSignature = ToNativeArrayBuffer(signature); auto nativeMessage = ToNativeArrayBuffer(message); + std::optional> nativeKey = std::nullopt; + if (key.has_value()) { + nativeKey = ToNativeArrayBuffer(key.value()); + } - return Promise::async([this, nativeSignature, nativeMessage]() { - return this->verifySync(nativeSignature, nativeMessage); + return Promise::async([this, nativeSignature, nativeMessage, nativeKey]() { + return this->verifySync(nativeSignature, nativeMessage, nativeKey); } ); } @@ -157,9 +170,11 @@ HybridEdKeyPair::verify( bool HybridEdKeyPair::verifySync( const std::shared_ptr& signature, - const std::shared_ptr& message + const std::shared_ptr& message, + const std::optional>& key ) { - this->checkKeyPair(); + // get key to use for verifying + EVP_PKEY* pkey = this->importPrivateKey(key); EVP_MD_CTX* md_ctx = nullptr; EVP_PKEY_CTX* pkey_ctx = nullptr; @@ -177,7 +192,7 @@ HybridEdKeyPair::verifySync( throw std::runtime_error("Error creating verify context: " + this->curve); } - if (EVP_DigestVerifyInit(md_ctx, &pkey_ctx, NULL, NULL, this->pkey) <= 0) { + if (EVP_DigestVerifyInit(md_ctx, &pkey_ctx, NULL, NULL, pkey) <= 0) { EVP_MD_CTX_free(md_ctx); char* err = ERR_error_string(ERR_get_error(), NULL); throw std::runtime_error("Failed to initialize verify: " + std::string(err)); @@ -230,4 +245,24 @@ HybridEdKeyPair::setCurve(const std::string& curve) { this->curve = curve; } +EVP_PKEY* +HybridEdKeyPair::importPrivateKey(const std::optional>& key) { + EVP_PKEY* pkey = nullptr; + if (key.has_value()) { + pkey = EVP_PKEY_new_raw_private_key( + EVP_PKEY_ED25519, // TODO: use this->curve somehow + NULL, + key.value()->data(), + 32 + ); + if (pkey == nullptr) { + throw std::runtime_error("Failed to read private key"); + } + } else { + this->checkKeyPair(); + pkey = this->pkey; + } + return pkey; +} + } // namespace margelo::nitro::crypto diff --git a/packages/react-native-quick-crypto/cpp/ed25519/HybridEdKeyPair.hpp b/packages/react-native-quick-crypto/cpp/ed25519/HybridEdKeyPair.hpp index b8cd8520..42eb14d9 100644 --- a/packages/react-native-quick-crypto/cpp/ed25519/HybridEdKeyPair.hpp +++ b/packages/react-native-quick-crypto/cpp/ed25519/HybridEdKeyPair.hpp @@ -37,36 +37,49 @@ class HybridEdKeyPair : public HybridEdKeyPairSpec { ) override; std::shared_ptr>> - sign(const std::shared_ptr& message) override; + sign( + const std::shared_ptr& message, + const std::optional>& key + ) override; std::shared_ptr - signSync(const std::shared_ptr& message) override; + signSync( + const std::shared_ptr& message, + const std::optional>& key + ) override; std::shared_ptr> verify( const std::shared_ptr& signature, - const std::shared_ptr& message + const std::shared_ptr& message, + const std::optional>& key ) override; bool verifySync( const std::shared_ptr& signature, - const std::shared_ptr& message + const std::shared_ptr& message, + const std::optional>& key ) override; protected: std::shared_ptr getPublicKey() override; - std::shared_ptr getPrivateKey(); + std::shared_ptr + getPrivateKey() override; void checkKeyPair(); - void setCurve(const std::string& curve); + void setCurve(const std::string& curve) override; private: std::string curve; EVP_PKEY* pkey = nullptr; + + EVP_PKEY* importPrivateKey( + const std::optional>& key + ); }; } // namespace margelo::nitro::crypto diff --git a/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridEdKeyPairSpec.cpp b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridEdKeyPairSpec.cpp index 35ee9ab5..fb0f5dc5 100644 --- a/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridEdKeyPairSpec.cpp +++ b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridEdKeyPairSpec.cpp @@ -17,6 +17,7 @@ namespace margelo::nitro::crypto { prototype.registerHybridMethod("generateKeyPair", &HybridEdKeyPairSpec::generateKeyPair); prototype.registerHybridMethod("generateKeyPairSync", &HybridEdKeyPairSpec::generateKeyPairSync); prototype.registerHybridMethod("getPublicKey", &HybridEdKeyPairSpec::getPublicKey); + prototype.registerHybridMethod("getPrivateKey", &HybridEdKeyPairSpec::getPrivateKey); prototype.registerHybridMethod("sign", &HybridEdKeyPairSpec::sign); prototype.registerHybridMethod("signSync", &HybridEdKeyPairSpec::signSync); prototype.registerHybridMethod("verify", &HybridEdKeyPairSpec::verify); diff --git a/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridEdKeyPairSpec.hpp b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridEdKeyPairSpec.hpp index 25c2425b..294df208 100644 --- a/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridEdKeyPairSpec.hpp +++ b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridEdKeyPairSpec.hpp @@ -55,10 +55,11 @@ namespace margelo::nitro::crypto { virtual std::shared_ptr> generateKeyPair(double publicFormat, double publicType, double privateFormat, double privateType, const std::optional& cipher, const std::optional>& passphrase) = 0; virtual void generateKeyPairSync(double publicFormat, double publicType, double privateFormat, double privateType, const std::optional& cipher, const std::optional>& passphrase) = 0; virtual std::shared_ptr getPublicKey() = 0; - virtual std::shared_ptr>> sign(const std::shared_ptr& message) = 0; - virtual std::shared_ptr signSync(const std::shared_ptr& message) = 0; - virtual std::shared_ptr> verify(const std::shared_ptr& signature, const std::shared_ptr& message) = 0; - virtual bool verifySync(const std::shared_ptr& signature, const std::shared_ptr& message) = 0; + virtual std::shared_ptr getPrivateKey() = 0; + virtual std::shared_ptr>> sign(const std::shared_ptr& message, const std::optional>& key) = 0; + virtual std::shared_ptr signSync(const std::shared_ptr& message, const std::optional>& key) = 0; + virtual std::shared_ptr> verify(const std::shared_ptr& message, const std::shared_ptr& signature, const std::optional>& key) = 0; + virtual bool verifySync(const std::shared_ptr& message, const std::shared_ptr& signature, const std::optional>& key) = 0; virtual void setCurve(const std::string& curve) = 0; protected: diff --git a/packages/react-native-quick-crypto/package.json b/packages/react-native-quick-crypto/package.json index e75f3e49..ac6c28b1 100644 --- a/packages/react-native-quick-crypto/package.json +++ b/packages/react-native-quick-crypto/package.json @@ -70,7 +70,7 @@ "dependencies": { "@craftzdog/react-native-buffer": "6.0.5", "events": "3.3.0", - "react-native-nitro-modules": "0.18.1", + "react-native-nitro-modules": "0.18.2", "react-native-quick-base64": "2.1.2", "readable-stream": "4.5.2", "string_decoder": "1.3.0", @@ -88,7 +88,7 @@ "eslint": "9.9.0", "eslint-plugin-react-native": "^4.1.0", "jest": "29.7.0", - "nitro-codegen": "0.18.1", + "nitro-codegen": "0.18.2", "prettier": "3.3.3", "react-native-builder-bob": "0.33.3", "release-it": "17.6.0", diff --git a/packages/react-native-quick-crypto/src/ed.ts b/packages/react-native-quick-crypto/src/ed.ts index 57e97106..34863327 100644 --- a/packages/react-native-quick-crypto/src/ed.ts +++ b/packages/react-native-quick-crypto/src/ed.ts @@ -40,19 +40,37 @@ export class Ed { return this.native.getPublicKey(); } - async sign(message: ArrayBuffer): Promise { - return this.native.sign(message); + getPrivateKey(): ArrayBuffer { + return this.native.getPrivateKey(); } - signSync(message: ArrayBuffer): ArrayBuffer { - return this.native.signSync(message); + async sign(message: ArrayBuffer, key?: ArrayBuffer): Promise { + return key ? this.native.sign(message, key) : this.native.sign(message); } - async verify(signature: ArrayBuffer, message: ArrayBuffer): Promise { - return this.native.verify(signature, message); + signSync(message: ArrayBuffer, key?: ArrayBuffer): ArrayBuffer { + return key + ? this.native.signSync(message, key) + : this.native.signSync(message); } - verifySync(signature: ArrayBuffer, message: ArrayBuffer): boolean { - return this.native.verifySync(signature, message); + async verify( + message: ArrayBuffer, + signature: ArrayBuffer, + key?: ArrayBuffer, + ): Promise { + return key + ? this.native.verify(message, signature, key) + : this.native.verify(message, signature); + } + + verifySync( + message: ArrayBuffer, + signature: ArrayBuffer, + key?: ArrayBuffer, + ): boolean { + return key + ? this.native.verifySync(message, signature, key) + : this.native.verifySync(message, signature); } } diff --git a/packages/react-native-quick-crypto/src/specs/edKeyPair.nitro.ts b/packages/react-native-quick-crypto/src/specs/edKeyPair.nitro.ts index dc6aba6f..5e14d71c 100644 --- a/packages/react-native-quick-crypto/src/specs/edKeyPair.nitro.ts +++ b/packages/react-native-quick-crypto/src/specs/edKeyPair.nitro.ts @@ -21,12 +21,21 @@ export interface EdKeyPair ): void; getPublicKey(): ArrayBuffer; + getPrivateKey(): ArrayBuffer; - sign(message: ArrayBuffer): Promise; - signSync(message: ArrayBuffer): ArrayBuffer; + sign(message: ArrayBuffer, key?: ArrayBuffer): Promise; + signSync(message: ArrayBuffer, key?: ArrayBuffer): ArrayBuffer; - verify(signature: ArrayBuffer, message: ArrayBuffer): Promise; - verifySync(signature: ArrayBuffer, message: ArrayBuffer): boolean; + verify( + message: ArrayBuffer, + signature: ArrayBuffer, + key?: ArrayBuffer, + ): Promise; + verifySync( + message: ArrayBuffer, + signature: ArrayBuffer, + key?: ArrayBuffer, + ): boolean; setCurve(curve: string): void; }