Skip to content

Mobile: Resolves #15287: Refactor (native) Crypto module to improve encryption performance on mobile - #15288

Merged
laurent22 merged 5 commits into
laurent22:devfrom
wh201906:wh201906/fast-crypto
May 12, 2026
Merged

Mobile: Resolves #15287: Refactor (native) Crypto module to improve encryption performance on mobile#15288
laurent22 merged 5 commits into
laurent22:devfrom
wh201906:wh201906/fast-crypto

Conversation

@wh201906

@wh201906 wh201906 commented May 7, 2026

Copy link
Copy Markdown
Contributor

This PR refactors Crypto module to use the fast buffer/string conversion feature in react-native-quick-crypto. Specifically:

  • Bump react-native-quick-crypto to 1.1.2 to use the new ab2str() API
  • Add Crypto.bufferToString(buffer: CryptoBuffer, encoding: CryptoBufferEncoding) as a wrapper to QuickCrypto.ab2str(buf: ArrayBuffer, ...) on mobile
    • For desktop, Crypto.bufferToString() tries to convert CryptoBuffer to string without copy. If CryptoBuffer is a Buffer, call Buffer.toString() directly. Otherwise, create a Buffer as a view (no copy) to the CryptoBuffer(Uint8array) then call .toString()
  • Replace Buffer.from()/toString() with QuickCrypto.binaryLikeToArrayBuffer()/Crypto.bufferToString() on mobile
    • Core of this PR. hex/utf16le/base64 encoding and decoding performance are significantly improved.
  • Replace the interface of CryptoBuffer from Uint8Array+toString() to Uint8Array<ArrayBuffer>
  • Unify the input/output data type of Crypto module to CryptoBuffer.
  • Add CryptoBufferEncoding
    • QuickCrypto doesn't accept all BufferEncoding because it doesn't normalize encoding name. Declare the names we need for better type checking.
  • Add test for Crypto.bufferToString()

wh201906 added 3 commits May 7, 2026 22:49
Add `Crypto.bufferToString()` as a wrapper to `QuickCrypto.ab2str()` on mobile
Replace `Buffer.from()`/`toString()` with `QuickCrypto.binaryLikeToArrayBuffer()`/`Crypto.bufferToString()` on mobile
Replace the interface of `CryptoBuffer` from `Uint8Array`+`toString()` to `Uint8Array<ArrayBuffer>`
Unify the input/output data type of `Crypto` module to `CryptoBuffer`
Add `CryptoBufferEncoding`
@coderabbitai coderabbitai Bot added mobile All mobile platforms performance Performance issues labels May 7, 2026
Comment thread packages/app-mobile/services/e2ee/crypto.ts Outdated
Comment thread packages/app-mobile/services/e2ee/crypto.ts
@github-actions github-actions Bot deleted a comment from coderabbitai Bot May 7, 2026
@coderabbitai coderabbitai Bot added enhancement Feature requests and code enhancements and removed mobile All mobile platforms performance Performance issues labels May 7, 2026
@github-actions github-actions Bot deleted a comment from coderabbitai Bot May 7, 2026
@personalizedrefrigerator personalizedrefrigerator linked an issue May 7, 2026 that may be closed by this pull request
@wh201906

wh201906 commented May 7, 2026

Copy link
Copy Markdown
Contributor Author

For 16MB resource file:

Metric fd72ee8 b1bb66a Improvement
Resource.fullPathForSyncUpload() total elapsed time 95.6s 64.4s 32.7%, 1.48x
Resource.decrypt() total elapsed time 100.4s 65.5s 34.8%, 1.53x
Resource.fullPathForSyncUpload() throughput 0.504 MB/s 0.747 MB/s +48.4%
Resource.decrypt() throughput 0.479 MB/s 0.734 MB/s +53.2%
EncryptionService.encrypt() elapsed time per chunk 105.5ms 8.4ms 92.0%, 12.55x
EncryptionService.decrypt() elapsed time per chunk 107.4ms 5.5ms 94.9%, 19.52x

Performance tests in cryptoTestUtils.runIntegrationTests()

Benchmark Encrypt Improvement Decrypt Improvement
string 100 chars roughly unchanged roughly unchanged
string 500k chars 4.70x 4.08x
string 1M chars 5.35x 2.09x
file 100kB 1.82x 1.93x
file 500kB 1.16x 1.80x
file 16MB 1.56x 1.60x

Log of fd72ee8, with e2ee profiling patch
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt lengthReadMs: {"avgMs":2.226,"p50Ms":2.211,"p95Ms":3.411,"maxMs":6.008}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt waitMs: {"avgMs":9.477,"p50Ms":8.024,"p95Ms":19.066,"maxMs":21.408}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt blockReadMs: {"avgMs":138.888,"p50Ms":136.828,"p95Ms":150.634,"maxMs":263.82}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt cryptoMs: {"avgMs":113.787,"p50Ms":113.638,"p95Ms":116.347,"maxMs":244.86}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt appendMs: {"avgMs":5.993,"p50Ms":5.825,"p95Ms":8.14,"maxMs":10.323}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt totalMs: {"avgMs":270.393,"p50Ms":267.301,"p95Ms":288.812,"maxMs":409.438}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload totalMs: {"avgMs":256.982,"p50Ms":259.699,"p95Ms":274.712,"maxMs":410.449}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload readMs: {"avgMs":10.501,"p50Ms":10.441,"p95Ms":14.424,"maxMs":17.902}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload waitMs: {"avgMs":12.516,"p50Ms":13.218,"p95Ms":19.378,"maxMs":21.782}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload cryptoMs: {"avgMs":108.446,"p50Ms":107.176,"p95Ms":115.864,"maxMs":256.296}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload nonceMs: {"avgMs":0.049,"p50Ms":0.048,"p95Ms":0.068,"maxMs":0.162}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload appendMs: {"avgMs":125.452,"p50Ms":129.28,"p95Ms":134.25,"maxMs":137.21}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt: 384 chunks
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] outer timings: {"uploadElapsedMs":[31011,33830,33896],"uploadTotalElapsedMs":98737,"uploadSpeedMBps":[0.516,0.473,0.472],"decryptElapsedMs":[34685,34714,34544],"decryptTotalElapsedMs":103943,"decryptSpeedMBps":[0.461,0.461,0.463]}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload: 384 chunks
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] config: {"fileSizeMb":16,"repeatCount":3,"chunkSize":131072,"tempDir":"/data/user/0/net.cozic.joplin.bench/files/tmp"}
Crypto Tests: testFilePerformance(): method: 9, count: 5, dataSize: 16000000, encryptTime: 156310.04123999178, decryptTime: 164216.71216401458, encryptTime/count: 31262.008247998358, decryptTime/count: 32843.342432802914.
Crypto Tests: testFilePerformance(): method: 9, count: 5, dataSize: 500000, encryptTime: 4748.362846985459, decryptTime: 4939.945539012551, encryptTime/count: 949.6725693970918, decryptTime/count: 987.9891078025103.
Crypto Tests: testFilePerformance(): method: 9, count: 5, dataSize: 100000, encryptTime: 1156.3557680100203, decryptTime: 1255.0003079622984, encryptTime/count: 231.27115360200406, decryptTime/count: 251.00006159245967.
Crypto Tests: testFilePerformance(): method: 9, count: 100, dataSize: 100, encryptTime: 4557.1247700452805, decryptTime: 6197.888839945197, encryptTime/count: 45.5712477004528, decryptTime/count: 61.97888839945197.
Crypto Tests: testStringPerformance(): method: 10, count: 5, dataSize: 1000000, encryptTime: 9855.156616985798, decryptTime: 8755.770616009831, encryptTime/count: 1971.0313233971597, decryptTime/count: 1751.1541232019663.
Crypto Tests: testStringPerformance(): method: 10, count: 5, dataSize: 500000, encryptTime: 4206.92307600379, decryptTime: 4936.226154997945, encryptTime/count: 841.3846152007579, decryptTime/count: 987.245230999589.
Crypto Tests: testStringPerformance(): method: 10, count: 100, dataSize: 100, encryptTime: 1787.9815349578857, decryptTime: 3009.14593192935, encryptTime/count: 17.87981534957886, decryptTime/count: 30.0914593192935.
Crypto Tests: Data could be decrypted
Crypto Tests: Could not decrypt data with an invalid password (Error: Authentication failed! Error: Cipher.final(...): Cipher final failed: error:00000000:lib(0)::reason(0))
Crypto Tests: Testing performance...
Crypto Tests: Data could be decrypted
Crypto Tests: Could not decrypt data with an invalid password (Error: Authentication failed! Error: Cipher.final(...): Cipher final failed: error:00000000:lib(0)::reason(0))
Crypto Tests: Decrypting using local data...
Crypto Tests: Data could be decrypted
Crypto Tests: Could not decrypt data with an invalid password (Error: Authentication failed! Error: Cipher.final(...): Cipher final failed: error:00000000:lib(0)::reason(0))
Crypto Tests: Running decrypt test data case base64File...
Crypto Tests: Data could be decrypted
Crypto Tests: Could not decrypt data with an invalid password (Error: Authentication failed! Error: Cipher.final(...): Cipher final failed: error:00000000:lib(0)::reason(0))
Crypto Tests: Running decrypt test data case hexKey...
Crypto Tests: Running decrypt test data case shortString...
Crypto Tests: Decrypting using known data...
Crypto Tests: Running integration tests...
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt cryptoMs: {"avgMs":95.201,"p50Ms":95.072,"p95Ms":97.781,"maxMs":102.682}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt appendMs: {"avgMs":6.009,"p50Ms":5.908,"p95Ms":8.049,"maxMs":9.983}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt totalMs: {"avgMs":243.541,"p50Ms":243.751,"p95Ms":255.905,"maxMs":402.081}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt lengthReadMs: {"avgMs":2.209,"p50Ms":2.183,"p95Ms":3.242,"maxMs":8.921}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt waitMs: {"avgMs":12.886,"p50Ms":13.785,"p95Ms":20.058,"maxMs":21.446}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt blockReadMs: {"avgMs":127.215,"p50Ms":126.002,"p95Ms":134.977,"maxMs":282.316}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload nonceMs: {"avgMs":0.045,"p50Ms":0.044,"p95Ms":0.058,"maxMs":0.11}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload appendMs: {"avgMs":110.173,"p50Ms":110.134,"p95Ms":114.427,"maxMs":161.377}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt: 384 chunks
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload totalMs: {"avgMs":233.504,"p50Ms":231.318,"p95Ms":245.968,"maxMs":390.07}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload readMs: {"avgMs":10.39,"p50Ms":10.496,"p95Ms":14.893,"maxMs":17.997}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload waitMs: {"avgMs":13.133,"p50Ms":13.578,"p95Ms":19.749,"maxMs":21.511}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload cryptoMs: {"avgMs":99.745,"p50Ms":96.845,"p95Ms":102.738,"maxMs":253.389}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] outer timings: {"uploadElapsedMs":[29811,29571,30342],"uploadTotalElapsedMs":89724,"uploadSpeedMBps":[0.537,0.541,0.527],"decryptElapsedMs":[31127,31416,31130],"decryptTotalElapsedMs":93673,"decryptSpeedMBps":[0.514,0.509,0.514]}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload: 384 chunks
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] config: {"fileSizeMb":16,"repeatCount":3,"chunkSize":131072,"tempDir":"/data/user/0/net.cozic.joplin.bench/files/tmp"}
Crypto Tests: testFilePerformance(): method: 9, count: 5, dataSize: 16000000, encryptTime: 164093.22416299582, decryptTime: 168308.77501000464, encryptTime/count: 32818.644832599166, decryptTime/count: 33661.75500200093.
Crypto Tests: testFilePerformance(): method: 9, count: 5, dataSize: 500000, encryptTime: 5269.4884610027075, decryptTime: 5472.984308987856, encryptTime/count: 1053.8976922005415, decryptTime/count: 1094.5968617975711.
Crypto Tests: testFilePerformance(): method: 9, count: 5, dataSize: 100000, encryptTime: 1182.1156920194626, decryptTime: 1219.1774619966745, encryptTime/count: 236.42313840389252, decryptTime/count: 243.8354923993349.
Crypto Tests: testFilePerformance(): method: 9, count: 100, dataSize: 100, encryptTime: 6209.121225088835, decryptTime: 4649.144082009792, encryptTime/count: 62.09121225088835, decryptTime/count: 46.49144082009792.
Crypto Tests: testStringPerformance(): method: 10, count: 5, dataSize: 1000000, encryptTime: 10482.221385002136, decryptTime: 9338.37292200327, encryptTime/count: 2096.444277000427, decryptTime/count: 1867.6745844006539.
Crypto Tests: testStringPerformance(): method: 10, count: 5, dataSize: 500000, encryptTime: 4046.5095380097628, decryptTime: 5368.984614998102, encryptTime/count: 809.3019076019525, decryptTime/count: 1073.7969229996204.
Crypto Tests: testStringPerformance(): method: 10, count: 100, dataSize: 100, encryptTime: 1815.497306972742, decryptTime: 3000.9004640877247, encryptTime/count: 18.154973069727422, decryptTime/count: 30.009004640877247.
Crypto Tests: Testing performance...
Crypto Tests: Data could be decrypted
Crypto Tests: Could not decrypt data with an invalid password (Error: Authentication failed! Error: Cipher.final(...): Cipher final failed: error:00000000:lib(0)::reason(0))
Crypto Tests: Data could be decrypted
Crypto Tests: Could not decrypt data with an invalid password (Error: Authentication failed! Error: Cipher.final(...): Cipher final failed: error:00000000:lib(0)::reason(0))
Crypto Tests: Decrypting using local data...
Crypto Tests: Data could be decrypted
Crypto Tests: Could not decrypt data with an invalid password (Error: Authentication failed! Error: Cipher.final(...): Cipher final failed: error:00000000:lib(0)::reason(0))
Crypto Tests: Running decrypt test data case base64File...
Crypto Tests: Data could be decrypted
Crypto Tests: Could not decrypt data with an invalid password (Error: Authentication failed! Error: Cipher.final(...): Cipher final failed: error:00000000:lib(0)::reason(0))
Crypto Tests: Running decrypt test data case hexKey...
Crypto Tests: Decrypting using known data...
Crypto Tests: Running decrypt test data case shortString...
Crypto Tests: Running integration tests...
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt totalMs: {"avgMs":269.823,"p50Ms":267.33,"p95Ms":287.217,"maxMs":298.102}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt lengthReadMs: {"avgMs":2.354,"p50Ms":2.337,"p95Ms":3.374,"maxMs":7.128}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt waitMs: {"avgMs":10,"p50Ms":8.385,"p95Ms":19.911,"maxMs":21.177}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt blockReadMs: {"avgMs":138.464,"p50Ms":136.825,"p95Ms":150.598,"maxMs":162.764}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt cryptoMs: {"avgMs":113.122,"p50Ms":113.452,"p95Ms":115.912,"maxMs":120.183}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt appendMs: {"avgMs":5.86,"p50Ms":5.654,"p95Ms":7.815,"maxMs":9.255}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt: 384 chunks
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload cryptoMs: {"avgMs":108.228,"p50Ms":107.035,"p95Ms":116.042,"maxMs":249.207}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload nonceMs: {"avgMs":0.05,"p50Ms":0.05,"p95Ms":0.061,"maxMs":0.119}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload appendMs: {"avgMs":123.673,"p50Ms":128.639,"p95Ms":133.668,"maxMs":248.503}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload totalMs: {"avgMs":256.052,"p50Ms":259.811,"p95Ms":274.047,"maxMs":400.868}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload readMs: {"avgMs":10.896,"p50Ms":10.788,"p95Ms":15.248,"maxMs":19.746}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload waitMs: {"avgMs":13.187,"p50Ms":13.751,"p95Ms":19.634,"maxMs":21.315}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] outer timings: {"uploadElapsedMs":[30326,34034,34051],"uploadTotalElapsedMs":98411,"uploadSpeedMBps":[0.528,0.47,0.47],"decryptElapsedMs":[34590,34422,34714],"decryptTotalElapsedMs":103726,"decryptSpeedMBps":[0.463,0.465,0.461]}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload: 384 chunks
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] config: {"fileSizeMb":16,"repeatCount":3,"chunkSize":131072,"tempDir":"/data/user/0/net.cozic.joplin.bench/files/tmp"}
Crypto Tests: testFilePerformance(): method: 9, count: 5, dataSize: 16000000, encryptTime: 163811.90393199027, decryptTime: 168017.66616497934, encryptTime/count: 32762.380786398055, decryptTime/count: 33603.53323299587.
Crypto Tests: testFilePerformance(): method: 9, count: 5, dataSize: 500000, encryptTime: 5286.299768000841, decryptTime: 5437.234617009759, encryptTime/count: 1057.2599536001683, decryptTime/count: 1087.4469234019518.
Crypto Tests: testFilePerformance(): method: 9, count: 5, dataSize: 100000, encryptTime: 1147.687770023942, decryptTime: 1260.4279229938984, encryptTime/count: 229.5375540047884, decryptTime/count: 252.0855845987797.
Crypto Tests: testFilePerformance(): method: 9, count: 100, dataSize: 100, encryptTime: 4483.201392933726, decryptTime: 6292.801998123527, encryptTime/count: 44.832013929337265, decryptTime/count: 62.928019981235266.
Crypto Tests: testStringPerformance(): method: 10, count: 5, dataSize: 1000000, encryptTime: 8193.018462985754, decryptTime: 11700.075770020485, encryptTime/count: 1638.6036925971507, decryptTime/count: 2340.015154004097.
Crypto Tests: testStringPerformance(): method: 10, count: 5, dataSize: 500000, encryptTime: 4881.197615981102, decryptTime: 4643.562768995762, encryptTime/count: 976.2395231962204, decryptTime/count: 928.7125537991524.
Crypto Tests: testStringPerformance(): method: 10, count: 100, dataSize: 100, encryptTime: 1928.4922289252281, decryptTime: 3061.172315120697, encryptTime/count: 19.284922289252282, decryptTime/count: 30.61172315120697.
Crypto Tests: Data could be decrypted
Crypto Tests: Could not decrypt data with an invalid password (Error: Authentication failed! Error: Cipher.final(...): Cipher final failed: error:00000000:lib(0)::reason(0))
Crypto Tests: Testing performance...
Crypto Tests: Data could be decrypted
Crypto Tests: Could not decrypt data with an invalid password (Error: Authentication failed! Error: Cipher.final(...): Cipher final failed: error:00000000:lib(0)::reason(0))
Crypto Tests: Decrypting using local data...
Crypto Tests: Running decrypt test data case base64File...
Crypto Tests: Could not decrypt data with an invalid password (Error: Authentication failed! Error: Cipher.final(...): Cipher final failed: error:00000000:lib(0)::reason(0))
Crypto Tests: Data could be decrypted
Crypto Tests: Could not decrypt data with an invalid password (Error: Authentication failed! Error: Cipher.final(...): Cipher final failed: error:00000000:lib(0)::reason(0))
Crypto Tests: Running decrypt test data case hexKey...
Crypto Tests: Data could be decrypted
Crypto Tests: Decrypting using known data...
Crypto Tests: Running decrypt test data case shortString...
Crypto Tests: Running integration tests...
Log of b1bb66a, with e2ee profiling patch
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt totalMs: {"avgMs":164.715,"p50Ms":162.133,"p95Ms":184.078,"maxMs":290.002}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt lengthReadMs: {"avgMs":2.175,"p50Ms":2.097,"p95Ms":3.313,"maxMs":5.614}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt waitMs: {"avgMs":12.747,"p50Ms":13.486,"p95Ms":19.644,"maxMs":21.184}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt blockReadMs: {"avgMs":138.979,"p50Ms":137.784,"p95Ms":152.539,"maxMs":265.395}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt cryptoMs: {"avgMs":5.443,"p50Ms":5.413,"p95Ms":7.274,"maxMs":11.123}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt appendMs: {"avgMs":5.349,"p50Ms":5.226,"p95Ms":7.142,"maxMs":10.214}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload cryptoMs: {"avgMs":8.245,"p50Ms":8.12,"p95Ms":11.698,"maxMs":17.238}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload nonceMs: {"avgMs":0.043,"p50Ms":0.039,"p95Ms":0.059,"maxMs":0.497}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload appendMs: {"avgMs":126.148,"p50Ms":122.012,"p95Ms":136.933,"maxMs":259.643}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt: 384 chunks
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload totalMs: {"avgMs":157.471,"p50Ms":154.537,"p95Ms":172.94,"maxMs":289.827}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload readMs: {"avgMs":10.998,"p50Ms":10.97,"p95Ms":14.722,"maxMs":17.706}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload waitMs: {"avgMs":12.018,"p50Ms":12.129,"p95Ms":19.592,"maxMs":21.574}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] outer timings: {"uploadElapsedMs":[19297,19700,21526],"uploadTotalElapsedMs":60523,"uploadSpeedMBps":[0.829,0.812,0.743],"decryptElapsedMs":[21463,19983,21916],"decryptTotalElapsedMs":63362,"decryptSpeedMBps":[0.745,0.801,0.73]}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload: 384 chunks
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] config: {"fileSizeMb":16,"repeatCount":3,"chunkSize":131072,"tempDir":"/data/user/0/net.cozic.joplin.bench/files/tmp"}
Crypto Tests: testFilePerformance(): method: 9, count: 5, dataSize: 16000000, encryptTime: 97613.14400699735, decryptTime: 97824.9134670049, encryptTime/count: 19522.62880139947, decryptTime/count: 19564.98269340098.
Crypto Tests: testFilePerformance(): method: 9, count: 5, dataSize: 500000, encryptTime: 4043.038386002183, decryptTime: 2767.2341540157795, encryptTime/count: 808.6076772004366, decryptTime/count: 553.4468308031559.
Crypto Tests: testFilePerformance(): method: 9, count: 5, dataSize: 100000, encryptTime: 635.1433840095997, decryptTime: 647.2702309936285, encryptTime/count: 127.02867680191994, decryptTime/count: 129.4540461987257.
Crypto Tests: testFilePerformance(): method: 9, count: 100, dataSize: 100, encryptTime: 2335.4160830676556, decryptTime: 2648.7669189423323, encryptTime/count: 23.354160830676555, decryptTime/count: 26.487669189423322.
Crypto Tests: testStringPerformance(): method: 10, count: 5, dataSize: 1000000, encryptTime: 1760.1959220021963, decryptTime: 4488.856077000499, encryptTime/count: 352.03918440043924, decryptTime/count: 897.7712154000998.
Crypto Tests: testStringPerformance(): method: 10, count: 5, dataSize: 500000, encryptTime: 929.6444630175829, decryptTime: 1208.59499900043, encryptTime/count: 185.92889260351657, decryptTime/count: 241.718999800086.
Crypto Tests: testStringPerformance(): method: 10, count: 100, dataSize: 100, encryptTime: 1871.8370760679245, decryptTime: 2942.4595389813185, encryptTime/count: 18.718370760679246, decryptTime/count: 29.424595389813184.
Crypto Tests: Data could be decrypted
Crypto Tests: Could not decrypt data with an invalid password (Error: Authentication failed! Error: Cipher.final(...): Cipher final failed: error:00000000:lib(0)::reason(0))
Crypto Tests: Testing performance...
Crypto Tests: Data could be decrypted
Crypto Tests: Could not decrypt data with an invalid password (Error: Authentication failed! Error: Cipher.final(...): Cipher final failed: error:00000000:lib(0)::reason(0))
Crypto Tests: Decrypting using local data...
Crypto Tests: Could not decrypt data with an invalid password (Error: Authentication failed! Error: Cipher.final(...): Cipher final failed: error:00000000:lib(0)::reason(0))
Crypto Tests: Running decrypt test data case base64File...
Crypto Tests: Data could be decrypted
Crypto Tests: Data could be decrypted
Crypto Tests: Could not decrypt data with an invalid password (Error: Authentication failed! Error: Cipher.final(...): Cipher final failed: error:00000000:lib(0)::reason(0))
Crypto Tests: Running decrypt test data case hexKey...
Crypto Tests: Testing bufferToString...
Crypto Tests: Decrypting using known data...
Crypto Tests: Running decrypt test data case shortString...
Crypto Tests: Running integration tests...
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt totalMs: {"avgMs":169.001,"p50Ms":167.53,"p95Ms":184.077,"maxMs":300.442}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt lengthReadMs: {"avgMs":2.166,"p50Ms":2.14,"p95Ms":2.956,"maxMs":7.429}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt waitMs: {"avgMs":12.447,"p50Ms":14.121,"p95Ms":19.905,"maxMs":21.797}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt blockReadMs: {"avgMs":143.618,"p50Ms":141.358,"p95Ms":153.954,"maxMs":278.353}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt cryptoMs: {"avgMs":5.569,"p50Ms":5.523,"p95Ms":7.409,"maxMs":10.945}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt appendMs: {"avgMs":5.18,"p50Ms":5.043,"p95Ms":7.031,"maxMs":9.674}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt: 384 chunks
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload totalMs: {"avgMs":169.293,"p50Ms":165.406,"p95Ms":181.064,"maxMs":313.515}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload readMs: {"avgMs":11.046,"p50Ms":10.947,"p95Ms":14.787,"maxMs":18.865}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload waitMs: {"avgMs":11.872,"p50Ms":11.794,"p95Ms":19.044,"maxMs":20.976}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload cryptoMs: {"avgMs":8.343,"p50Ms":8.15,"p95Ms":11.419,"maxMs":23.495}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload nonceMs: {"avgMs":0.042,"p50Ms":0.039,"p95Ms":0.068,"maxMs":0.112}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload appendMs: {"avgMs":137.973,"p50Ms":134.482,"p95Ms":140.583,"maxMs":276.53}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] outer timings: {"uploadElapsedMs":[21697,21664,21702],"uploadTotalElapsedMs":65063,"uploadSpeedMBps":[0.737,0.739,0.737],"decryptElapsedMs":[21835,21441,21735],"decryptTotalElapsedMs":65011,"decryptSpeedMBps":[0.733,0.746,0.736]}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload: 384 chunks
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] config: {"fileSizeMb":16,"repeatCount":3,"chunkSize":131072,"tempDir":"/data/user/0/net.cozic.joplin.bench/files/tmp"}
Crypto Tests: testFilePerformance(): method: 9, count: 5, dataSize: 16000000, encryptTime: 103210.00308401883, decryptTime: 105267.01231199503, encryptTime/count: 20642.000616803765, decryptTime/count: 21053.402462399004.
Crypto Tests: testFilePerformance(): method: 9, count: 5, dataSize: 500000, encryptTime: 4342.7768459916115, decryptTime: 2851.3825400173664, encryptTime/count: 868.5553691983223, decryptTime/count: 570.2765080034733.
Crypto Tests: testFilePerformance(): method: 9, count: 5, dataSize: 100000, encryptTime: 638.0814610123634, decryptTime: 641.0564619898796, encryptTime/count: 127.61629220247269, decryptTime/count: 128.2112923979759.
Crypto Tests: testFilePerformance(): method: 9, count: 100, dataSize: 100, encryptTime: 2255.698234975338, decryptTime: 2645.4946900457144, encryptTime/count: 22.55698234975338, decryptTime/count: 26.454946900457145.
Crypto Tests: testStringPerformance(): method: 10, count: 5, dataSize: 1000000, encryptTime: 1761.4622320085764, decryptTime: 4776.482308000326, encryptTime/count: 352.29244640171527, decryptTime/count: 955.2964616000652.
Crypto Tests: testStringPerformance(): method: 10, count: 5, dataSize: 500000, encryptTime: 918.0395370423794, decryptTime: 1205.8663869798183, encryptTime/count: 183.60790740847588, decryptTime/count: 241.17327739596368.
Crypto Tests: testStringPerformance(): method: 10, count: 100, dataSize: 100, encryptTime: 1858.1730039417744, decryptTime: 2929.1016109138727, encryptTime/count: 18.581730039417742, decryptTime/count: 29.291016109138727.
Crypto Tests: Data could be decrypted
Crypto Tests: Could not decrypt data with an invalid password (Error: Authentication failed! Error: Cipher.final(...): Cipher final failed: error:00000000:lib(0)::reason(0))
Crypto Tests: Testing performance...
Crypto Tests: Data could be decrypted
Crypto Tests: Could not decrypt data with an invalid password (Error: Authentication failed! Error: Cipher.final(...): Cipher final failed: error:00000000:lib(0)::reason(0))
Crypto Tests: Decrypting using local data...
Crypto Tests: Could not decrypt data with an invalid password (Error: Authentication failed! Error: Cipher.final(...): Cipher final failed: error:00000000:lib(0)::reason(0))
Crypto Tests: Running decrypt test data case base64File...
Crypto Tests: Data could be decrypted
Crypto Tests: Running decrypt test data case hexKey...
Crypto Tests: Data could be decrypted
Crypto Tests: Could not decrypt data with an invalid password (Error: Authentication failed! Error: Cipher.final(...): Cipher final failed: error:00000000:lib(0)::reason(0))
Crypto Tests: Testing bufferToString...
Crypto Tests: Decrypting using known data...
Crypto Tests: Running decrypt test data case shortString...
Crypto Tests: Running integration tests...
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt lengthReadMs: {"avgMs":2.435,"p50Ms":2.427,"p95Ms":3.523,"maxMs":6.57}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt waitMs: {"avgMs":11.594,"p50Ms":10.902,"p95Ms":20.244,"maxMs":21.635}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt blockReadMs: {"avgMs":152.215,"p50Ms":150.025,"p95Ms":161.084,"maxMs":300.271}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt cryptoMs: {"avgMs":5.489,"p50Ms":5.467,"p95Ms":7.241,"maxMs":9.444}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt appendMs: {"avgMs":5.324,"p50Ms":5.104,"p95Ms":7.466,"maxMs":11.755}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt totalMs: {"avgMs":177.083,"p50Ms":174.264,"p95Ms":193.083,"maxMs":331.699}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload readMs: {"avgMs":10.789,"p50Ms":10.647,"p95Ms":14.375,"maxMs":17.453}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload waitMs: {"avgMs":13.084,"p50Ms":14.092,"p95Ms":20.535,"maxMs":22.822}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload cryptoMs: {"avgMs":8.616,"p50Ms":8.306,"p95Ms":12.154,"maxMs":30.753}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload nonceMs: {"avgMs":0.043,"p50Ms":0.041,"p95Ms":0.058,"maxMs":0.187}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload appendMs: {"avgMs":143.328,"p50Ms":141.729,"p95Ms":148.752,"maxMs":281.401}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.decrypt: 384 chunks
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload totalMs: {"avgMs":175.877,"p50Ms":174.7,"p95Ms":187.808,"maxMs":311.436}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] config: {"fileSizeMb":16,"repeatCount":3,"chunkSize":131072,"tempDir":"/data/user/0/net.cozic.joplin.bench/files/tmp"}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] outer timings: {"uploadElapsedMs":[22437,22529,22631],"uploadTotalElapsedMs":67597,"uploadSpeedMBps":[0.713,0.71,0.707],"decryptElapsedMs":[22539,22735,22846],"decryptTotalElapsedMs":68120,"decryptSpeedMBps":[0.71,0.704,0.7]}
E2eeResourcePerfButton: Crypto Tests: [E2EE PERF] Resource.fullPathForSyncUpload: 384 chunks
Crypto Tests: testFilePerformance(): method: 9, count: 5, dataSize: 16000000, encryptTime: 108770.77669902146, decryptTime: 109395.55692897737, encryptTime/count: 21754.155339804292, decryptTime/count: 21879.111385795473.
Crypto Tests: testFilePerformance(): method: 9, count: 5, dataSize: 500000, encryptTime: 4785.004384979606, decryptTime: 3168.6330000013113, encryptTime/count: 957.0008769959211, decryptTime/count: 633.7266000002622.
Crypto Tests: testFilePerformance(): method: 9, count: 5, dataSize: 100000, encryptTime: 647.4569229930639, decryptTime: 645.9735379964113, encryptTime/count: 129.4913845986128, decryptTime/count: 129.19470759928225.
Crypto Tests: testFilePerformance(): method: 9, count: 100, dataSize: 100, encryptTime: 1847.7491510212421, decryptTime: 1848.5069999843836, encryptTime/count: 18.47749151021242, decryptTime/count: 18.485069999843837.
Crypto Tests: testStringPerformance(): method: 10, count: 5, dataSize: 1000000, encryptTime: 1808.025537982583, decryptTime: 4970.954539015889, encryptTime/count: 361.60510759651663, decryptTime/count: 994.1909078031779.
Crypto Tests: testStringPerformance(): method: 10, count: 5, dataSize: 500000, encryptTime: 947.6246930062771, decryptTime: 1247.5461539924145, encryptTime/count: 189.52493860125543, decryptTime/count: 249.5092307984829.
Crypto Tests: testStringPerformance(): method: 10, count: 100, dataSize: 100, encryptTime: 1735.3243770748377, decryptTime: 2813.799926891923, encryptTime/count: 17.353243770748378, decryptTime/count: 28.13799926891923.
Crypto Tests: Testing performance...
Crypto Tests: Could not decrypt data with an invalid password (Error: Authentication failed! Error: Cipher.final(...): Cipher final failed: error:00000000:lib(0)::reason(0))
Crypto Tests: Data could be decrypted
Crypto Tests: Could not decrypt data with an invalid password (Error: Authentication failed! Error: Cipher.final(...): Cipher final failed: error:00000000:lib(0)::reason(0))
Crypto Tests: Decrypting using local data...
Crypto Tests: Data could be decrypted
Crypto Tests: Running decrypt test data case base64File...
Crypto Tests: Data could be decrypted
Crypto Tests: Could not decrypt data with an invalid password (Error: Authentication failed! Error: Cipher.final(...): Cipher final failed: error:00000000:lib(0)::reason(0))
Crypto Tests: Could not decrypt data with an invalid password (Error: Authentication failed! Error: Cipher.final(...): Cipher final failed: error:00000000:lib(0)::reason(0))
Crypto Tests: Running decrypt test data case hexKey...
Crypto Tests: Data could be decrypted
Crypto Tests: Testing bufferToString...
Crypto Tests: Decrypting using known data...
Crypto Tests: Running decrypt test data case shortString...
Crypto Tests: Running integration tests...

@mrjo118

mrjo118 commented May 7, 2026

Copy link
Copy Markdown
Collaborator

Did you do any testing on Android? There are some tests which run at startup, when you start the dev build of the Android app

@personalizedrefrigerator

Copy link
Copy Markdown
Collaborator

Note: For future reference, this pull request is related to upstream contributions by @wh201906 to react-native-quick-crypto.

@wh201906

wh201906 commented May 8, 2026

Copy link
Copy Markdown
Contributor Author

Did you do any testing on Android? There are some tests which run at startup, when you start the dev build of the Android app

Yes. #15288 (comment) is based on the release build (for an accurate result) running on a real Android device, with some modifications to force enable the runCryptoIntegrationTests() with testPerformance = true

Core diff of the profiling patch
diff --git a/packages/app-mobile/utils/buildStartupTasks.ts b/packages/app-mobile/utils/buildStartupTasks.ts
index 07bd80740..0e84c4823 100644
--- a/packages/app-mobile/utils/buildStartupTasks.ts
+++ b/packages/app-mobile/utils/buildStartupTasks.ts
@@ -481,9 +481,10 @@ const buildStartupTasks = (
 		// ----------------------------------------------------------------------------
 		if (Setting.value('env') === 'dev') {
 			await runRsaIntegrationTests();
-			await runCryptoIntegrationTests();
+			// await runCryptoIntegrationTests();
 			await runOnDeviceFsDriverTests();
 		}
+		await runCryptoIntegrationTests(false, true);
 
 		// ----------------------------------------------------------------------------
 		// Keep this below to test react-native-rsa-native
diff --git a/packages/lib/services/e2ee/EncryptionService.ts b/packages/lib/services/e2ee/EncryptionService.ts
index ca21d98ca..14c4d4d3e 100644
--- a/packages/lib/services/e2ee/EncryptionService.ts
+++ b/packages/lib/services/e2ee/EncryptionService.ts
@@ -602,7 +622,10 @@ export default class EncryptionService {
 		let doneSize = 0;
 
 		while (true) {
+			const totalStart = timingNow();
+			const readStart = totalStart;
 			const block = await source.read(chunkSize);
+			const readMs = timingNow() - readStart;
 			if (!block) break;
 
 			doneSize += chunkSize;
@@ -610,13 +633,31 @@ export default class EncryptionService {
 
 			// Wait for a frame so that the app remains responsive in mobile.
 			// https://corbt.com/posts/2015/12/22/breaking-up-heavy-processing-in-react-native.html
+			const waitStart = timingNow();
 			await shim.waitForFrame();
+			const waitMs = timingNow() - waitStart;
 
+			const cryptoStart = timingNow();
 			const encrypted = await this.encrypt(method, masterKeyPlainText, block);
+			const cryptoMs = timingNow() - cryptoStart;
+
+			const nonceStart = timingNow();
 			await crypto.increaseNonce(this.encryptionNonce_);
+			const nonceMs = timingNow() - nonceStart;
 
+			const appendStart = timingNow();
 			await destination.append(padLeft(encrypted.length.toString(16), 6, '0'));
 			await destination.append(encrypted);
+			const appendMs = timingNow() - appendStart;
+
+			const totalMs = timingNow() - totalStart;
 		}
 	}
 
@@ -631,7 +672,10 @@ export default class EncryptionService {
 		let doneSize = 0;
 
 		while (true) {
+			const totalStart = timingNow();
+			const lengthReadStart = totalStart;
 			const lengthHex = await source.read(6);
+			const lengthReadMs = timingNow() - lengthReadStart;
 			if (!lengthHex) break;
 			if (lengthHex.length !== 6) throw new Error(`Invalid block size: ${lengthHex}`);
 			const length = parseInt(lengthHex, 16);
@@ -640,12 +684,30 @@ export default class EncryptionService {
 			doneSize += length;
 			if (options.onProgress) options.onProgress({ doneSize: doneSize });
 
+			const waitStart = timingNow();
 			await shim.waitForFrame();
+			const waitMs = timingNow() - waitStart;
 
+			const blockReadStart = timingNow();
 			const block = await source.read(length);
+			const blockReadMs = timingNow() - blockReadStart;
 
+			const cryptoStart = timingNow();
 			const plainText = await this.decrypt(header.encryptionMethod, masterKeyPlainText, block);
+			const cryptoMs = timingNow() - cryptoStart;
+
+			const appendStart = timingNow();
 			await destination.append(plainText);
+			const appendMs = timingNow() - appendStart;
+
+			const totalMs = timingNow() - totalStart;
 		}
 	}
 
diff --git a/packages/lib/services/e2ee/cryptoTestUtils.ts b/packages/lib/services/e2ee/cryptoTestUtils.ts
index 43333f225..43c931f66 100644
--- a/packages/lib/services/e2ee/cryptoTestUtils.ts
+++ b/packages/lib/services/e2ee/cryptoTestUtils.ts
@@ -264,30 +264,31 @@ export const runIntegrationTests = async (silent = false, testPerformance = fals
 		log('Testing performance...');
 		if (shim.mobilePlatform() === '') {
 			await testStringPerformance(EncryptionMethod.StringV1, 100, 1000);
-			await testStringPerformance(EncryptionMethod.StringV1, 1000000, 10);
-			await testStringPerformance(EncryptionMethod.StringV1, 5000000, 10);
-			await testStringPerformance(EncryptionMethod.SJCL1a, 100, 1000);
-			await testStringPerformance(EncryptionMethod.SJCL1a, 1000000, 10);
-			await testStringPerformance(EncryptionMethod.SJCL1a, 5000000, 10);
-			await testFilePerformance(EncryptionMethod.FileV1, 100, 1000);
-			await testFilePerformance(EncryptionMethod.FileV1, 1000000, 3);
-			await testFilePerformance(EncryptionMethod.FileV1, 5000000, 3);
-			await testFilePerformance(EncryptionMethod.SJCL1a, 100, 1000);
-			await testFilePerformance(EncryptionMethod.SJCL1a, 1000000, 3);
-			await testFilePerformance(EncryptionMethod.SJCL1a, 5000000, 3);
+			// await testStringPerformance(EncryptionMethod.StringV1, 1000000, 10);
+			// await testStringPerformance(EncryptionMethod.StringV1, 5000000, 10);
+			// await testStringPerformance(EncryptionMethod.SJCL1a, 100, 1000);
+			// await testStringPerformance(EncryptionMethod.SJCL1a, 1000000, 10);
+			// await testStringPerformance(EncryptionMethod.SJCL1a, 5000000, 10);
+			// await testFilePerformance(EncryptionMethod.FileV1, 100, 1000);
+			// await testFilePerformance(EncryptionMethod.FileV1, 1000000, 3);
+			// await testFilePerformance(EncryptionMethod.FileV1, 5000000, 3);
+			// await testFilePerformance(EncryptionMethod.SJCL1a, 100, 1000);
+			// await testFilePerformance(EncryptionMethod.SJCL1a, 1000000, 3);
+			// await testFilePerformance(EncryptionMethod.SJCL1a, 5000000, 3);
 		} else {
 			await testStringPerformance(EncryptionMethod.StringV1, 100, 100);
-			await testStringPerformance(EncryptionMethod.StringV1, 500000, 3);
-			await testStringPerformance(EncryptionMethod.StringV1, 1000000, 3);
-			await testStringPerformance(EncryptionMethod.SJCL1a, 100, 100);
-			await testStringPerformance(EncryptionMethod.SJCL1a, 500000, 3);
-			await testStringPerformance(EncryptionMethod.SJCL1a, 1000000, 3);
+			await testStringPerformance(EncryptionMethod.StringV1, 500000, 5);
+			await testStringPerformance(EncryptionMethod.StringV1, 1000000, 5);
+			// await testStringPerformance(EncryptionMethod.SJCL1a, 100, 100);
+			// await testStringPerformance(EncryptionMethod.SJCL1a, 500000, 3);
+			// await testStringPerformance(EncryptionMethod.SJCL1a, 1000000, 3);
 			await testFilePerformance(EncryptionMethod.FileV1, 100, 100);
-			await testFilePerformance(EncryptionMethod.FileV1, 100000, 3);
-			await testFilePerformance(EncryptionMethod.FileV1, 500000, 3);
-			await testFilePerformance(EncryptionMethod.SJCL1a, 100, 100);
-			await testFilePerformance(EncryptionMethod.SJCL1a, 100000, 3);
-			await testFilePerformance(EncryptionMethod.SJCL1a, 500000, 3);
+			await testFilePerformance(EncryptionMethod.FileV1, 100000, 5);
+			await testFilePerformance(EncryptionMethod.FileV1, 500000, 5);
+			await testFilePerformance(EncryptionMethod.FileV1, 16000000, 5);
+			// await testFilePerformance(EncryptionMethod.SJCL1a, 100, 100);
+			// await testFilePerformance(EncryptionMethod.SJCL1a, 100000, 3);
+			// await testFilePerformance(EncryptionMethod.SJCL1a, 500000, 3);
 		}
 
 	}

I also tested the dev build on Android and found no errors during startup (b1bb66a)

@wh201906

wh201906 commented May 8, 2026

Copy link
Copy Markdown
Contributor Author

Note: For future reference, this pull request is related to upstream contributions by @wh201906 to react-native-quick-crypto.

It's also related to margelo/react-native-quick-crypto@9a30242 from boorad. I cannot integrate simdutf or other native encoding support without this framework.

@wh201906 wh201906 changed the title Mobile: Resolves #15287: Refactor (native) Crypto module to improve mobile performance Mobile: Resolves #15287: Refactor (native) Crypto module to improve encryption performance on mobile May 8, 2026
};

const pbkdf2Raw = (password: string, salt: CryptoBuffer, iterations: number, keylen: number, digest: Digest): Promise<CryptoBuffer> => {
const pbkdf2Raw = (password: string, salt: CryptoBuffer | ArrayBuffer, iterations: number, keylen: number, digest: Digest): Promise<CryptoBuffer> => {

@wh201906 wh201906 May 10, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wh201906

Copy link
Copy Markdown
Contributor Author

I added some comment in the PR, hope this helps.
The code is not changed since last review from coderabbitai

@laurent22

Copy link
Copy Markdown
Owner

Looks good to me and the performance gain seems impressive, thanks for working on this @wh201906. Just waiting for confirmation from @personalizedrefrigerator since he knows the native crypto part better than me

@personalizedrefrigerator

personalizedrefrigerator commented May 11, 2026

Copy link
Copy Markdown
Collaborator

This isn't building for me on MacOS (x86_64 Mac, building for iOS simulator):

.../joplin/packages/app-mobile/ios/Pods/Headers/Public/React-jsi/jsi/jsi.h:80:9 Cannot initialize a parameter of type 'char *' with an rvalue of type 'const value_type *' (aka 'const char *')

in file included from .../joplin/packages/app-mobile/node_modules/react-native-quick-base64/cpp/react-native-quick-base64.cpp:1:
in file included from .../joplin/packages/app-mobile/node_modules/react-native-quick-base64/cpp/react-native-quick-base64.h:1:
in file included from .../joplin/packages/app-mobile/ios/Pods/Headers/Public/React-jsi/jsi/jsilib.h:10:
Passing argument to parameter '__str' here

I'm currently cleaning cached files and plan to try a full rebuild.

@personalizedrefrigerator personalizedrefrigerator left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm currently cleaning cached files and plan to try a full rebuild.

The full rebuild (or an XCode update) seems to have resolved the issue! I've verified that the new startup tests pass successfully both on a simulator and a physical device.

@laurent22
laurent22 merged commit 226c2c7 into laurent22:dev May 12, 2026
15 checks passed
@wh201906
wh201906 deleted the wh201906/fast-crypto branch May 15, 2026 01:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Feature requests and code enhancements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Buffer<->String conversion is slow on mobile

4 participants