diff --git a/.github/workflows/validate-cpp.yml b/.github/workflows/validate-cpp.yml index cd9dad45..cb4da4f5 100644 --- a/.github/workflows/validate-cpp.yml +++ b/.github/workflows/validate-cpp.yml @@ -27,10 +27,12 @@ jobs: github_token: ${{ secrets.github_token }} reporter: github-pr-review flags: --linelength=230 - targets: --recursive packages/react-native-quick-crypto/cpp packages/react-native-quick-crypto/android/src/main/cpp packages/react-native-quick-crypto/nitrogen/generated/shared/c++ + targets: --recursive packages/react-native-quick-crypto/cpp packages/react-native-quick-crypto/android/src/main/cpp filter: "-legal/copyright\ ,-readability/todo\ ,-build/namespaces\ ,-whitespace/comments\ ,-build/include_order\ + ,-whitespace/indent_namespace\ + ,-whitespace/parens\ " diff --git a/bun.lockb b/bun.lockb index 8ebdaea2..005a0446 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/docs/implementation-coverage.md b/docs/implementation-coverage.md index 6fd759c6..4e89d93d 100644 --- a/docs/implementation-coverage.md +++ b/docs/implementation-coverage.md @@ -111,8 +111,8 @@ This document attempts to describe the implementation status of Crypto APIs/Inte * ❌ `crypto.diffieHellman(options)` * ❌ `crypto.hash(algorithm, data[, outputEncoding])` * ❌ `crypto.generateKey(type, options, callback)` - * ❌ `crypto.generateKeyPair(type, options, callback)` - * ❌ `crypto.generateKeyPairSync(type, options)` + * 🚧 `crypto.generateKeyPair(type, options, callback)` + * 🚧 `crypto.generateKeyPairSync(type, options)` * ❌ `crypto.generateKeySync(type, options)` * ❌ `crypto.generatePrime(size[, options[, callback]])` * ❌ `crypto.generatePrimeSync(size[, options])` @@ -141,10 +141,10 @@ This document attempts to describe the implementation status of Crypto APIs/Inte * ❌ `crypto.secureHeapUsed()` * ❌ `crypto.setEngine(engine[, flags])` * ❌ `crypto.setFips(bool)` - * ❌ `crypto.sign(algorithm, data, key[, callback])` + * 🚧 `crypto.sign(algorithm, data, key[, callback])` * ❌ `crypto.subtle` (see below) * ❌ `crypto.timingSafeEqual(a, b)` - * ❌ `crypto.verify(algorithm, data, key, signature[, callback])` + * 🚧 `crypto.verify(algorithm, data, key, signature[, callback])` * ❌ `crypto.webcrypto` (see below) 🚧 Details below still a work in progress 🚧 @@ -162,10 +162,10 @@ This document attempts to describe the implementation status of Crypto APIs/Inte | `rsa-pss` | ❌ | | `dsa` | ❌ | | `ec` | ❌ | -| `ed25519` | ❌ | -| `ed448` | ❌ | -| `x25519` | ❌ | -| `x448` | ❌ | +| `ed25519` | ✅ | +| `ed448` | ✅ | +| `x25519` | ✅ | +| `x448` | ✅ | | `dh` | ❌ | ## `crypto.generateKeyPairSync` @@ -175,10 +175,10 @@ This document attempts to describe the implementation status of Crypto APIs/Inte | `rsa-pss` | ❌ | | `dsa` | ❌ | | `ec` | ❌ | -| `ed25519` | ❌ | -| `ed448` | ❌ | -| `x25519` | ❌ | -| `x448` | ❌ | +| `ed25519` | ✅ | +| `ed448` | ✅ | +| `x25519` | ✅ | +| `x448` | ✅ | | `dh` | ❌ | ## `crypto.generateKeySync` @@ -187,6 +187,25 @@ This document attempts to describe the implementation status of Crypto APIs/Inte | `aes` | ❌ | | `hmac` | ❌ | +## `crypto.sign` +| Algorithm | Status | +| --------- | :----: | +| `RSASSA-PKCS1-v1_5` | | +| `RSA-PSS` | | +| `ECDSA` | | +| `Ed25519` | ✅ | +| `Ed448` | ✅ | +| `HMAC` | | + +## `crypto.verify` +| Algorithm | Status | +| --------- | :----: | +| `RSASSA-PKCS1-v1_5` | | +| `RSA-PSS` | | +| `ECDSA` | | +| `Ed25519` | ✅ | +| `Ed448` | ✅ | +| `HMAC` | | # `WebCrypto` diff --git a/example/index.ts b/example/index.ts index 9167e9b7..fb144100 100644 --- a/example/index.ts +++ b/example/index.ts @@ -2,6 +2,9 @@ import { install } from 'react-native-quick-crypto'; install(); +// event-target-shim +import 'event-target-polyfill'; + // readable-stream // @ts-expect-error - although process.version is readonly, we're setting it for readable-stream global.process.version = 'v22.0.0'; diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 79feb2aa..7534179d 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -7,19 +7,21 @@ PODS: - hermes-engine (0.76.1): - hermes-engine/Pre-built (= 0.76.1) - hermes-engine/Pre-built (0.76.1) - - NitroModules (0.14.0): + - NitroModules (0.18.1): - DoubleConversion - glog - hermes-engine - RCT-Folly (= 2024.01.01.00) - RCTRequired - RCTTypeSafety + - React-callinvoker - React-Core - React-debug - React-Fabric - React-featureflags - React-graphics - React-ImageManager + - React-jsi - React-NativeModulesApple - React-RCTFabric - React-rendererdebug @@ -28,8 +30,8 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - OpenSSL-Universal (3.2.2000) - - QuickCrypto (1.0.0-beta.3): + - OpenSSL-Universal (3.3.2000) + - QuickCrypto (1.0.0-beta.5): - DoubleConversion - glog - hermes-engine @@ -1936,9 +1938,9 @@ SPEC CHECKSUMS: fmt: 10c6e61f4be25dc963c36bd73fc7b1705fe975be glog: 08b301085f15bcbb6ff8632a8ebaf239aae04e6a hermes-engine: 46f1ffbf0297f4298862068dd4c274d4ac17a1fd - NitroModules: 69a6524b390ed8ca220e15f00bcfbd2f7c24472e - OpenSSL-Universal: f8a9c4fdab7e21cb70bda471c269e86e9212439c - QuickCrypto: 452b6fe586fa5c0a93e7ccca0a619387763d5adf + NitroModules: 55f64932b4581a7d02103bc35b84c7bd3204106b + OpenSSL-Universal: b60a3702c9fea8b3145549d421fdb018e53ab7b4 + QuickCrypto: 8d76ae3a0bf60509f671193eb4ed666a80da34cb RCT-Folly: 84578c8756030547307e4572ab1947de1685c599 RCTDeprecation: fde92935b3caa6cb65cbff9fbb7d3a9867ffb259 RCTRequired: 75c6cee42d21c1530a6f204ba32ff57335d19007 diff --git a/example/package.json b/example/package.json index a1d1edcc..5e902394 100644 --- a/example/package.json +++ b/example/package.json @@ -21,6 +21,7 @@ }, "dependencies": { "@craftzdog/react-native-buffer": "6.0.5", + "@noble/curves": "^1.7.0", "@noble/hashes": "^1.5.0", "@react-navigation/bottom-tabs": "^6.6.1", "@react-navigation/native": "6.1.18", @@ -28,17 +29,19 @@ "buffer": "6.0.3", "chai": "<5.0.0", "crypto-browserify": "^3.12.0", + "event-target-polyfill": "^0.0.4", "events": "3.3.0", "react": "18.3.1", "react-native": "0.76.1", "react-native-bouncy-checkbox": "4.0.1", - "react-native-nitro-modules": "0.14.0", + "react-native-nitro-modules": "0.18.1", "react-native-quick-base64": "2.1.2", - "react-native-quick-crypto": "1.0.0-beta.5", + "react-native-quick-crypto": "workspace:*", "react-native-safe-area-context": "4.14.0", "react-native-screens": "3.35.0", "react-native-vector-icons": "^10.1.0", "readable-stream": "4.5.2", + "tinybench": "^3.0.6", "util": "0.12.5" }, "devDependencies": { @@ -86,7 +89,9 @@ "@release-it/bumper": { "out": { "file": "package.json", - "path": ["dependencies.react-native-quick-crypto"] + "path": [ + "dependencies.react-native-quick-crypto" + ] } } } diff --git a/example/src/benchmarks/benchmarks.ts b/example/src/benchmarks/benchmarks.ts index e79112ce..a0a2dbce 100644 --- a/example/src/benchmarks/benchmarks.ts +++ b/example/src/benchmarks/benchmarks.ts @@ -1,92 +1,57 @@ -import type { - BenchmarkFn, - BenchmarkResult, - Challenger, - ImportedBenchmark, - SuiteState, -} from '../types/benchmarks'; -import { calculateTimes } from './utils'; +import type { Bench } from 'tinybench'; +import type { BenchFn, BenchmarkResult, SuiteState } from '../types/benchmarks'; export class BenchmarkSuite { name: string; enabled: boolean; - benchmarks: Benchmark[]; + benchmarks: BenchFn[]; state: SuiteState; results: BenchmarkResult[] = []; + notes?: Record; - constructor(name: string) { + constructor( + name: string, + benchmarks: BenchFn[], + notes?: Record, + ) { this.name = name; this.enabled = false; this.state = 'idle'; - this.benchmarks = []; + this.benchmarks = benchmarks; this.results = []; - } - - addBenchmark(imported: ImportedBenchmark) { - this.benchmarks.push(new Benchmark(imported)); + this.notes = notes; } addResult(result: BenchmarkResult) { this.results.push(result); } - run(multiplier: number = 1) { + async run() { this.results = []; - this.benchmarks.forEach(benchmark => { - benchmark.run(this, multiplier); + const promises = this.benchmarks.map(async benchFn => { + const b = await benchFn(); + await b.run(); + this.processResults(b); + this.state = 'done'; }); + await Promise.all(promises); } -} -export class Benchmark { - name: string; // function name - runCount: number; - us?: BenchmarkFn; - them: Challenger[]; - - constructor(benchmark: ImportedBenchmark) { - this.name = benchmark.name; - this.runCount = benchmark.runCount; - this.us = benchmark.us; - this.them = benchmark.them; - } + processResults = (b: Bench): void => { + const tasks = b.tasks; + const us = tasks.find(t => t.name === 'rnqc'); + const themTasks = tasks.filter(t => t.name !== 'rnqc'); - run(suite: BenchmarkSuite, multiplier: number = 1) { - const usTime = this.timeFn(this.us!, multiplier); - this.them.forEach(them => { - const themTime = this.timeFn(them.fn, multiplier); - const type = usTime < themTime ? 'faster' : 'slower'; - const times = calculateTimes(usTime, themTime); - const result: BenchmarkResult = { + themTasks.map(them => { + const notes = this.notes?.[them.name] ?? ''; + this.addResult({ errorMsg: undefined, challenger: them.name, - notes: them.notes, - runCount: this.runCount * multiplier, - fnName: this.name, - time: themTime, - us: usTime, - type, - times, - }; - suite.addResult(result); + notes, + benchName: b.name, + them: them.result, + us: us?.result, + }); }); - } - - /** - * @returns time in ms - */ - timeFn = (fn: BenchmarkFn, multiplier: number = 1): number => { - // warm up imports, etc. - fn(); - - const totalRunCount = this.runCount * multiplier; - - // do the actual benchmark - const start = performance.now(); - for (let i = 0; i < totalRunCount; i++) { - fn(); - } - const end = performance.now(); - return end - start; }; } diff --git a/example/src/benchmarks/ed/ed25519.ts b/example/src/benchmarks/ed/ed25519.ts new file mode 100644 index 00000000..61ab6960 --- /dev/null +++ b/example/src/benchmarks/ed/ed25519.ts @@ -0,0 +1,80 @@ +import { Bench } from 'tinybench'; +import rnqc from 'react-native-quick-crypto'; +import { ed25519 as noble } from '@noble/curves/ed25519'; +import type { BenchFn } from '../../types/benchmarks'; + +const TIME_MS = 1000; +const message = 'hello world'; +const buffer = Buffer.from(message); +const ab = buffer.buffer; +const arr = new Uint8Array(buffer); + +const ed25519_sign_verify_async: BenchFn = async () => { + // rnqc setup + const ed = new rnqc.Ed('ed25519', {}); + await ed.generateKeyPair(); + + // noble setup + const noblePrivateKey = noble.utils.randomPrivateKey(); + const noblePublicKey = noble.getPublicKey(noblePrivateKey); + + const bench = new Bench({ + name: 'ed25519 sign/verify (async)', + time: TIME_MS, + }); + + bench.add('rnqc', async () => { + const signature = await ed.sign(ab); + const verified = await ed.verify(signature, ab); + if (!verified) { + throw new Error('Signature verification failed'); + } + }); + + bench.add('@noble/curves/ed25519', () => { + const signature = noble.sign(arr, noblePrivateKey); + const verified = noble.verify(signature, arr, noblePublicKey); + if (!verified) { + throw new Error('Signature verification failed'); + } + }); + + bench.warmupTime = 100; + return bench; +}; + +const ed25519_sign_verify_sync: BenchFn = () => { + // rnqc setup + const ed = new rnqc.Ed('ed25519', {}); + ed.generateKeyPairSync(); + + // noble setup + const noblePrivateKey = noble.utils.randomPrivateKey(); + const noblePublicKey = noble.getPublicKey(noblePrivateKey); + + const bench = new Bench({ + name: 'ed25519 sign/verify (sync)', + time: TIME_MS, + }); + + bench.add('rnqc', () => { + const signature = ed.signSync(ab); + const verified = ed.verifySync(signature, ab); + if (!verified) { + throw new Error('Signature verification failed'); + } + }); + + bench.add('@noble/curves/ed25519', () => { + const signature = noble.sign(arr, noblePrivateKey); + const verified = noble.verify(signature, arr, noblePublicKey); + if (!verified) { + throw new Error('Signature verification failed'); + } + }); + + bench.warmupTime = 100; + return bench; +}; + +export default [ed25519_sign_verify_async, ed25519_sign_verify_sync]; diff --git a/example/src/benchmarks/pbkdf2/pbkdf2.ts b/example/src/benchmarks/pbkdf2/pbkdf2.ts index aab26a37..08f8af1a 100644 --- a/example/src/benchmarks/pbkdf2/pbkdf2.ts +++ b/example/src/benchmarks/pbkdf2/pbkdf2.ts @@ -3,44 +3,51 @@ import * as noble from '@noble/hashes/pbkdf2'; 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 type { ImportedBenchmark } from '../../types/benchmarks'; - -export const pbkdf2_256_1_32_async: ImportedBenchmark = { - name: 'pbkdf2_256_1_32_async', - runCount: 1000, - us: () => rnqc.pbkdf2('password', 'salt', 1, 32, 'sha256', () => {}), - them: [ - { - name: '@noble/hashes/pbkdf2', - notes: '', - fn: () => - noble.pbkdf2Async(sha256, 'password', 'salt', { c: 1, dkLen: 32 }), - }, - { - name: 'browserify/pbkdf2', - notes: '', - fn: () => - browserify.pbkdf2('password', 'salt', 1, 32, 'sha256', () => {}), - }, - ], +const TIME_MS = 1000; + +const pbkdf2_256_1_32_async: BenchFn = () => { + const bench = new Bench({ + name: 'pbkdf2 sha256 1x 32b (async)', + time: TIME_MS, + }); + + bench + .add('rnqc', () => { + rnqc.pbkdf2('password', 'salt', 1, 32, 'sha256', () => {}); + }) + .add('@noble/hashes/pbkdf2', async () => { + await noble.pbkdf2Async(sha256, 'password', 'salt', { c: 1, dkLen: 32 }); + }) + .add('browserify/pbkdf2', () => { + browserify.pbkdf2('password', 'salt', 1, 32, 'sha256', () => {}); + }); + + bench.warmupTime = 100; + return bench; }; -export const pbkdf2_256_1_32_sync: ImportedBenchmark = { - name: 'pbkdf2_256_1_32_sync', - runCount: 1000, - us: () => - rnqc.pbkdf2Sync('password', 'salt', 1, 32, 'sha256' as HashAlgorithm), - them: [ - { - name: '@noble/hashes/pbkdf2', - notes: '', - fn: () => noble.pbkdf2(sha256, 'password', 'salt', { c: 1, dkLen: 32 }), - }, - { - name: 'browserify/pbkdf2', - notes: '', - fn: () => browserify.pbkdf2Sync('password', 'salt', 1, 32, 'sha256'), - }, - ], +const pbkdf2_256_1_32_sync: BenchFn = () => { + const bench = new Bench({ + name: 'pbkdf2 sha256 1x 32b (sync)', + time: TIME_MS, + }); + + bench + .add('rnqc', () => { + rnqc.pbkdf2Sync('password', 'salt', 1, 32, 'sha256' as HashAlgorithm); + }) + .add('@noble/hashes/pbkdf2', () => { + noble.pbkdf2(sha256, 'password', 'salt', { c: 1, dkLen: 32 }); + }) + .add('browserify/pbkdf2', () => { + browserify.pbkdf2Sync('password', 'salt', 1, 32, 'sha256'); + }); + + bench.warmupTime = 100; + return bench; }; + +export default [pbkdf2_256_1_32_async, pbkdf2_256_1_32_sync]; diff --git a/example/src/benchmarks/random/randomBytes.ts b/example/src/benchmarks/random/randomBytes.ts index 9fb083ca..3b1ff3d8 100644 --- a/example/src/benchmarks/random/randomBytes.ts +++ b/example/src/benchmarks/random/randomBytes.ts @@ -1,30 +1,39 @@ import rnqc from 'react-native-quick-crypto'; // @ts-expect-error - crypto-browserify is not typed import browserify from 'crypto-browserify'; -import type { ImportedBenchmark } from '../../types/benchmarks'; - -export const randomBytes10: ImportedBenchmark = { - name: 'randomBytes10', - runCount: 10000, - us: () => rnqc.randomBytes(10), - them: [ - { - name: 'crypto-browserify', - notes: `crypto-browserify uses 'globalThis.crypto' under the hood, which on RN is this library, if polyfilled. So this benchmark doesn't make a lot of sense.`, - fn: () => browserify.randomBytes(10), - }, - ], +import type { BenchFn } from '../../types/benchmarks'; +import { Bench } from 'tinybench'; + +const TIME_MS = 1000; + +const randomBytes10: BenchFn = () => { + const bench = new Bench({ + name: 'randomBytes10', + time: TIME_MS, + }); + + bench + .add('rnqc', () => { + rnqc.randomBytes(10); + }) + .add('browserify/randombytes', () => browserify.randomBytes(10)); + + bench.warmupTime = 100; + return bench; }; -export const randomBytes1024: ImportedBenchmark = { - name: 'randomBytes1024', - runCount: 5000, - us: () => rnqc.randomBytes(1024), - them: [ - { - name: 'crypto-browserify', - notes: `crypto-browserify uses 'globalThis.crypto' under the hood, which on RN is this library, if polyfilled. So this benchmark doesn't make a lot of sense.`, - fn: () => browserify.randomBytes(1024), - }, - ], +const randomBytes1024: BenchFn = () => { + const bench = new Bench({ + name: 'randomBytes1024', + time: TIME_MS, + }); + + bench + .add('rnqc', () => rnqc.randomBytes(1024)) + .add('browserify/randombytes', () => browserify.randomBytes(1024)); + bench.warmupTime = 100; + + return bench; }; + +export default [randomBytes10, randomBytes1024]; diff --git a/example/src/components/BenchmarkItem.tsx b/example/src/components/BenchmarkItem.tsx index 2873924a..129aa627 100644 --- a/example/src/components/BenchmarkItem.tsx +++ b/example/src/components/BenchmarkItem.tsx @@ -5,11 +5,9 @@ import { StyleSheet, TouchableOpacity, ActivityIndicator, - InteractionManager, } from 'react-native'; import BouncyCheckbox from 'react-native-bouncy-checkbox'; import { useNavigation } from '@react-navigation/native'; -// import { calculateTimes, formatNumber } from '../benchmarks/utils'; import { colors } from '../styles/colors'; import type { BenchmarkSuite } from '../benchmarks/benchmarks'; import { calculateTimes, formatNumber } from '../benchmarks/utils'; @@ -17,13 +15,13 @@ import { calculateTimes, formatNumber } from '../benchmarks/utils'; type BenchmarkItemProps = { suite: BenchmarkSuite; toggle: () => void; - multiplier: number; + bumpRunCurrent: () => void; }; export const BenchmarkItem: React.FC = ({ suite, toggle, - multiplier, + bumpRunCurrent, }: BenchmarkItemProps) => { const [running, setRunning] = useState(false); const navigation = useNavigation(); @@ -35,38 +33,24 @@ export const BenchmarkItem: React.FC = ({ useEffect(() => { const run = async () => { - await waitForGc(); - suite.run(multiplier); - suite.state = 'done'; + await suite.run(); setRunning(false); + bumpRunCurrent(); }; - if (running) run(); + if (running) { + run(); + } }, [running]); - function delay(ms: number): Promise { - return new Promise(resolve => setTimeout(resolve, ms)); - } - - async function waitForGc(): Promise { - await delay(500); - return new Promise(resolve => { - requestAnimationFrame(() => { - InteractionManager.runAfterInteractions(() => { - resolve(); - }); - }); - }); - } - // results handling - const usTime = suite.results.reduce((acc, result) => { - return acc + result.us; + const usTput = suite.results.reduce((acc, result) => { + return acc + (result.us?.throughput.mean || 0); }, 0); - const themTime = suite.results.reduce((acc, result) => { - return acc + result.time; + const themTput = suite.results.reduce((acc, result) => { + return acc + (result.them?.throughput.mean || 0); }, 0); - const times = calculateTimes(usTime, themTime); - const timesStyle = usTime < themTime ? styles.faster : styles.slower; + const times = calculateTimes(usTput, themTput); + const timesStyle = usTput > themTput ? styles.faster : styles.slower; // render component return ( diff --git a/example/src/components/BenchmarkResultItem.tsx b/example/src/components/BenchmarkResultItem.tsx index dc92bd4a..da5e003f 100644 --- a/example/src/components/BenchmarkResultItem.tsx +++ b/example/src/components/BenchmarkResultItem.tsx @@ -1,17 +1,18 @@ import React from 'react'; import { View, Text, StyleSheet } from 'react-native'; import type { BenchmarkResult } from '../types/benchmarks'; -import { formatNumber } from '../benchmarks/utils'; +import { calculateTimes, formatNumber } from '../benchmarks/utils'; import { colors } from '../styles/colors'; type BenchmarkResultItemProps = { result: BenchmarkResult; }; +type Key = 'throughput' | 'latency'; + export const BenchmarkResultItemHeader: React.FC = () => { return ( -     times rnqc @@ -23,31 +24,49 @@ export const BenchmarkResultItemHeader: React.FC = () => { export const BenchmarkResultItem: React.FC = ({ result, }: BenchmarkResultItemProps) => { - const emoji = result.type === 'faster' ? '🐇' : '🐢'; - const timesType = result.type === 'faster' ? 'faster' : 'slower'; - const timesStyle = timesType === 'faster' ? styles.faster : styles.slower; + const rows = ['throughput', 'latency'].map((key, i) => { + const us = result.us![key as Key].mean; + const them = result.them![key as Key].mean; + const comparison = key === 'throughput' ? us > them : us < them; + const places = key === 'throughput' ? 2 : 3; + const times = calculateTimes(us, them); + const emoji = comparison ? '🐇' : '🐢'; + const timesType = comparison ? 'faster' : 'slower'; + const timesStyle = timesType === 'faster' ? styles.faster : styles.slower; + + return ( + + + {emoji} + + {key} {key === 'throughput' ? '(ops/s)' : '(ms)'} + + + {formatNumber(times, 2, 'x')} + + {formatNumber(us, places, '')} + {formatNumber(them, places, '')} + + + ); + }); return ( - - {emoji} - {result.fnName} - - {formatNumber(result.times, 2, 'x')} - - {formatNumber(result.us, 2, 'ms')} - {formatNumber(result.time, 2, 'ms')} + + {result.benchName} + {rows} challenger {result.challenger} - runs - {result.runCount} - - - notes - {result.notes} + {result.notes !== '' && ( + + notes + {result.notes} + + )} ); }; @@ -60,6 +79,7 @@ const styles = StyleSheet.create({ subContainer: { flexDirection: 'row', paddingHorizontal: 4, + paddingTop: 8, }, text: { flexShrink: 1, @@ -68,12 +88,14 @@ const styles = StyleSheet.create({ }, description: { flex: 3, + fontSize: 10, + alignSelf: 'flex-end', }, value: { fontSize: 10, fontFamily: 'Courier New', minWidth: 60, - textAlign: 'center', + textAlign: 'right', alignSelf: 'flex-end', }, label: { @@ -101,8 +123,10 @@ const styles = StyleSheet.create({ subValue: { flex: 2, }, - notes: { - paddingTop: 2, - flex: 5, + benchName: { + fontSize: 10, + fontWeight: 'bold', + flex: 1, + textAlign: 'left', }, }); diff --git a/example/src/hooks/useBenchmarks.ts b/example/src/hooks/useBenchmarks.ts index 16d7930b..5342f79d 100644 --- a/example/src/hooks/useBenchmarks.ts +++ b/example/src/hooks/useBenchmarks.ts @@ -1,6 +1,8 @@ -/* eslint-disable @typescript-eslint/no-require-imports */ import { useEffect, useState } from 'react'; import { BenchmarkSuite } from '../benchmarks/benchmarks'; +import ed from '../benchmarks/ed/ed25519'; +import pbkdf2 from '../benchmarks/pbkdf2/pbkdf2'; +import random from '../benchmarks/random/randomBytes'; export const useBenchmarks = (): [ BenchmarkSuite[], @@ -8,17 +10,47 @@ export const useBenchmarks = (): [ () => void, () => void, () => void, + () => void, ] => { const [suites, setSuites] = useState([]); + const [runCurrent, setRunCurrent] = useState(-1); // initial load of benchmark suites useEffect(() => { const newSuites: BenchmarkSuite[] = []; - newSuites.push(random()); - newSuites.push(pbkdf2()); + newSuites.push(new BenchmarkSuite('ed', ed)); + newSuites.push(new BenchmarkSuite('pbkdf2', pbkdf2)); + newSuites.push( + new BenchmarkSuite('random', random, { + 'browserify/randombytes': + 'polyfilled with RNQC, so a somewhat senseless benchmark', + }), + ); setSuites(newSuites); }, []); + // This jank is used to trick async functions into running synchronously + // so we run one benchmark at a time and have dedicated resources instead of + // conflicting with other benchmarks. + useEffect(() => { + if (runCurrent < 0) return; // not running benchmarks + // reset to -1 if we're past the end + if (runCurrent >= suites.length) { + setRunCurrent(-1); + return; + } + const s = suites[runCurrent]; + if (s?.enabled) { + updateSuites(suite => { + if (suite.name === s.name) { + suite.state = 'running'; + } + }); + } else { + setRunCurrent(runCurrent + 1); + } + }, [runCurrent]); + const updateSuites = (fn: (suite: BenchmarkSuite) => void) => { if (!suites.length) return; const copy = [...suites]; @@ -43,32 +75,13 @@ export const useBenchmarks = (): [ suite.enabled = false; }); - const runBenchmarks = () => - updateSuites(suite => { - if (suite.enabled && suite.state !== 'running') { - suite.state = 'running'; - } - }); - - return [suites, toggle, checkAll, clearAll, runBenchmarks]; -}; + const runBenchmarks = () => { + setRunCurrent(0); + }; -const random = () => { - const suite = new BenchmarkSuite('random'); - suite.addBenchmark(require('../benchmarks/random/randomBytes').randomBytes10); - suite.addBenchmark( - require('../benchmarks/random/randomBytes').randomBytes1024, - ); - return suite; -}; + const bumpRunCurrent = () => { + setRunCurrent(runCurrent + 1); + }; -const pbkdf2 = () => { - const suite = new BenchmarkSuite('pbkdf2'); - suite.addBenchmark( - require('../benchmarks/pbkdf2/pbkdf2').pbkdf2_256_1_32_async, - ); - suite.addBenchmark( - require('../benchmarks/pbkdf2/pbkdf2').pbkdf2_256_1_32_sync, - ); - return suite; + return [suites, toggle, checkAll, clearAll, runBenchmarks, bumpRunCurrent]; }; diff --git a/example/src/hooks/useTestsList.ts b/example/src/hooks/useTestsList.ts index b76ad333..bbe542fa 100644 --- a/example/src/hooks/useTestsList.ts +++ b/example/src/hooks/useTestsList.ts @@ -2,6 +2,7 @@ import { useState, useCallback } from 'react'; import type { TestSuites } from '../types/tests'; import { TestsContext } from '../tests/util'; +import '../tests/ed25519/ed25519_tests'; import '../tests/pbkdf2/pbkdf2_tests'; import '../tests/random/random_tests'; // import '../tests/HmacTests/HmacTests'; diff --git a/example/src/hooks/useTestsRun.ts b/example/src/hooks/useTestsRun.ts index 9e5d04c0..71e4c0a0 100644 --- a/example/src/hooks/useTestsRun.ts +++ b/example/src/hooks/useTestsRun.ts @@ -50,10 +50,10 @@ const run = ( Object.entries(suites).map(([suiteName, suite]) => { stats.suites++; - Object.entries(suite.tests).map(([testName, test]) => { + Object.entries(suite.tests).map(async ([testName, test]) => { if (!suite.value) return; try { - test(); + await test(); stats.passes++; addTestResult({ type: 'correct', diff --git a/example/src/navigators/children/BenchmarkSuitesScreen.tsx b/example/src/navigators/children/BenchmarkSuitesScreen.tsx index 7c8902ce..5ba525f8 100644 --- a/example/src/navigators/children/BenchmarkSuitesScreen.tsx +++ b/example/src/navigators/children/BenchmarkSuitesScreen.tsx @@ -1,41 +1,17 @@ -import React, { useState } from 'react'; -import { - View, - Text, - StyleSheet, - SafeAreaView, - TextInput, - FlatList, -} from 'react-native'; +import React from 'react'; +import { View, Text, StyleSheet, SafeAreaView, FlatList } from 'react-native'; import { BenchmarkItem } from '../../components/BenchmarkItem'; -import { colors } from '../../styles/colors'; import { useBenchmarks } from '../../hooks/useBenchmarks'; import { Button } from '../../components/Button'; export const BenchmarkSuitesScreen = () => { - const [suites, toggle, checkAll, clearAll, runBenchmarks] = useBenchmarks(); - const [multiplier, setMultiplier] = useState(1); + const [suites, toggle, checkAll, clearAll, runBenchmarks, bumpRunCurrent] = + useBenchmarks(); let totalCount = 0; return ( - - - run count multiplier - setMultiplier(parseInt(s, 10))} - /> - - - Each benchmark has a distinct run count. If you want to really - exercise the device, you can increase this number to multiply the run - count of each benchmark. Recommended values are 1-5. - - - { key={index.toString()} suite={item} toggle={() => toggle(item.name)} - multiplier={multiplier} + bumpRunCurrent={bumpRunCurrent} /> ); }} @@ -59,7 +35,7 @@ export const BenchmarkSuitesScreen = () => {