Skip to content

Commit 054aefb

Browse files
committed
fix: hash string passing
1 parent 90f8af7 commit 054aefb

8 files changed

Lines changed: 126 additions & 11 deletions

File tree

docs/implementation-coverage.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ These algorithms provide quantum-resistant cryptography.
253253
*`subtle.decapsulateKey(decapsulationAlgorithm, decapsulationKey, ciphertext, sharedKeyAlgorithm, extractable, usages)`
254254
*`subtle.decrypt(algorithm, key, data)`
255255
* 🚧 `subtle.deriveBits(algorithm, baseKey, length)`
256-
* `subtle.deriveKey(algorithm, baseKey, derivedKeyAlgorithm, extractable, keyUsages)`
256+
* 🚧 `subtle.deriveKey(algorithm, baseKey, derivedKeyAlgorithm, extractable, keyUsages)`
257257
* 🚧 `subtle.digest(algorithm, data)`
258258
*`subtle.encapsulateBits(encapsulationAlgorithm, encapsulationKey)`
259259
*`subtle.encapsulateKey(encapsulationAlgorithm, encapsulationKey, sharedKeyAlgorithm, extractable, usages)`
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import rnqc from 'react-native-quick-crypto';
2+
import { sha256 } from '@noble/hashes/sha2';
3+
// @ts-expect-error - crypto-browserify is not typed
4+
import browserify from 'crypto-browserify';
5+
import type { BenchFn } from '../../types/benchmarks';
6+
import { Bench } from 'tinybench';
7+
8+
// Generate test data of different sizes using repeating pattern
9+
const generateString = (sizeInMB: number): string => {
10+
const chunk =
11+
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
12+
const bytesPerMB = 1024 * 1024;
13+
const totalBytes = Math.floor(sizeInMB * bytesPerMB);
14+
const repeatCount = Math.ceil(totalBytes / chunk.length);
15+
return chunk.repeat(repeatCount).substring(0, totalBytes);
16+
};
17+
18+
// Pre-generate test data (8MB as reported in issue)
19+
const text100KB = generateString(0.1);
20+
const text1MB = generateString(1);
21+
const text8MB = generateString(8);
22+
23+
const hash_sha256_8mb: BenchFn = () => {
24+
const bench = new Bench({
25+
name: 'hash sha256 8MB string',
26+
time: 10,
27+
iterations: 1,
28+
warmupIterations: 0,
29+
});
30+
31+
bench
32+
.add('rnqc', () => {
33+
const hash = rnqc.createHash('sha256');
34+
hash.update(text8MB);
35+
hash.digest('hex');
36+
})
37+
.add('@noble/hashes/sha256', () => {
38+
sha256(text8MB);
39+
})
40+
.add('browserify', () => {
41+
const hash = browserify.createHash('sha256');
42+
hash.update(text8MB);
43+
hash.digest('hex');
44+
});
45+
46+
return bench;
47+
};
48+
49+
const hash_sha256_1mb: BenchFn = () => {
50+
const bench = new Bench({
51+
name: 'hash sha256 1MB string',
52+
time: 50,
53+
iterations: 2,
54+
warmupIterations: 0,
55+
});
56+
57+
bench
58+
.add('rnqc', () => {
59+
const hash = rnqc.createHash('sha256');
60+
hash.update(text1MB);
61+
hash.digest('hex');
62+
})
63+
.add('@noble/hashes/sha256', () => {
64+
sha256(text1MB);
65+
})
66+
.add('browserify', () => {
67+
const hash = browserify.createHash('sha256');
68+
hash.update(text1MB);
69+
hash.digest('hex');
70+
});
71+
72+
return bench;
73+
};
74+
75+
const hash_sha256_100kb: BenchFn = () => {
76+
const bench = new Bench({
77+
name: 'hash sha256 100KB string',
78+
iterations: 5,
79+
});
80+
81+
bench
82+
.add('rnqc', () => {
83+
const hash = rnqc.createHash('sha256');
84+
hash.update(text100KB);
85+
hash.digest('hex');
86+
})
87+
.add('@noble/hashes/sha256', () => {
88+
sha256(text100KB);
89+
})
90+
.add('browserify', () => {
91+
const hash = browserify.createHash('sha256');
92+
hash.update(text100KB);
93+
hash.digest('hex');
94+
});
95+
96+
bench.warmupTime = 100;
97+
return bench;
98+
};
99+
100+
export default [hash_sha256_100kb, hash_sha256_1mb, hash_sha256_8mb];

example/src/hooks/useBenchmarks.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import { useEffect, useState } from 'react';
22
import { BenchmarkSuite } from '../benchmarks/benchmarks';
33
import blake3 from '../benchmarks/blake3/blake3';
44
import ed from '../benchmarks/ed/ed25519';
5+
import hkdf from '../benchmarks/hkdf/hkdf';
6+
import hash from '../benchmarks/hash/hash';
57
import pbkdf2 from '../benchmarks/pbkdf2/pbkdf2';
68
import random from '../benchmarks/random/randomBytes';
79
import xsalsa20 from '../benchmarks/cipher/xsalsa20';
8-
import hkdf from '../benchmarks/hkdf/hkdf';
910

1011
export const useBenchmarks = (): [
1112
BenchmarkSuite[],
@@ -22,16 +23,17 @@ export const useBenchmarks = (): [
2223
useEffect(() => {
2324
const newSuites: BenchmarkSuite[] = [];
2425
newSuites.push(new BenchmarkSuite('blake3', blake3));
26+
newSuites.push(new BenchmarkSuite('cipher', xsalsa20));
2527
newSuites.push(new BenchmarkSuite('ed', ed));
2628
newSuites.push(new BenchmarkSuite('pbkdf2', pbkdf2));
29+
newSuites.push(new BenchmarkSuite('hash', hash));
2730
newSuites.push(new BenchmarkSuite('hkdf', hkdf));
2831
newSuites.push(
2932
new BenchmarkSuite('random', random, {
3033
'browserify/randombytes':
3134
'polyfilled with RNQC, so a somewhat senseless benchmark',
3235
}),
3336
);
34-
newSuites.push(new BenchmarkSuite('cipher', xsalsa20));
3537
setSuites(newSuites);
3638
}, []);
3739

packages/react-native-quick-crypto/cpp/hash/HybridHash.cpp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,21 @@ void HybridHash::createHash(const std::string& hashAlgorithmArg, const std::opti
6868
}
6969
}
7070

71-
void HybridHash::update(const std::shared_ptr<ArrayBuffer>& data) {
71+
void HybridHash::update(const std::variant<std::string, std::shared_ptr<ArrayBuffer>>& data) {
7272
if (!ctx) {
7373
throw std::runtime_error("Hash context not initialized");
7474
}
7575

76-
// Update the digest with the data
77-
if (EVP_DigestUpdate(ctx, reinterpret_cast<const uint8_t*>(data->data()), data->size()) != 1) {
78-
throw std::runtime_error("Failed to update hash digest: " + std::to_string(ERR_get_error()));
76+
if (std::holds_alternative<std::string>(data)) {
77+
const std::string& str = std::get<std::string>(data);
78+
if (EVP_DigestUpdate(ctx, reinterpret_cast<const uint8_t*>(str.data()), str.length()) != 1) {
79+
throw std::runtime_error("Failed to update hash digest: " + std::to_string(ERR_get_error()));
80+
}
81+
} else {
82+
const std::shared_ptr<ArrayBuffer>& buffer = std::get<std::shared_ptr<ArrayBuffer>>(data);
83+
if (EVP_DigestUpdate(ctx, reinterpret_cast<const uint8_t*>(buffer->data()), buffer->size()) != 1) {
84+
throw std::runtime_error("Failed to update hash digest: " + std::to_string(ERR_get_error()));
85+
}
7986
}
8087
}
8188

packages/react-native-quick-crypto/cpp/hash/HybridHash.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class HybridHash : public HybridHashSpec {
2121
public:
2222
// Methods
2323
void createHash(const std::string& algorithm, const std::optional<double> outputLength) override;
24-
void update(const std::shared_ptr<ArrayBuffer>& data) override;
24+
void update(const std::variant<std::string, std::shared_ptr<ArrayBuffer>>& data) override;
2525
std::shared_ptr<ArrayBuffer> digest(const std::optional<std::string>& encoding = std::nullopt) override;
2626
std::shared_ptr<margelo::nitro::crypto::HybridHashSpec> copy(const std::optional<double> outputLength) override;
2727
std::vector<std::string> getSupportedHashAlgorithms() override;

packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridHashSpec.hpp

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/react-native-quick-crypto/src/hash.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,12 @@ class Hash extends Stream.Transform {
9898
const defaultEncoding: Encoding = 'utf8';
9999
inputEncoding = inputEncoding ?? defaultEncoding;
100100

101-
this.native.update(binaryLikeToArrayBuffer(data, inputEncoding));
101+
// OPTIMIZED PATH: Pass UTF-8 strings directly to native without conversion
102+
if (typeof data === 'string' && inputEncoding === 'utf8') {
103+
this.native.update(data);
104+
} else {
105+
this.native.update(binaryLikeToArrayBuffer(data, inputEncoding));
106+
}
102107

103108
return this; // to support chaining syntax createHash().update().digest()
104109
}

packages/react-native-quick-crypto/src/specs/hash.nitro.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { HybridObject } from 'react-native-nitro-modules';
22

33
export interface Hash extends HybridObject<{ ios: 'c++'; android: 'c++' }> {
44
createHash(algorithm: string, outputLength?: number): void;
5-
update(data: ArrayBuffer): void;
5+
update(data: ArrayBuffer | string): void;
66
digest(encoding?: string): ArrayBuffer;
77
copy(outputLength?: number): Hash;
88
getSupportedHashAlgorithms(): string[];

0 commit comments

Comments
 (0)