diff --git a/docs/implementation-coverage.md b/docs/implementation-coverage.md index fd017f54..092e37a5 100644 --- a/docs/implementation-coverage.md +++ b/docs/implementation-coverage.md @@ -253,7 +253,7 @@ These algorithms provide quantum-resistant cryptography. * ❌ `subtle.decapsulateKey(decapsulationAlgorithm, decapsulationKey, ciphertext, sharedKeyAlgorithm, extractable, usages)` * ✅ `subtle.decrypt(algorithm, key, data)` * 🚧 `subtle.deriveBits(algorithm, baseKey, length)` - * ❌ `subtle.deriveKey(algorithm, baseKey, derivedKeyAlgorithm, extractable, keyUsages)` + * 🚧 `subtle.deriveKey(algorithm, baseKey, derivedKeyAlgorithm, extractable, keyUsages)` * 🚧 `subtle.digest(algorithm, data)` * ❌ `subtle.encapsulateBits(encapsulationAlgorithm, encapsulationKey)` * ❌ `subtle.encapsulateKey(encapsulationAlgorithm, encapsulationKey, sharedKeyAlgorithm, extractable, usages)` diff --git a/example/src/benchmarks/benchmarks.ts b/example/src/benchmarks/benchmarks.ts index 012b7af4..fc8f2db4 100644 --- a/example/src/benchmarks/benchmarks.ts +++ b/example/src/benchmarks/benchmarks.ts @@ -28,13 +28,13 @@ export class BenchmarkSuite { async run() { this.results = []; - const promises = this.benchmarks.map(async benchFn => { + // Run benchmarks sequentially to avoid timing interference + for (const benchFn of this.benchmarks) { const b = await benchFn(); await b.run(); this.processResults(b); - this.state = 'done'; - }); - await Promise.all(promises); + } + this.state = 'done'; } processResults = (b: Bench): void => { diff --git a/example/src/benchmarks/cipher/cipher.ts b/example/src/benchmarks/cipher/cipher.ts new file mode 100644 index 00000000..0d3f741e --- /dev/null +++ b/example/src/benchmarks/cipher/cipher.ts @@ -0,0 +1,45 @@ +import rnqc from 'react-native-quick-crypto'; +// @ts-expect-error - crypto-browserify is not typed +import browserify from 'crypto-browserify'; +import { gcm } from '@noble/ciphers/aes.js'; +import type { BenchFn } from '../../types/benchmarks'; +import { Bench } from 'tinybench'; +import { buffer1MB } from '../testData'; + +// Generate a key for AES-256-GCM +const key = rnqc.randomBytes(32); +const iv = rnqc.randomBytes(12); + +// @noble requires Uint8Array +const nobleKey = new Uint8Array(key); +const nobleIv = new Uint8Array(iv); + +const cipher_aes256gcm_1mb_buffer: BenchFn = () => { + const bench = new Bench({ + name: 'cipher aes256gcm 1MB Buffer', + iterations: 1, + warmupIterations: 0, + }); + + const nobleData = new Uint8Array(buffer1MB); + + bench + .add('rnqc', () => { + const cipher = rnqc.createCipheriv('aes-256-gcm', key, iv); + cipher.update(buffer1MB); + cipher.final(); + }) + .add('@noble/ciphers/aes', () => { + const cipher = gcm(nobleKey, nobleIv); + cipher.encrypt(nobleData); + }) + .add('browserify', () => { + const cipher = browserify.createCipheriv('aes-256-gcm', key, iv); + cipher.update(buffer1MB); + cipher.final(); + }); + + return bench; +}; + +export default [cipher_aes256gcm_1mb_buffer]; diff --git a/example/src/benchmarks/hash/hash.ts b/example/src/benchmarks/hash/hash.ts new file mode 100644 index 00000000..65f43eae --- /dev/null +++ b/example/src/benchmarks/hash/hash.ts @@ -0,0 +1,118 @@ +import rnqc from 'react-native-quick-crypto'; +import { sha256 } from '@noble/hashes/sha2'; +// @ts-expect-error - crypto-browserify is not typed +import browserify from 'crypto-browserify'; +import type { BenchFn } from '../../types/benchmarks'; +import { Bench } from 'tinybench'; +import { text1MB, text8MB, buffer1MB, buffer8MB } from '../testData'; + +const hash_sha256_8mb_string: BenchFn = () => { + const bench = new Bench({ + name: 'hash sha256 8MB string', + iterations: 3, + warmupIterations: 1, + time: 0, + }); + + bench + .add('rnqc', () => { + const hash = rnqc.createHash('sha256'); + hash.update(text8MB); + hash.digest('hex'); + }) + .add('@noble/hashes/sha256', () => { + sha256(text8MB); + }) + .add('browserify', () => { + const hash = browserify.createHash('sha256'); + hash.update(text8MB); + hash.digest('hex'); + }); + + return bench; +}; + +const hash_sha256_1mb_string: BenchFn = () => { + const bench = new Bench({ + name: 'hash sha256 1MB string', + iterations: 5, + warmupIterations: 2, + time: 0, + }); + + bench + .add('rnqc', () => { + const hash = rnqc.createHash('sha256'); + hash.update(text1MB); + hash.digest('hex'); + }) + .add('@noble/hashes/sha256', () => { + sha256(text1MB); + }) + .add('browserify', () => { + const hash = browserify.createHash('sha256'); + hash.update(text1MB); + hash.digest('hex'); + }); + + return bench; +}; + +const hash_sha256_8mb_buffer: BenchFn = () => { + const bench = new Bench({ + name: 'hash sha256 8MB Buffer', + iterations: 3, + warmupIterations: 1, + time: 0, + }); + + bench + .add('rnqc', () => { + const hash = rnqc.createHash('sha256'); + hash.update(buffer8MB); + hash.digest('hex'); + }) + .add('@noble/hashes/sha256', () => { + sha256(buffer8MB); + }) + .add('browserify', () => { + const hash = browserify.createHash('sha256'); + hash.update(buffer8MB); + hash.digest('hex'); + }); + + return bench; +}; + +const hash_sha256_1mb_buffer: BenchFn = () => { + const bench = new Bench({ + name: 'hash sha256 1MB Buffer', + iterations: 5, + warmupIterations: 2, + time: 0, + }); + + bench + .add('rnqc', () => { + const hash = rnqc.createHash('sha256'); + hash.update(buffer1MB); + hash.digest('hex'); + }) + .add('@noble/hashes/sha256', () => { + sha256(buffer1MB); + }) + .add('browserify', () => { + const hash = browserify.createHash('sha256'); + hash.update(buffer1MB); + hash.digest('hex'); + }); + + return bench; +}; + +export default [ + hash_sha256_1mb_string, + hash_sha256_1mb_buffer, + hash_sha256_8mb_string, + hash_sha256_8mb_buffer, +]; diff --git a/example/src/benchmarks/hmac/hmac.ts b/example/src/benchmarks/hmac/hmac.ts new file mode 100644 index 00000000..e998e3bf --- /dev/null +++ b/example/src/benchmarks/hmac/hmac.ts @@ -0,0 +1,121 @@ +import rnqc from 'react-native-quick-crypto'; +// @ts-expect-error - crypto-browserify is not typed +import browserify from 'crypto-browserify'; +import { hmac } from '@noble/hashes/hmac'; +import { sha256 } from '@noble/hashes/sha2'; +import type { BenchFn } from '../../types/benchmarks'; +import { Bench } from 'tinybench'; +import { text1MB, text8MB, buffer1MB, buffer8MB } from '../testData'; + +const hmacKey = 'test-key-for-hmac-benchmarks'; + +const hmac_sha256_8mb_string: BenchFn = () => { + const bench = new Bench({ + name: 'hmac sha256 8MB string', + iterations: 3, + warmupIterations: 1, + time: 0, + }); + + bench + .add('rnqc', () => { + const h = rnqc.createHmac('sha256', hmacKey); + h.update(text8MB); + h.digest('hex'); + }) + .add('@noble/hashes/hmac', () => { + hmac(sha256, hmacKey, text8MB); + }) + .add('browserify', () => { + const h = browserify.createHmac('sha256', hmacKey); + h.update(text8MB); + h.digest('hex'); + }); + + return bench; +}; + +const hmac_sha256_1mb_string: BenchFn = () => { + const bench = new Bench({ + name: 'hmac sha256 1MB string', + iterations: 5, + warmupIterations: 2, + time: 0, + }); + + bench + .add('rnqc', () => { + const h = rnqc.createHmac('sha256', hmacKey); + h.update(text1MB); + h.digest('hex'); + }) + .add('@noble/hashes/hmac', () => { + hmac(sha256, hmacKey, text1MB); + }) + .add('browserify', () => { + const h = browserify.createHmac('sha256', hmacKey); + h.update(text1MB); + h.digest('hex'); + }); + + return bench; +}; + +const hmac_sha256_8mb_buffer: BenchFn = () => { + const bench = new Bench({ + name: 'hmac sha256 8MB Buffer', + iterations: 3, + warmupIterations: 1, + time: 0, + }); + + bench + .add('rnqc', () => { + const h = rnqc.createHmac('sha256', hmacKey); + h.update(buffer8MB); + h.digest('hex'); + }) + .add('@noble/hashes/hmac', () => { + hmac(sha256, hmacKey, buffer8MB); + }) + .add('browserify', () => { + const h = browserify.createHmac('sha256', hmacKey); + h.update(buffer8MB); + h.digest('hex'); + }); + + return bench; +}; + +const hmac_sha256_1mb_buffer: BenchFn = () => { + const bench = new Bench({ + name: 'hmac sha256 1MB Buffer', + iterations: 5, + warmupIterations: 2, + time: 0, + }); + + bench + .add('rnqc', () => { + const h = rnqc.createHmac('sha256', hmacKey); + h.update(buffer1MB); + h.digest('hex'); + }) + .add('@noble/hashes/hmac', () => { + hmac(sha256, hmacKey, buffer1MB); + }) + .add('browserify', () => { + const h = browserify.createHmac('sha256', hmacKey); + h.update(buffer1MB); + h.digest('hex'); + }); + + return bench; +}; + +export default [ + hmac_sha256_1mb_string, + hmac_sha256_1mb_buffer, + hmac_sha256_8mb_string, + hmac_sha256_8mb_buffer, +]; diff --git a/example/src/benchmarks/testData.ts b/example/src/benchmarks/testData.ts new file mode 100644 index 00000000..d4561844 --- /dev/null +++ b/example/src/benchmarks/testData.ts @@ -0,0 +1,20 @@ +// Shared test data for benchmarks +// Generate test data of different sizes using repeating pattern +const generateString = (sizeInMB: number): string => { + const chunk = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + const bytesPerMB = 1024 * 1024; + const totalBytes = Math.floor(sizeInMB * bytesPerMB); + const repeatCount = Math.ceil(totalBytes / chunk.length); + return chunk.repeat(repeatCount).substring(0, totalBytes); +}; + +// Pre-generate test data once for all benchmarks +export const text100KB = generateString(0.1); +export const text1MB = generateString(1); +export const text8MB = generateString(8); + +// Pre-generate Buffer versions for comparison +export const buffer100KB = Buffer.from(text100KB); +export const buffer1MB = Buffer.from(text1MB); +export const buffer8MB = Buffer.from(text8MB); diff --git a/example/src/components/BenchmarkResultItem.tsx b/example/src/components/BenchmarkResultItem.tsx index da5e003f..a39a946d 100644 --- a/example/src/components/BenchmarkResultItem.tsx +++ b/example/src/components/BenchmarkResultItem.tsx @@ -24,12 +24,36 @@ export const BenchmarkResultItemHeader: React.FC = () => { export const BenchmarkResultItem: React.FC = ({ result, }: BenchmarkResultItemProps) => { + // Check if benchmark errored out + const usHasError = result.us?.error !== undefined; + const themHasError = result.them?.error !== undefined; + + if (usHasError || themHasError) { + return ( + + + {result.benchName} + + + error + + {usHasError ? 'rnqc failed' : ''} + {usHasError && themHasError ? ' / ' : ''} + {themHasError ? `${result.challenger} failed` : ''} + + + + ); + } + + const hasComparison = result.them !== undefined; + const rows = ['throughput', 'latency'].map((key, i) => { const us = result.us![key as Key].mean; - const them = result.them![key as Key].mean; + const them = hasComparison ? result.them![key as Key].mean : 0; const comparison = key === 'throughput' ? us > them : us < them; const places = key === 'throughput' ? 2 : 3; - const times = calculateTimes(us, them); + const times = hasComparison ? calculateTimes(us, them) : 0; const emoji = comparison ? '🐇' : '🐢'; const timesType = comparison ? 'faster' : 'slower'; const timesStyle = timesType === 'faster' ? styles.faster : styles.slower; @@ -41,11 +65,15 @@ export const BenchmarkResultItem: React.FC = ({ {key} {key === 'throughput' ? '(ops/s)' : '(ms)'} - - {formatNumber(times, 2, 'x')} - + {hasComparison && ( + + {formatNumber(times, 2, 'x')} + + )} {formatNumber(us, places, '')} - {formatNumber(them, places, '')} + {hasComparison && ( + {formatNumber(them, places, '')} + )} ); diff --git a/example/src/hooks/useBenchmarks.ts b/example/src/hooks/useBenchmarks.ts index cad4c55f..b2fccaa8 100644 --- a/example/src/hooks/useBenchmarks.ts +++ b/example/src/hooks/useBenchmarks.ts @@ -1,11 +1,14 @@ import { useEffect, useState } from 'react'; import { BenchmarkSuite } from '../benchmarks/benchmarks'; import blake3 from '../benchmarks/blake3/blake3'; +import cipher from '../benchmarks/cipher/cipher'; import ed from '../benchmarks/ed/ed25519'; +import hkdf from '../benchmarks/hkdf/hkdf'; +import hash from '../benchmarks/hash/hash'; +import hmac from '../benchmarks/hmac/hmac'; import pbkdf2 from '../benchmarks/pbkdf2/pbkdf2'; import random from '../benchmarks/random/randomBytes'; import xsalsa20 from '../benchmarks/cipher/xsalsa20'; -import hkdf from '../benchmarks/hkdf/hkdf'; export const useBenchmarks = (): [ BenchmarkSuite[], @@ -22,8 +25,11 @@ export const useBenchmarks = (): [ useEffect(() => { const newSuites: BenchmarkSuite[] = []; newSuites.push(new BenchmarkSuite('blake3', blake3)); + newSuites.push(new BenchmarkSuite('cipher', [...xsalsa20, ...cipher])); newSuites.push(new BenchmarkSuite('ed', ed)); newSuites.push(new BenchmarkSuite('pbkdf2', pbkdf2)); + newSuites.push(new BenchmarkSuite('hash', hash)); + newSuites.push(new BenchmarkSuite('hmac', hmac)); newSuites.push(new BenchmarkSuite('hkdf', hkdf)); newSuites.push( new BenchmarkSuite('random', random, { @@ -31,7 +37,6 @@ export const useBenchmarks = (): [ 'polyfilled with RNQC, so a somewhat senseless benchmark', }), ); - newSuites.push(new BenchmarkSuite('cipher', xsalsa20)); setSuites(newSuites); }, []); diff --git a/packages/react-native-quick-crypto/cpp/hash/HybridHash.cpp b/packages/react-native-quick-crypto/cpp/hash/HybridHash.cpp index 307c2374..e0c94088 100644 --- a/packages/react-native-quick-crypto/cpp/hash/HybridHash.cpp +++ b/packages/react-native-quick-crypto/cpp/hash/HybridHash.cpp @@ -68,14 +68,21 @@ void HybridHash::createHash(const std::string& hashAlgorithmArg, const std::opti } } -void HybridHash::update(const std::shared_ptr& data) { +void HybridHash::update(const std::variant>& data) { if (!ctx) { throw std::runtime_error("Hash context not initialized"); } - // Update the digest with the data - if (EVP_DigestUpdate(ctx, reinterpret_cast(data->data()), data->size()) != 1) { - throw std::runtime_error("Failed to update hash digest: " + std::to_string(ERR_get_error())); + if (std::holds_alternative(data)) { + const std::string& str = std::get(data); + if (EVP_DigestUpdate(ctx, reinterpret_cast(str.data()), str.length()) != 1) { + throw std::runtime_error("Failed to update hash digest: " + std::to_string(ERR_get_error())); + } + } else { + const std::shared_ptr& buffer = std::get>(data); + if (EVP_DigestUpdate(ctx, reinterpret_cast(buffer->data()), buffer->size()) != 1) { + throw std::runtime_error("Failed to update hash digest: " + std::to_string(ERR_get_error())); + } } } diff --git a/packages/react-native-quick-crypto/cpp/hash/HybridHash.hpp b/packages/react-native-quick-crypto/cpp/hash/HybridHash.hpp index 84644435..907b8dac 100644 --- a/packages/react-native-quick-crypto/cpp/hash/HybridHash.hpp +++ b/packages/react-native-quick-crypto/cpp/hash/HybridHash.hpp @@ -21,7 +21,7 @@ class HybridHash : public HybridHashSpec { public: // Methods void createHash(const std::string& algorithm, const std::optional outputLength) override; - void update(const std::shared_ptr& data) override; + void update(const std::variant>& data) override; std::shared_ptr digest(const std::optional& encoding = std::nullopt) override; std::shared_ptr copy(const std::optional outputLength) override; std::vector getSupportedHashAlgorithms() override; diff --git a/packages/react-native-quick-crypto/cpp/hmac/HybridHmac.cpp b/packages/react-native-quick-crypto/cpp/hmac/HybridHmac.cpp index 2f0e8be6..2284fe5a 100644 --- a/packages/react-native-quick-crypto/cpp/hmac/HybridHmac.cpp +++ b/packages/react-native-quick-crypto/cpp/hmac/HybridHmac.cpp @@ -60,14 +60,23 @@ void HybridHmac::createHmac(const std::string& hmacAlgorithm, const std::shared_ } } -void HybridHmac::update(const std::shared_ptr& data) { +void HybridHmac::update(const std::variant>& data) { if (!ctx) { throw std::runtime_error("HMAC context not initialized"); } - // Update HMAC with new data - if (EVP_MAC_update(ctx, reinterpret_cast(data->data()), data->size()) != 1) { - throw std::runtime_error("Failed to update HMAC: " + std::to_string(ERR_get_error())); + if (std::holds_alternative(data)) { + // Handle string: pass UTF-8 bytes directly to OpenSSL + const std::string& str = std::get(data); + if (EVP_MAC_update(ctx, reinterpret_cast(str.data()), str.length()) != 1) { + throw std::runtime_error("Failed to update HMAC: " + std::to_string(ERR_get_error())); + } + } else { + // Handle ArrayBuffer + const std::shared_ptr& buffer = std::get>(data); + if (EVP_MAC_update(ctx, reinterpret_cast(buffer->data()), buffer->size()) != 1) { + throw std::runtime_error("Failed to update HMAC: " + std::to_string(ERR_get_error())); + } } } diff --git a/packages/react-native-quick-crypto/cpp/hmac/HybridHmac.hpp b/packages/react-native-quick-crypto/cpp/hmac/HybridHmac.hpp index eaf08b6b..fdec7dd3 100644 --- a/packages/react-native-quick-crypto/cpp/hmac/HybridHmac.hpp +++ b/packages/react-native-quick-crypto/cpp/hmac/HybridHmac.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "HybridHmacSpec.hpp" @@ -19,7 +20,7 @@ class HybridHmac : public HybridHmacSpec { public: // Methods void createHmac(const std::string& algorithm, const std::shared_ptr& key) override; - void update(const std::shared_ptr& data) override; + void update(const std::variant>& data) override; std::shared_ptr digest() override; private: 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 79edd0a6..b256c242 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 @@ -21,6 +21,7 @@ namespace margelo::nitro::crypto { class HybridHashSpec; } #include #include #include +#include #include #include "HybridHashSpec.hpp" #include @@ -57,7 +58,7 @@ namespace margelo::nitro::crypto { public: // Methods virtual void createHash(const std::string& algorithm, std::optional outputLength) = 0; - virtual void update(const std::shared_ptr& data) = 0; + virtual void update(const std::variant>& data) = 0; virtual std::shared_ptr digest(const std::optional& encoding) = 0; virtual std::shared_ptr copy(std::optional outputLength) = 0; virtual std::vector getSupportedHashAlgorithms() = 0; diff --git a/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridHmacSpec.hpp b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridHmacSpec.hpp index 40af4171..7cb33575 100644 --- a/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridHmacSpec.hpp +++ b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridHmacSpec.hpp @@ -18,6 +18,7 @@ namespace NitroModules { class ArrayBuffer; } #include #include +#include namespace margelo::nitro::crypto { @@ -51,7 +52,7 @@ namespace margelo::nitro::crypto { public: // Methods virtual void createHmac(const std::string& algorithm, const std::shared_ptr& key) = 0; - virtual void update(const std::shared_ptr& data) = 0; + virtual void update(const std::variant>& data) = 0; virtual std::shared_ptr digest() = 0; protected: diff --git a/packages/react-native-quick-crypto/src/hash.ts b/packages/react-native-quick-crypto/src/hash.ts index ac121f6e..cc0f3b58 100644 --- a/packages/react-native-quick-crypto/src/hash.ts +++ b/packages/react-native-quick-crypto/src/hash.ts @@ -98,7 +98,12 @@ class Hash extends Stream.Transform { const defaultEncoding: Encoding = 'utf8'; inputEncoding = inputEncoding ?? defaultEncoding; - this.native.update(binaryLikeToArrayBuffer(data, inputEncoding)); + // OPTIMIZED PATH: Pass UTF-8 strings directly to native without conversion + if (typeof data === 'string' && inputEncoding === 'utf8') { + this.native.update(data); + } else { + this.native.update(binaryLikeToArrayBuffer(data, inputEncoding)); + } return this; // to support chaining syntax createHash().update().digest() } diff --git a/packages/react-native-quick-crypto/src/hmac.ts b/packages/react-native-quick-crypto/src/hmac.ts index bbc9a5f9..4cfab0df 100644 --- a/packages/react-native-quick-crypto/src/hmac.ts +++ b/packages/react-native-quick-crypto/src/hmac.ts @@ -54,7 +54,12 @@ class Hmac extends Stream.Transform { const defaultEncoding: Encoding = 'utf8'; inputEncoding = inputEncoding ?? defaultEncoding; - this.native.update(binaryLikeToArrayBuffer(data, inputEncoding)); + // Optimize: pass UTF-8 strings directly to C++ without conversion + if (typeof data === 'string' && inputEncoding === 'utf8') { + this.native.update(data); + } else { + this.native.update(binaryLikeToArrayBuffer(data, inputEncoding)); + } return this; // to support chaining syntax createHmac().update().digest() } 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 4f851c33..ad203972 100644 --- a/packages/react-native-quick-crypto/src/specs/hash.nitro.ts +++ b/packages/react-native-quick-crypto/src/specs/hash.nitro.ts @@ -2,7 +2,7 @@ import type { HybridObject } from 'react-native-nitro-modules'; export interface Hash extends HybridObject<{ ios: 'c++'; android: 'c++' }> { createHash(algorithm: string, outputLength?: number): void; - update(data: ArrayBuffer): void; + update(data: ArrayBuffer | string): void; digest(encoding?: string): ArrayBuffer; copy(outputLength?: number): Hash; getSupportedHashAlgorithms(): string[]; diff --git a/packages/react-native-quick-crypto/src/specs/hmac.nitro.ts b/packages/react-native-quick-crypto/src/specs/hmac.nitro.ts index 25a88f62..489e4eb9 100644 --- a/packages/react-native-quick-crypto/src/specs/hmac.nitro.ts +++ b/packages/react-native-quick-crypto/src/specs/hmac.nitro.ts @@ -2,6 +2,6 @@ import type { HybridObject } from 'react-native-nitro-modules'; export interface Hmac extends HybridObject<{ ios: 'c++'; android: 'c++' }> { createHmac(algorithm: string, key: ArrayBuffer): void; - update(data: ArrayBuffer): void; + update(data: ArrayBuffer | string): void; digest(): ArrayBuffer; }