diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index f134ef0d..75d21591 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -37,7 +37,7 @@ PODS: - glog - hermes-engine - NitroModules - - OpenSSL-Universal + - OpenSSL-Universal (= 3.3.3001) - RCT-Folly (= 2024.10.14.00) - RCTRequired - RCTTypeSafety @@ -1974,72 +1974,72 @@ SPEC CHECKSUMS: fmt: 01b82d4ca6470831d1cc0852a1af644be019e8f6 glog: 08b301085f15bcbb6ff8632a8ebaf239aae04e6a hermes-engine: 9e868dc7be781364296d6ee2f56d0c1a9ef0bb11 - NitroModules: fdc6fcf8f397091615951004ae81022b759e27bc + NitroModules: 161f76b119543ff2b097c17cc629ea1ca39e4279 OpenSSL-Universal: 6082b0bf950e5636fe0d78def171184e2b3899c2 - QuickCrypto: 1027d2c8c624b7534dcd1df7284954d26246d517 - RCT-Folly: ea9d9256ba7f9322ef911169a9f696e5857b9e17 + QuickCrypto: a0b0541abc0b2594e228fe753bb25458b450afab + RCT-Folly: 7b4f73a92ad9571b9dbdb05bb30fad927fa971e1 RCTDeprecation: ebe712bb05077934b16c6bf25228bdec34b64f83 RCTRequired: ca91e5dd26b64f577b528044c962baf171c6b716 RCTTypeSafety: e7678bd60850ca5a41df9b8dc7154638cb66871f React: 4641770499c39f45d4e7cde1eba30e081f9d8a3d React-callinvoker: 4bef67b5c7f3f68db5929ab6a4d44b8a002998ea - React-Core: a68cea3e762814e60ecc3fa521c7f14c36c99245 - React-CoreModules: d81b1eaf8066add66299bab9d23c9f00c9484c7c - React-cxxreact: 984f8b1feeca37181d4e95301fcd6f5f6501c6ab + React-Core: 0a06707a0b34982efc4a556aff5dae4b22863455 + React-CoreModules: 907334e94314189c2e5eed4877f3efe7b26d85b0 + React-cxxreact: 3a1d5e8f4faa5e09be26614e9c8bbcae8d11b73d React-debug: 817160c07dc8d24d020fbd1eac7b3558ffc08964 - React-defaultsnativemodule: 18a684542f82ce1897552a1c4b847be414c9566e - React-domnativemodule: 90bdd4ec3ab38c47cfc3461c1e9283a8507d613f - React-Fabric: f6dade7007533daeb785ba5925039d83f343be4b - React-FabricComponents: b0655cc3e1b5ae12a4a1119aa7d8308f0ad33520 - React-FabricImage: 9b157c4c01ac2bf433f834f0e1e5fe234113a576 + React-defaultsnativemodule: 814830ccbc3fb08d67d0190e63b179ee4098c67b + React-domnativemodule: 270acf94bd0960b026bc3bfb327e703665d27fb4 + React-Fabric: 64586dc191fc1c170372a638b8e722e4f1d0a09b + React-FabricComponents: b0ebd032387468ea700574c581b139f57a7497fb + React-FabricImage: 81f0e0794caf25ad1224fa406d288fbc1986607f React-featureflags: f2792b067a351d86fdc7bec23db3b9a2f2c8d26c - React-featureflagsnativemodule: 742a8325b3c821d2a1ca13a6d2a0fc72d04555e0 - React-graphics: 68969e4e49d73f89da7abef4116c9b5f466aa121 - React-hermes: ac0bcba26a5d288ebc99b500e1097da2d0297ddf - React-idlecallbacksnativemodule: d61d9c9816131bf70d3d80cd04889fc625ee523f - React-ImageManager: e906eec93a9eb6102a06576b89d48d80a4683020 - React-jserrorhandler: ac5dde01104ff444e043cad8f574ca02756e20d6 - React-jsi: 496fa2b9d63b726aeb07d0ac800064617d71211d - React-jsiexecutor: dd22ab48371b80f37a0a30d0e8915b6d0f43a893 - React-jsinspector: 4629ac376f5765e684d19064f2093e55c97fd086 - React-jsitracing: 7a1c9cd484248870cf660733cd3b8114d54c035f - React-logger: c4052eb941cca9a097ef01b59543a656dc088559 - React-Mapbuffer: 33546a3ebefbccb8770c33a1f8a5554fa96a54de - React-microtasksnativemodule: d80ff86c8902872d397d9622f1a97aadcc12cead - react-native-quick-base64: 17dc4b8daee50e680d5f57cc3ee6773b6ee0110a - react-native-safe-area-context: 60695fadbcee6ab51b28b835abb10ea983dbe181 + React-featureflagsnativemodule: 0d7091ae344d6160c0557048e127897654a5c00f + React-graphics: cbebe910e4a15b65b0bff94a4d3ed278894d6386 + React-hermes: ec18c10f5a69d49fb9b5e17ae95494e9ea13d4d3 + React-idlecallbacksnativemodule: 6b84add48971da9c40403bd1860d4896462590f2 + React-ImageManager: f2a4c01c2ccb2193e60a20c135da74c7ca4d36f2 + React-jserrorhandler: 61d205b5a7cbc57fed3371dd7eed48c97f49fc64 + React-jsi: 95f7676103137861b79b0f319467627bcfa629ee + React-jsiexecutor: 41e0fe87cda9ea3970ffb872ef10f1ff8dbd1932 + React-jsinspector: 15578208796723e5c6f39069b6e8bf36863ef6e2 + React-jsitracing: 3758cdb155ea7711f0e77952572ea62d90c69f0b + React-logger: dbca7bdfd4aa5ef69431362bde6b36d49403cb20 + React-Mapbuffer: 6efad4a606c1fae7e4a93385ee096681ef0300dc + React-microtasksnativemodule: a645237a841d733861c70b69908ab4a1707b52ad + react-native-quick-base64: 5c67f3f20cf7878e9deb449188b432de897d212c + react-native-safe-area-context: 511649fbcdb7b88d29660aa5c0936b3cd03c935d React-nativeconfig: 8efdb1ef1e9158c77098a93085438f7e7b463678 - React-NativeModulesApple: cebca2e5320a3d66e123cade23bd90a167ffce5e - React-perflogger: 72e653eb3aba9122f9e57cf012d22d2486f33358 - React-performancetimeline: cd6a9374a72001165995d2ab632f672df04076dc + React-NativeModulesApple: 958d4f6c5c2ace4c0f427cf7ef82e28ae6538a22 + React-perflogger: 9b4f13c0afe56bc7b4a0e93ec74b1150421ee22d + React-performancetimeline: 359db1cb889aa0282fafc5838331b0987c4915a9 React-RCTActionSheet: aacf2375084dea6e7c221f4a727e579f732ff342 - React-RCTAnimation: 395ab53fd064dff81507c15efb781c8684d9a585 - React-RCTAppDelegate: 345a6f1b82abc578437df0ce7e9c48740eca827c - React-RCTBlob: 13311e554c1a367de063c10ee7c5e6573b2dd1d6 - React-RCTFabric: 007b1a98201cc49b5bc6e1417d7fe3f6fc6e2b78 - React-RCTImage: 1b1f914bcc12187c49ba5d949dac38c2eb9f5cc8 - React-RCTLinking: 4ac7c42beb65e36fba0376f3498f3cd8dd0be7fa - React-RCTNetwork: 938902773add4381e84426a7aa17a2414f5f94f7 - React-RCTSettings: e848f1ba17a7a18479cf5a31d28145f567da8223 - React-RCTText: 7e98fafdde7d29e888b80f0b35544e0cb07913cf - React-RCTVibration: cd7d80affd97dc7afa62f9acd491419558b64b78 + React-RCTAnimation: d8c82deebebe3aaf7a843affac1b57cb2dc073d4 + React-RCTAppDelegate: 1774aa421a29a41a704ecaf789811ef73c4634b6 + React-RCTBlob: 70a58c11a6a3500d1a12f2e51ca4f6c99babcff8 + React-RCTFabric: 731cda82aed592aacce2d32ead69d78cde5d9274 + React-RCTImage: 5e9d655ba6a790c31e3176016f9b47fd0978fbf0 + React-RCTLinking: 2a48338252805091f7521eaf92687206401bdf2a + React-RCTNetwork: 0c1282b377257f6b1c81934f72d8a1d0c010e4c3 + React-RCTSettings: f757b679a74e5962be64ea08d7865a7debd67b40 + React-RCTText: e7d20c490b407d3b4a2daa48db4bcd8ec1032af2 + React-RCTVibration: 8228e37144ca3122a91f1de16ba8e0707159cfec React-rendererconsistency: b4917053ecbaa91469c67a4319701c9dc0d40be6 - React-rendererdebug: aa181c36dd6cf5b35511d1ed875d6638fd38f0ec + React-rendererdebug: 81becbc8852b38d9b1b68672aa504556481330d5 React-rncore: 120d21715c9b4ba8f798bffe986cb769b988dd74 - React-RuntimeApple: d033becbbd1eba6f9f6e3af6f1893030ce203edd - React-RuntimeCore: 38af280bb678e66ba000a3c3d42920b2a138eebb + React-RuntimeApple: 52ed0e9e84a7c2607a901149fb13599a3c057655 + React-RuntimeCore: ca6189d2e53d86db826e2673fe8af6571b8be157 React-runtimeexecutor: 877596f82f5632d073e121cba2d2084b76a76899 - React-RuntimeHermes: 37aad735ff21ca6de2d8450a96de1afe9f86c385 - React-runtimescheduler: 8ec34cc885281a34696ea16c4fd86892d631f38d + React-RuntimeHermes: 3b752dc5d8a1661c9d1687391d6d96acfa385549 + React-runtimescheduler: 8321bb09175ace2a4f0b3e3834637eb85bf42ebe React-timing: 331cbf9f2668c67faddfd2e46bb7f41cbd9320b9 - React-utils: ed818f19ab445000d6b5c4efa9d462449326cc9f - ReactCodegen: b53cf388615e972ad884b0bb3a5cc5669dba3622 - ReactCommon: 300d8d9c5cb1a6cd79a67cf5d8f91e4d477195f9 - RNScreens: b31a78e0ad400e1ebca64016a0d3f45cbde507eb - RNVectorIcons: 5470dab316a41f06439f84a15a51416fb7f6844d + React-utils: 54df9ada708578c8ad40d92895d6fed03e0e8a9e + ReactCodegen: 90cd5a1af9dc764219d39bc39be3306ac084c4dc + ReactCommon: bfd3600989d79bc3acbe7704161b171a1480b9fd + RNScreens: 2a0cfe88d6323d5f4e6ac3e8d033ba452724631b + RNVectorIcons: abbbb380d83c3464522fdec4ad29f33759041fdb SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Yoga: feb4910aba9742cfedc059e2b2902e22ffe9954a PODFILE CHECKSUM: 63c4c1ca9d3ee6394f468b17e19efd3e548b5357 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/example/src/tests/hash/hash_tests.ts b/example/src/tests/hash/hash_tests.ts index ec3e4393..76c5562f 100644 --- a/example/src/tests/hash/hash_tests.ts +++ b/example/src/tests/hash/hash_tests.ts @@ -33,6 +33,42 @@ test(SUITE, 'createHash with null algorithm', () => { }).to.throw(/Algorithm must be a non-empty string/); }); +test(SUITE, 'check openssl version', () => { + expect(() => { + // Create a hash to trigger OpenSSL initialization + const hash = createHash('sha256'); + + // Get OpenSSL version directly from the hash object + const version = hash.getOpenSSLVersion(); + console.log('OpenSSL Version:', version); + }).to.not.throw(); +}); + +test(SUITE, 'keccak256 function using provider-aware API', () => { + const { keccak256 } = require('react-native-quick-crypto'); + + // Test with a simple string + const result1 = keccak256('test'); + expect(result1.toString('hex')).to.equal('9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658'); + + // Test with empty string + const result2 = keccak256(''); + expect(result2.toString('hex')).to.equal('c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470'); + + // Test with Buffer + const result3 = keccak256(Buffer.from('hello world')); + expect(result3.toString('hex')).to.equal('47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad'); + + // Verify the result is 32 bytes (256 bits) + expect(result1.length).to.equal(32); + expect(result2.length).to.equal(32); + expect(result3.length).to.equal(32); + + // Test that it's different from SHA3-256 (they should be different) + const sha3Hash = createHash('SHA3-256').update('test').digest(); + expect(result1.toString('hex')).to.not.equal(sha3Hash.toString('hex')); +}); + // test hashing const a0 = createHash('md5').update('Test123').digest('latin1'); const a1 = createHash('sha1').update('Test123').digest('hex'); diff --git a/packages/react-native-quick-crypto/QuickCrypto.podspec b/packages/react-native-quick-crypto/QuickCrypto.podspec index bd7d6153..ef601cdf 100644 --- a/packages/react-native-quick-crypto/QuickCrypto.podspec +++ b/packages/react-native-quick-crypto/QuickCrypto.podspec @@ -102,6 +102,6 @@ Pod::Spec.new do |s| s.dependency "React-jsi" s.dependency "React-callinvoker" - s.dependency "OpenSSL-Universal" + s.dependency "OpenSSL-Universal", "3.3.3001" install_modules_dependencies(s) end diff --git a/packages/react-native-quick-crypto/cpp/hash/HybridHash.cpp b/packages/react-native-quick-crypto/cpp/hash/HybridHash.cpp index 6c790e4c..c292b982 100644 --- a/packages/react-native-quick-crypto/cpp/hash/HybridHash.cpp +++ b/packages/react-native-quick-crypto/cpp/hash/HybridHash.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -148,4 +149,44 @@ void HybridHash::setParams() { } } +std::string HybridHash::getOpenSSLVersion() { + return OpenSSL_version(OPENSSL_VERSION); +} + +std::shared_ptr HybridHash::keccak256(const std::shared_ptr& data) { + // 1. Obtain the Keccak-256 message-digest implementation from any loaded provider. + const EVP_MD* md = EVP_MD_fetch(nullptr, "KECCAK-256", nullptr); + if (!md) { + throw std::runtime_error("KECCAK-256 digest not available in the current OpenSSL build (provider not loaded?)"); + } + + // 2. Create and initialise a digest context. + EVP_MD_CTX* ctx = EVP_MD_CTX_new(); + if (!ctx) { + throw std::runtime_error("Failed to allocate EVP_MD_CTX"); + } + auto ctx_guard = std::unique_ptr(ctx, &EVP_MD_CTX_free); + + if (EVP_DigestInit_ex(ctx, md, nullptr) != 1) { + throw std::runtime_error("Failed to initialise KECCAK-256 digest"); + } + + // 3. Feed the data. + if (EVP_DigestUpdate(ctx, data->data(), data->size()) != 1) { + throw std::runtime_error("Failed to update KECCAK-256 digest"); + } + + // 4. Finalise and collect the output. + unsigned char hash[EVP_MAX_MD_SIZE]; + unsigned int out_len = 0; + if (EVP_DigestFinal_ex(ctx, hash, &out_len) != 1) { + throw std::runtime_error("Failed to finalise KECCAK-256 digest"); + } + + // 5. Move the result into a managed ArrayBuffer. + unsigned char* out_buf = new unsigned char[out_len]; + std::memcpy(out_buf, hash, out_len); + return std::make_shared(out_buf, out_len, [out_buf]() { delete[] out_buf; }); +} + } // namespace margelo::nitro::crypto diff --git a/packages/react-native-quick-crypto/cpp/hash/HybridHash.hpp b/packages/react-native-quick-crypto/cpp/hash/HybridHash.hpp index dfd519e3..88e08d88 100644 --- a/packages/react-native-quick-crypto/cpp/hash/HybridHash.hpp +++ b/packages/react-native-quick-crypto/cpp/hash/HybridHash.hpp @@ -25,6 +25,8 @@ class HybridHash : public HybridHashSpec { std::shared_ptr digest(const std::optional& encoding = std::nullopt) override; std::shared_ptr copy(const std::optional outputLength) override; std::vector getSupportedHashAlgorithms() override; + std::string getOpenSSLVersion() override; + std::shared_ptr keccak256(const std::shared_ptr& data) override; private: // Methods diff --git a/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridHashSpec.cpp b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridHashSpec.cpp index 0a8c7ab1..29f7a6ed 100644 --- a/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridHashSpec.cpp +++ b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridHashSpec.cpp @@ -19,6 +19,8 @@ namespace margelo::nitro::crypto { prototype.registerHybridMethod("digest", &HybridHashSpec::digest); prototype.registerHybridMethod("copy", &HybridHashSpec::copy); prototype.registerHybridMethod("getSupportedHashAlgorithms", &HybridHashSpec::getSupportedHashAlgorithms); + prototype.registerHybridMethod("getOpenSSLVersion", &HybridHashSpec::getOpenSSLVersion); + prototype.registerHybridMethod("keccak256", &HybridHashSpec::keccak256); }); } diff --git a/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridHashSpec.hpp b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridHashSpec.hpp index 75c50367..bf8e2a72 100644 --- a/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridHashSpec.hpp +++ b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridHashSpec.hpp @@ -61,6 +61,8 @@ namespace margelo::nitro::crypto { virtual std::shared_ptr digest(const std::optional& encoding) = 0; virtual std::shared_ptr copy(std::optional outputLength) = 0; virtual std::vector getSupportedHashAlgorithms() = 0; + virtual std::string getOpenSSLVersion() = 0; + virtual std::shared_ptr keccak256(const std::shared_ptr& data) = 0; protected: // Hybrid Setup diff --git a/packages/react-native-quick-crypto/src/hash.ts b/packages/react-native-quick-crypto/src/hash.ts index e8b5e593..e5f9861a 100644 --- a/packages/react-native-quick-crypto/src/hash.ts +++ b/packages/react-native-quick-crypto/src/hash.ts @@ -10,12 +10,20 @@ class HashUtils { public static getSupportedHashAlgorithms(): string[] { return this.native.getSupportedHashAlgorithms(); } + public static keccak256(data: BinaryLike): Buffer { + const nativeDigest = this.native.keccak256(binaryLikeToArrayBuffer(data, 'utf8')); + return Buffer.from(nativeDigest); + } } export function getHashes() { return HashUtils.getSupportedHashAlgorithms(); } +export function keccak256(data: BinaryLike): Buffer { + return HashUtils.keccak256(data); +} + interface HashOptions extends TransformOptions { /** * For XOF hash functions such as `shake256`, the @@ -157,6 +165,25 @@ class Hash extends Stream.Transform { return hash; } + /** + * Returns the OpenSSL version string + * @since v1.0.0 + */ + getOpenSSLVersion(): string { + return this.native.getOpenSSLVersion(); + } + + /** + * Computes KECCAK-256 hash of the provided data using OpenSSL's provider-aware API + * @since v1.0.0 + * @param data The data to hash + * @returns Buffer containing the KECCAK-256 hash + */ + keccak256(data: BinaryLike): Buffer { + const nativeDigest = this.native.keccak256(binaryLikeToArrayBuffer(data, 'utf8')); + return Buffer.from(nativeDigest); + } + // stream interface _transform( chunk: BinaryLike, @@ -205,4 +232,5 @@ export function createHash(algorithm: string, options?: HashOptions): Hash { export const hashExports = { createHash, getHashes, + keccak256, }; diff --git a/packages/react-native-quick-crypto/src/specs/hash.nitro.ts b/packages/react-native-quick-crypto/src/specs/hash.nitro.ts index eaaa2bd0..7060fbd8 100644 --- a/packages/react-native-quick-crypto/src/specs/hash.nitro.ts +++ b/packages/react-native-quick-crypto/src/specs/hash.nitro.ts @@ -6,4 +6,6 @@ export interface Hash extends HybridObject<{ ios: 'c++'; android: 'c++' }> { digest(encoding?: string): ArrayBuffer; copy(outputLength?: number): Hash; getSupportedHashAlgorithms(): string[]; + getOpenSSLVersion(): string; + keccak256(data: ArrayBuffer): ArrayBuffer; }