From 225eece98b8373ce28aab3c0ff95176fce3ba7b9 Mon Sep 17 00:00:00 2001 From: Athex Web3 Date: Thu, 4 Dec 2025 11:28:54 +0600 Subject: [PATCH 01/11] feat: add X25519/X448 ECDH support Add support for X25519 and X448 elliptic curve key exchange. Changes: - Implement key generation, import, export, and derivation - Add comprehensive test suite - Fix curve type selection in native layer - Fix husky pre-commit hook PATH issue --- .gitignore | 2 +- .husky/pre-commit | 3 + bun.lock | 21 +- example/ios/Podfile.lock | 348 ++++++++---------- .../project.pbxproj | 4 +- example/package.json | 6 +- example/src/hooks/useTestsList.ts | 1 + example/src/tests/subtle/x25519_x448.ts | 155 ++++++++ package.json | 2 +- .../cpp/ed25519/HybridEdKeyPair.cpp | 10 +- .../react-native-quick-crypto/package.json | 12 +- packages/react-native-quick-crypto/src/ed.ts | 102 +++++ .../react-native-quick-crypto/src/subtle.ts | 57 ++- 13 files changed, 496 insertions(+), 227 deletions(-) create mode 100644 example/src/tests/subtle/x25519_x448.ts diff --git a/.gitignore b/.gitignore index 3812bff3..9aefc126 100644 --- a/.gitignore +++ b/.gitignore @@ -187,4 +187,4 @@ tsconfig.tsbuildinfo # development stuffs *scratch* -.claude/settings.local.json +.agent/ \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit index 91e14e4f..c6ed3648 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,3 +1,6 @@ +# Add bun to PATH +export PATH="$HOME/.bun/bin:$PATH" + # Run linting and formatting on staged files bun lint-staged diff --git a/bun.lock b/bun.lock index 7ee442d5..a1e08560 100644 --- a/bun.lock +++ b/bun.lock @@ -1,6 +1,5 @@ { "lockfileVersion": 1, - "configVersion": 0, "workspaces": { "": { "dependencies": { @@ -47,10 +46,9 @@ "react": "19.1.0", "react-native": "0.81.1", "react-native-bouncy-checkbox": "4.1.2", - "react-native-fast-encoder": "^0.3.1", "react-native-nitro-modules": "0.29.1", "react-native-quick-base64": "2.2.2", - "react-native-quick-crypto": "workspace:*", + "react-native-quick-crypto": "1.0.0", "react-native-safe-area-context": "^5.2.2", "react-native-screens": "4.18.0", "react-native-vector-icons": "^10.3.0", @@ -80,7 +78,6 @@ "@types/react-test-renderer": "^19.1.0", "babel-jest": "29.7.0", "babel-plugin-module-resolver": "5.0.2", - "jose": "6.1.3", "react-test-renderer": "19.1.0", "typescript": "^5.8.3", }, @@ -91,6 +88,7 @@ "dependencies": { "@craftzdog/react-native-buffer": "6.1.0", "events": "3.3.0", + "expo-build-properties": "0.14.6", "react-native-quick-base64": "2.2.2", "readable-stream": "4.5.2", "safe-buffer": "^5.2.1", @@ -103,7 +101,6 @@ "@types/readable-stream": "4.0.18", "del-cli": "7.0.0", "expo": "^54.0.25", - "expo-build-properties": "^1.0.0", "jest": "29.7.0", "nitro-codegen": "0.29.1", "react-native-builder-bob": "0.40.15", @@ -111,14 +108,12 @@ }, "peerDependencies": { "expo": ">=48.0.0", - "expo-build-properties": "*", "react": "*", "react-native": "*", "react-native-nitro-modules": ">=0.29.1", }, "optionalPeers": [ "expo", - "expo-build-properties", ], }, }, @@ -1308,7 +1303,7 @@ "expo-asset": ["expo-asset@12.0.10", "", { "dependencies": { "@expo/image-utils": "^0.8.7", "expo-constants": "~18.0.10" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-pZyeJkoDsALh4gpCQDzTA/UCLaPH/1rjQNGubmLn/uDM27S4iYJb/YWw4+CNZOtd5bCUOhDPg5DtGQnydNFSXg=="], - "expo-build-properties": ["expo-build-properties@1.0.9", "", { "dependencies": { "ajv": "^8.11.0", "semver": "^7.6.0" }, "peerDependencies": { "expo": "*" } }, "sha512-2icttCy3OPTk/GWIFt+vwA+0hup53jnmYb7JKRbvNvrrOrz+WblzpeoiaOleI2dYG/vjwpNO8to8qVyKhYJtrQ=="], + "expo-build-properties": ["expo-build-properties@0.14.6", "", { "dependencies": { "ajv": "^8.11.0", "semver": "^7.6.0" }, "peerDependencies": { "expo": "*" } }, "sha512-46+gcnFxb2Dz2TFEhFlEJ11qT85THlPtFgkRKQ3a11S3+stgDzDBC2WwbXS5/GMINLIDdBFbbZlajgVND0tMnQ=="], "expo-constants": ["expo-constants@18.0.10", "", { "dependencies": { "@expo/config": "~12.0.10", "@expo/env": "~2.0.7" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-Rhtv+X974k0Cahmvx6p7ER5+pNhBC0XbP1lRviL2J1Xl4sT2FBaIuIxF/0I0CbhOsySf0ksqc5caFweAy9Ewiw=="], @@ -1340,7 +1335,7 @@ "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, ""], - "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], + "fast-uri": ["fast-uri@3.0.6", "", {}, "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw=="], "fast-xml-parser": ["fast-xml-parser@4.4.1", "", { "dependencies": { "strnum": "^1.0.5" }, "bin": { "fxparser": "src/cli/cli.js" } }, ""], @@ -1366,8 +1361,6 @@ "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, ""], - "flatbuffers": ["flatbuffers@2.0.6", "", {}, "sha512-QTTZTXTbVfuOVQu2X6eLOw4vefUxnFJZxAKeN3rEPhjEzBtIbehimJLfVGHPM8iX0Na+9i76SBEg0skf0c0sCA=="], - "flatted": ["flatted@3.3.1", "", {}, ""], "flow-enums-runtime": ["flow-enums-runtime@0.0.6", "", {}, ""], @@ -1686,8 +1679,6 @@ "joi": ["joi@17.13.3", "", { "dependencies": { "@hapi/hoek": "^9.3.0", "@hapi/topo": "^5.1.0", "@sideway/address": "^4.1.5", "@sideway/formula": "^3.0.1", "@sideway/pinpoint": "^2.0.0" } }, ""], - "jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="], - "js-tokens": ["js-tokens@4.0.0", "", {}, ""], "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": "bin/js-yaml.js" }, ""], @@ -2110,8 +2101,6 @@ "react-native-builder-bob": ["react-native-builder-bob@0.40.15", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-transform-flow-strip-types": "^7.26.5", "@babel/plugin-transform-strict-mode": "^7.24.7", "@babel/preset-env": "^7.25.2", "@babel/preset-react": "^7.24.7", "@babel/preset-typescript": "^7.24.7", "arktype": "^2.1.15", "babel-plugin-syntax-hermes-parser": "^0.28.0", "browserslist": "^4.20.4", "cross-spawn": "^7.0.3", "dedent": "^0.7.0", "del": "^6.1.1", "escape-string-regexp": "^4.0.0", "fs-extra": "^10.1.0", "glob": "^8.0.3", "is-git-dirty": "^2.0.1", "json5": "^2.2.1", "kleur": "^4.1.4", "prompts": "^2.4.2", "react-native-monorepo-config": "^0.1.8", "which": "^2.0.2", "yargs": "^17.5.1" }, "bin": { "bob": "bin/bob" } }, "sha512-p70LXlYOe53bZeEQchbK1hIhBGQsuB13bT81E7KkOe4dQABwMV6c585A4np0c3nwTTaqQMDYUat4x7J0oLnjxQ=="], - "react-native-fast-encoder": ["react-native-fast-encoder@0.3.1", "", { "dependencies": { "big-integer": "^1.6.51", "flatbuffers": "2.0.6" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-D5ZEbffxayZImtUUErbNod+7IvJITkxTCPZAQTFg6lrSjl/443Mk5CvT9nKpJC1w4cg2llWk6QP8nczij5ClcQ=="], - "react-native-monorepo-config": ["react-native-monorepo-config@0.1.10", "", { "dependencies": { "escape-string-regexp": "^5.0.0", "fast-glob": "^3.3.3" } }, "sha512-v0rlaLZiCUg95Mpw6xNRQce5k9yio0qscKjNQaPtFYMNL75YugS2UPUItIPLIRbZubK+s2/LRzBjX+mdyUgh4g=="], "react-native-nitro-modules": ["react-native-nitro-modules@0.29.1", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-91A/Lc4Zc1Bvzj1iMSnD6vA5Swqv8aVcwGcv8ddjoPd9mahNvVS2arFh3o7kAqRH4RIh3KcQ0NpYslu7AYn55Q=="], @@ -3024,8 +3013,6 @@ "expo-build-properties/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], - "expo-build-properties/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], - "expo-modules-autolinking/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, ""], diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index a97b75e1..41bbfb17 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -37,7 +37,8 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - QuickCrypto (1.0.1): + - OpenSSL-Universal (3.3.3001) + - QuickCrypto (1.0.0): - boost - DoubleConversion - fast_float @@ -45,6 +46,7 @@ PODS: - glog - hermes-engine - NitroModules + - OpenSSL-Universal (= 3.3.3001) - RCT-Folly - RCT-Folly/Fabric - RCTRequired @@ -1807,34 +1809,6 @@ PODS: - React-RCTFBReactNativeSpec - ReactCommon/turbomodule/core - SocketRocket - - react-native-fast-encoder (0.3.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-ImageManager - - React-jsi - - React-NativeModulesApple - - React-RCTFabric - - React-renderercss - - React-rendererdebug - - React-utils - - ReactCodegen - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - SocketRocket - - Yoga - react-native-quick-base64 (2.2.2): - React-Core - react-native-safe-area-context (5.6.2): @@ -2520,249 +2494,247 @@ PODS: - Yoga (0.0.0) DEPENDENCIES: - - boost (from `../../node_modules/react-native/third-party-podspecs/boost.podspec`) - - DoubleConversion (from `../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - - fast_float (from `../../node_modules/react-native/third-party-podspecs/fast_float.podspec`) - - FBLazyVector (from `../../node_modules/react-native/Libraries/FBLazyVector`) - - fmt (from `../../node_modules/react-native/third-party-podspecs/fmt.podspec`) - - glog (from `../../node_modules/react-native/third-party-podspecs/glog.podspec`) - - hermes-engine (from `../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) - - NitroModules (from `../../node_modules/react-native-nitro-modules`) - - QuickCrypto (from `../../node_modules/react-native-quick-crypto`) - - RCT-Folly (from `../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - - RCTDeprecation (from `../../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`) - - RCTRequired (from `../../node_modules/react-native/Libraries/Required`) - - RCTTypeSafety (from `../../node_modules/react-native/Libraries/TypeSafety`) - - React (from `../../node_modules/react-native/`) - - React-callinvoker (from `../../node_modules/react-native/ReactCommon/callinvoker`) - - React-Core (from `../../node_modules/react-native/`) - - React-Core/RCTWebSocket (from `../../node_modules/react-native/`) - - React-CoreModules (from `../../node_modules/react-native/React/CoreModules`) - - React-cxxreact (from `../../node_modules/react-native/ReactCommon/cxxreact`) - - React-debug (from `../../node_modules/react-native/ReactCommon/react/debug`) - - React-defaultsnativemodule (from `../../node_modules/react-native/ReactCommon/react/nativemodule/defaults`) - - React-domnativemodule (from `../../node_modules/react-native/ReactCommon/react/nativemodule/dom`) - - React-Fabric (from `../../node_modules/react-native/ReactCommon`) - - React-FabricComponents (from `../../node_modules/react-native/ReactCommon`) - - React-FabricImage (from `../../node_modules/react-native/ReactCommon`) - - React-featureflags (from `../../node_modules/react-native/ReactCommon/react/featureflags`) - - React-featureflagsnativemodule (from `../../node_modules/react-native/ReactCommon/react/nativemodule/featureflags`) - - React-graphics (from `../../node_modules/react-native/ReactCommon/react/renderer/graphics`) - - React-hermes (from `../../node_modules/react-native/ReactCommon/hermes`) - - React-idlecallbacksnativemodule (from `../../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks`) - - React-ImageManager (from `../../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios`) - - React-jserrorhandler (from `../../node_modules/react-native/ReactCommon/jserrorhandler`) - - React-jsi (from `../../node_modules/react-native/ReactCommon/jsi`) - - React-jsiexecutor (from `../../node_modules/react-native/ReactCommon/jsiexecutor`) - - React-jsinspector (from `../../node_modules/react-native/ReactCommon/jsinspector-modern`) - - React-jsinspectorcdp (from `../../node_modules/react-native/ReactCommon/jsinspector-modern/cdp`) - - React-jsinspectornetwork (from `../../node_modules/react-native/ReactCommon/jsinspector-modern/network`) - - React-jsinspectortracing (from `../../node_modules/react-native/ReactCommon/jsinspector-modern/tracing`) - - React-jsitooling (from `../../node_modules/react-native/ReactCommon/jsitooling`) - - React-jsitracing (from `../../node_modules/react-native/ReactCommon/hermes/executor/`) - - React-logger (from `../../node_modules/react-native/ReactCommon/logger`) - - React-Mapbuffer (from `../../node_modules/react-native/ReactCommon`) - - React-microtasksnativemodule (from `../../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`) - - react-native-fast-encoder (from `../../node_modules/react-native-fast-encoder`) - - react-native-quick-base64 (from `../../node_modules/react-native-quick-base64`) - - react-native-safe-area-context (from `../../node_modules/react-native-safe-area-context`) - - React-NativeModulesApple (from `../../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) - - React-oscompat (from `../../node_modules/react-native/ReactCommon/oscompat`) - - React-perflogger (from `../../node_modules/react-native/ReactCommon/reactperflogger`) - - React-performancetimeline (from `../../node_modules/react-native/ReactCommon/react/performance/timeline`) - - React-RCTActionSheet (from `../../node_modules/react-native/Libraries/ActionSheetIOS`) - - React-RCTAnimation (from `../../node_modules/react-native/Libraries/NativeAnimation`) - - React-RCTAppDelegate (from `../../node_modules/react-native/Libraries/AppDelegate`) - - React-RCTBlob (from `../../node_modules/react-native/Libraries/Blob`) - - React-RCTFabric (from `../../node_modules/react-native/React`) - - React-RCTFBReactNativeSpec (from `../../node_modules/react-native/React`) - - React-RCTImage (from `../../node_modules/react-native/Libraries/Image`) - - React-RCTLinking (from `../../node_modules/react-native/Libraries/LinkingIOS`) - - React-RCTNetwork (from `../../node_modules/react-native/Libraries/Network`) - - React-RCTRuntime (from `../../node_modules/react-native/React/Runtime`) - - React-RCTSettings (from `../../node_modules/react-native/Libraries/Settings`) - - React-RCTText (from `../../node_modules/react-native/Libraries/Text`) - - React-RCTVibration (from `../../node_modules/react-native/Libraries/Vibration`) - - React-rendererconsistency (from `../../node_modules/react-native/ReactCommon/react/renderer/consistency`) - - React-renderercss (from `../../node_modules/react-native/ReactCommon/react/renderer/css`) - - React-rendererdebug (from `../../node_modules/react-native/ReactCommon/react/renderer/debug`) - - React-RuntimeApple (from `../../node_modules/react-native/ReactCommon/react/runtime/platform/ios`) - - React-RuntimeCore (from `../../node_modules/react-native/ReactCommon/react/runtime`) - - React-runtimeexecutor (from `../../node_modules/react-native/ReactCommon/runtimeexecutor`) - - React-RuntimeHermes (from `../../node_modules/react-native/ReactCommon/react/runtime`) - - React-runtimescheduler (from `../../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`) - - React-timing (from `../../node_modules/react-native/ReactCommon/react/timing`) - - React-utils (from `../../node_modules/react-native/ReactCommon/react/utils`) + - boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`) + - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) + - fast_float (from `../node_modules/react-native/third-party-podspecs/fast_float.podspec`) + - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) + - fmt (from `../node_modules/react-native/third-party-podspecs/fmt.podspec`) + - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) + - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) + - NitroModules (from `../node_modules/react-native-nitro-modules`) + - QuickCrypto (from `../node_modules/react-native-quick-crypto`) + - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) + - RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`) + - RCTRequired (from `../node_modules/react-native/Libraries/Required`) + - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`) + - React (from `../node_modules/react-native/`) + - React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`) + - React-Core (from `../node_modules/react-native/`) + - React-Core/RCTWebSocket (from `../node_modules/react-native/`) + - React-CoreModules (from `../node_modules/react-native/React/CoreModules`) + - React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`) + - React-debug (from `../node_modules/react-native/ReactCommon/react/debug`) + - React-defaultsnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/defaults`) + - React-domnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/dom`) + - React-Fabric (from `../node_modules/react-native/ReactCommon`) + - React-FabricComponents (from `../node_modules/react-native/ReactCommon`) + - React-FabricImage (from `../node_modules/react-native/ReactCommon`) + - React-featureflags (from `../node_modules/react-native/ReactCommon/react/featureflags`) + - React-featureflagsnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/featureflags`) + - React-graphics (from `../node_modules/react-native/ReactCommon/react/renderer/graphics`) + - React-hermes (from `../node_modules/react-native/ReactCommon/hermes`) + - React-idlecallbacksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks`) + - React-ImageManager (from `../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios`) + - React-jserrorhandler (from `../node_modules/react-native/ReactCommon/jserrorhandler`) + - React-jsi (from `../node_modules/react-native/ReactCommon/jsi`) + - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) + - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector-modern`) + - React-jsinspectorcdp (from `../node_modules/react-native/ReactCommon/jsinspector-modern/cdp`) + - React-jsinspectornetwork (from `../node_modules/react-native/ReactCommon/jsinspector-modern/network`) + - React-jsinspectortracing (from `../node_modules/react-native/ReactCommon/jsinspector-modern/tracing`) + - React-jsitooling (from `../node_modules/react-native/ReactCommon/jsitooling`) + - React-jsitracing (from `../node_modules/react-native/ReactCommon/hermes/executor/`) + - React-logger (from `../node_modules/react-native/ReactCommon/logger`) + - React-Mapbuffer (from `../node_modules/react-native/ReactCommon`) + - React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`) + - react-native-quick-base64 (from `../node_modules/react-native-quick-base64`) + - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) + - React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) + - React-oscompat (from `../node_modules/react-native/ReactCommon/oscompat`) + - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) + - React-performancetimeline (from `../node_modules/react-native/ReactCommon/react/performance/timeline`) + - React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`) + - React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`) + - React-RCTAppDelegate (from `../node_modules/react-native/Libraries/AppDelegate`) + - React-RCTBlob (from `../node_modules/react-native/Libraries/Blob`) + - React-RCTFabric (from `../node_modules/react-native/React`) + - React-RCTFBReactNativeSpec (from `../node_modules/react-native/React`) + - React-RCTImage (from `../node_modules/react-native/Libraries/Image`) + - React-RCTLinking (from `../node_modules/react-native/Libraries/LinkingIOS`) + - React-RCTNetwork (from `../node_modules/react-native/Libraries/Network`) + - React-RCTRuntime (from `../node_modules/react-native/React/Runtime`) + - React-RCTSettings (from `../node_modules/react-native/Libraries/Settings`) + - React-RCTText (from `../node_modules/react-native/Libraries/Text`) + - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) + - React-rendererconsistency (from `../node_modules/react-native/ReactCommon/react/renderer/consistency`) + - React-renderercss (from `../node_modules/react-native/ReactCommon/react/renderer/css`) + - React-rendererdebug (from `../node_modules/react-native/ReactCommon/react/renderer/debug`) + - React-RuntimeApple (from `../node_modules/react-native/ReactCommon/react/runtime/platform/ios`) + - React-RuntimeCore (from `../node_modules/react-native/ReactCommon/react/runtime`) + - React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`) + - React-RuntimeHermes (from `../node_modules/react-native/ReactCommon/react/runtime`) + - React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`) + - React-timing (from `../node_modules/react-native/ReactCommon/react/timing`) + - React-utils (from `../node_modules/react-native/ReactCommon/react/utils`) - ReactAppDependencyProvider (from `build/generated/ios`) - ReactCodegen (from `build/generated/ios`) - - ReactCommon/turbomodule/core (from `../../node_modules/react-native/ReactCommon`) - - RNScreens (from `../../node_modules/react-native-screens`) - - RNVectorIcons (from `../../node_modules/react-native-vector-icons`) + - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) + - RNScreens (from `../node_modules/react-native-screens`) + - RNVectorIcons (from `../node_modules/react-native-vector-icons`) - SocketRocket (~> 0.7.1) - - Yoga (from `../../node_modules/react-native/ReactCommon/yoga`) + - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) SPEC REPOS: trunk: + - OpenSSL-Universal - SocketRocket EXTERNAL SOURCES: boost: - :podspec: "../../node_modules/react-native/third-party-podspecs/boost.podspec" + :podspec: "../node_modules/react-native/third-party-podspecs/boost.podspec" DoubleConversion: - :podspec: "../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" + :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" fast_float: - :podspec: "../../node_modules/react-native/third-party-podspecs/fast_float.podspec" + :podspec: "../node_modules/react-native/third-party-podspecs/fast_float.podspec" FBLazyVector: - :path: "../../node_modules/react-native/Libraries/FBLazyVector" + :path: "../node_modules/react-native/Libraries/FBLazyVector" fmt: - :podspec: "../../node_modules/react-native/third-party-podspecs/fmt.podspec" + :podspec: "../node_modules/react-native/third-party-podspecs/fmt.podspec" glog: - :podspec: "../../node_modules/react-native/third-party-podspecs/glog.podspec" + :podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec" hermes-engine: - :podspec: "../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" + :podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" :tag: hermes-2025-07-07-RNv0.81.0-e0fc67142ec0763c6b6153ca2bf96df815539782 NitroModules: - :path: "../../node_modules/react-native-nitro-modules" + :path: "../node_modules/react-native-nitro-modules" QuickCrypto: - :path: "../../node_modules/react-native-quick-crypto" + :path: "../node_modules/react-native-quick-crypto" RCT-Folly: - :podspec: "../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec" + :podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec" RCTDeprecation: - :path: "../../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation" + :path: "../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation" RCTRequired: - :path: "../../node_modules/react-native/Libraries/Required" + :path: "../node_modules/react-native/Libraries/Required" RCTTypeSafety: - :path: "../../node_modules/react-native/Libraries/TypeSafety" + :path: "../node_modules/react-native/Libraries/TypeSafety" React: - :path: "../../node_modules/react-native/" + :path: "../node_modules/react-native/" React-callinvoker: - :path: "../../node_modules/react-native/ReactCommon/callinvoker" + :path: "../node_modules/react-native/ReactCommon/callinvoker" React-Core: - :path: "../../node_modules/react-native/" + :path: "../node_modules/react-native/" React-CoreModules: - :path: "../../node_modules/react-native/React/CoreModules" + :path: "../node_modules/react-native/React/CoreModules" React-cxxreact: - :path: "../../node_modules/react-native/ReactCommon/cxxreact" + :path: "../node_modules/react-native/ReactCommon/cxxreact" React-debug: - :path: "../../node_modules/react-native/ReactCommon/react/debug" + :path: "../node_modules/react-native/ReactCommon/react/debug" React-defaultsnativemodule: - :path: "../../node_modules/react-native/ReactCommon/react/nativemodule/defaults" + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/defaults" React-domnativemodule: - :path: "../../node_modules/react-native/ReactCommon/react/nativemodule/dom" + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/dom" React-Fabric: - :path: "../../node_modules/react-native/ReactCommon" + :path: "../node_modules/react-native/ReactCommon" React-FabricComponents: - :path: "../../node_modules/react-native/ReactCommon" + :path: "../node_modules/react-native/ReactCommon" React-FabricImage: - :path: "../../node_modules/react-native/ReactCommon" + :path: "../node_modules/react-native/ReactCommon" React-featureflags: - :path: "../../node_modules/react-native/ReactCommon/react/featureflags" + :path: "../node_modules/react-native/ReactCommon/react/featureflags" React-featureflagsnativemodule: - :path: "../../node_modules/react-native/ReactCommon/react/nativemodule/featureflags" + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/featureflags" React-graphics: - :path: "../../node_modules/react-native/ReactCommon/react/renderer/graphics" + :path: "../node_modules/react-native/ReactCommon/react/renderer/graphics" React-hermes: - :path: "../../node_modules/react-native/ReactCommon/hermes" + :path: "../node_modules/react-native/ReactCommon/hermes" React-idlecallbacksnativemodule: - :path: "../../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks" + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks" React-ImageManager: - :path: "../../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios" + :path: "../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios" React-jserrorhandler: - :path: "../../node_modules/react-native/ReactCommon/jserrorhandler" + :path: "../node_modules/react-native/ReactCommon/jserrorhandler" React-jsi: - :path: "../../node_modules/react-native/ReactCommon/jsi" + :path: "../node_modules/react-native/ReactCommon/jsi" React-jsiexecutor: - :path: "../../node_modules/react-native/ReactCommon/jsiexecutor" + :path: "../node_modules/react-native/ReactCommon/jsiexecutor" React-jsinspector: - :path: "../../node_modules/react-native/ReactCommon/jsinspector-modern" + :path: "../node_modules/react-native/ReactCommon/jsinspector-modern" React-jsinspectorcdp: - :path: "../../node_modules/react-native/ReactCommon/jsinspector-modern/cdp" + :path: "../node_modules/react-native/ReactCommon/jsinspector-modern/cdp" React-jsinspectornetwork: - :path: "../../node_modules/react-native/ReactCommon/jsinspector-modern/network" + :path: "../node_modules/react-native/ReactCommon/jsinspector-modern/network" React-jsinspectortracing: - :path: "../../node_modules/react-native/ReactCommon/jsinspector-modern/tracing" + :path: "../node_modules/react-native/ReactCommon/jsinspector-modern/tracing" React-jsitooling: - :path: "../../node_modules/react-native/ReactCommon/jsitooling" + :path: "../node_modules/react-native/ReactCommon/jsitooling" React-jsitracing: - :path: "../../node_modules/react-native/ReactCommon/hermes/executor/" + :path: "../node_modules/react-native/ReactCommon/hermes/executor/" React-logger: - :path: "../../node_modules/react-native/ReactCommon/logger" + :path: "../node_modules/react-native/ReactCommon/logger" React-Mapbuffer: - :path: "../../node_modules/react-native/ReactCommon" + :path: "../node_modules/react-native/ReactCommon" React-microtasksnativemodule: - :path: "../../node_modules/react-native/ReactCommon/react/nativemodule/microtasks" - react-native-fast-encoder: - :path: "../../node_modules/react-native-fast-encoder" + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/microtasks" react-native-quick-base64: - :path: "../../node_modules/react-native-quick-base64" + :path: "../node_modules/react-native-quick-base64" react-native-safe-area-context: - :path: "../../node_modules/react-native-safe-area-context" + :path: "../node_modules/react-native-safe-area-context" React-NativeModulesApple: - :path: "../../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios" + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios" React-oscompat: - :path: "../../node_modules/react-native/ReactCommon/oscompat" + :path: "../node_modules/react-native/ReactCommon/oscompat" React-perflogger: - :path: "../../node_modules/react-native/ReactCommon/reactperflogger" + :path: "../node_modules/react-native/ReactCommon/reactperflogger" React-performancetimeline: - :path: "../../node_modules/react-native/ReactCommon/react/performance/timeline" + :path: "../node_modules/react-native/ReactCommon/react/performance/timeline" React-RCTActionSheet: - :path: "../../node_modules/react-native/Libraries/ActionSheetIOS" + :path: "../node_modules/react-native/Libraries/ActionSheetIOS" React-RCTAnimation: - :path: "../../node_modules/react-native/Libraries/NativeAnimation" + :path: "../node_modules/react-native/Libraries/NativeAnimation" React-RCTAppDelegate: - :path: "../../node_modules/react-native/Libraries/AppDelegate" + :path: "../node_modules/react-native/Libraries/AppDelegate" React-RCTBlob: - :path: "../../node_modules/react-native/Libraries/Blob" + :path: "../node_modules/react-native/Libraries/Blob" React-RCTFabric: - :path: "../../node_modules/react-native/React" + :path: "../node_modules/react-native/React" React-RCTFBReactNativeSpec: - :path: "../../node_modules/react-native/React" + :path: "../node_modules/react-native/React" React-RCTImage: - :path: "../../node_modules/react-native/Libraries/Image" + :path: "../node_modules/react-native/Libraries/Image" React-RCTLinking: - :path: "../../node_modules/react-native/Libraries/LinkingIOS" + :path: "../node_modules/react-native/Libraries/LinkingIOS" React-RCTNetwork: - :path: "../../node_modules/react-native/Libraries/Network" + :path: "../node_modules/react-native/Libraries/Network" React-RCTRuntime: - :path: "../../node_modules/react-native/React/Runtime" + :path: "../node_modules/react-native/React/Runtime" React-RCTSettings: - :path: "../../node_modules/react-native/Libraries/Settings" + :path: "../node_modules/react-native/Libraries/Settings" React-RCTText: - :path: "../../node_modules/react-native/Libraries/Text" + :path: "../node_modules/react-native/Libraries/Text" React-RCTVibration: - :path: "../../node_modules/react-native/Libraries/Vibration" + :path: "../node_modules/react-native/Libraries/Vibration" React-rendererconsistency: - :path: "../../node_modules/react-native/ReactCommon/react/renderer/consistency" + :path: "../node_modules/react-native/ReactCommon/react/renderer/consistency" React-renderercss: - :path: "../../node_modules/react-native/ReactCommon/react/renderer/css" + :path: "../node_modules/react-native/ReactCommon/react/renderer/css" React-rendererdebug: - :path: "../../node_modules/react-native/ReactCommon/react/renderer/debug" + :path: "../node_modules/react-native/ReactCommon/react/renderer/debug" React-RuntimeApple: - :path: "../../node_modules/react-native/ReactCommon/react/runtime/platform/ios" + :path: "../node_modules/react-native/ReactCommon/react/runtime/platform/ios" React-RuntimeCore: - :path: "../../node_modules/react-native/ReactCommon/react/runtime" + :path: "../node_modules/react-native/ReactCommon/react/runtime" React-runtimeexecutor: - :path: "../../node_modules/react-native/ReactCommon/runtimeexecutor" + :path: "../node_modules/react-native/ReactCommon/runtimeexecutor" React-RuntimeHermes: - :path: "../../node_modules/react-native/ReactCommon/react/runtime" + :path: "../node_modules/react-native/ReactCommon/react/runtime" React-runtimescheduler: - :path: "../../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler" + :path: "../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler" React-timing: - :path: "../../node_modules/react-native/ReactCommon/react/timing" + :path: "../node_modules/react-native/ReactCommon/react/timing" React-utils: - :path: "../../node_modules/react-native/ReactCommon/react/utils" + :path: "../node_modules/react-native/ReactCommon/react/utils" ReactAppDependencyProvider: :path: build/generated/ios ReactCodegen: :path: build/generated/ios ReactCommon: - :path: "../../node_modules/react-native/ReactCommon" + :path: "../node_modules/react-native/ReactCommon" RNScreens: - :path: "../../node_modules/react-native-screens" + :path: "../node_modules/react-native-screens" RNVectorIcons: - :path: "../../node_modules/react-native-vector-icons" + :path: "../node_modules/react-native-vector-icons" Yoga: - :path: "../../node_modules/react-native/ReactCommon/yoga" + :path: "../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90 @@ -2773,7 +2745,8 @@ SPEC CHECKSUMS: glog: 5683914934d5b6e4240e497e0f4a3b42d1854183 hermes-engine: 4f8246b1f6d79f625e0d99472d1f3a71da4d28ca NitroModules: 1715fe0e22defd9e2cdd48fb5e0dbfd01af54bec - QuickCrypto: 389afd05e4f7d62ec5e2be215aeaf0dd4745b5e1 + OpenSSL-Universal: 6082b0bf950e5636fe0d78def171184e2b3899c2 + QuickCrypto: 4e82c6565ea7b5f9d4c3f0ad3f19b785a676b4cc RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669 RCTDeprecation: c4b9e2fd0ab200e3af72b013ed6113187c607077 RCTRequired: e97dd5dafc1db8094e63bc5031e0371f092ae92a @@ -2807,7 +2780,6 @@ SPEC CHECKSUMS: React-logger: 7aef4d74123e5e3d267e5af1fbf5135b5a0d8381 React-Mapbuffer: 91e0eab42a6ae7f3e34091a126d70fc53bd3823e React-microtasksnativemodule: 1ead4fe154df3b1ba34b5a9e35ef3c4bdfa72ccb - react-native-fast-encoder: f2728ab5e520601ba04df15716722941d941495e react-native-quick-base64: 6568199bb2ac8e72ecdfdc73a230fbc5c1d3aac4 react-native-safe-area-context: c00143b4823773bba23f2f19f85663ae89ceb460 React-NativeModulesApple: eff2eba56030eb0d107b1642b8f853bc36a833ac @@ -2838,13 +2810,13 @@ SPEC CHECKSUMS: React-timing: 97ada2c47b4c5932e7f773c7d239c52b90d6ca68 React-utils: f0949d247a46b4c09f03e5a3cb1167602d0b729a ReactAppDependencyProvider: 3eb9096cb139eb433965693bbe541d96eb3d3ec9 - ReactCodegen: c881aa301b4194d7d08b2a1afcb0b9e108917629 + ReactCodegen: cb29ed8113eb9f7821e0b80d42a7b1535ce3a52b ReactCommon: ce5d4226dfaf9d5dacbef57b4528819e39d3a120 RNScreens: d821082c6dd1cb397cc0c98b026eeafaa68be479 RNVectorIcons: 791f13226ec4a3fd13062eda9e892159f0981fae SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Yoga: 11c9686a21e2cd82a094a723649d9f4507200fb0 -PODFILE CHECKSUM: bc958092bb9060694d04c6fcf716262b0549cded +PODFILE CHECKSUM: 8bf59f4e86b38489f786b2878e119cdf1824ca75 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/example/ios/QuickCryptoExample.xcodeproj/project.pbxproj b/example/ios/QuickCryptoExample.xcodeproj/project.pbxproj index bcfe455e..040aaf19 100644 --- a/example/ios/QuickCryptoExample.xcodeproj/project.pbxproj +++ b/example/ios/QuickCryptoExample.xcodeproj/project.pbxproj @@ -415,7 +415,7 @@ "-DFOLLY_HAVE_CLOCK_GETTIME=1", ); OTHER_LDFLAGS = "$(inherited)"; - REACT_NATIVE_PATH = "${PODS_ROOT}/../../../node_modules/react-native"; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; SWIFT_ENABLE_EXPLICIT_MODULES = NO; @@ -500,7 +500,7 @@ "-DFOLLY_HAVE_CLOCK_GETTIME=1", ); OTHER_LDFLAGS = "$(inherited)"; - REACT_NATIVE_PATH = "${PODS_ROOT}/../../../node_modules/react-native"; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_ENABLE_EXPLICIT_MODULES = NO; USE_HERMES = true; diff --git a/example/package.json b/example/package.json index dc416319..460eaef2 100644 --- a/example/package.json +++ b/example/package.json @@ -1,6 +1,6 @@ { "name": "react-native-quick-crypto-example", - "version": "1.0.1", + "version": "1.0.0", "private": true, "type": "module", "scripts": { @@ -36,10 +36,9 @@ "react": "19.1.0", "react-native": "0.81.1", "react-native-bouncy-checkbox": "4.1.2", - "react-native-fast-encoder": "^0.3.1", "react-native-nitro-modules": "0.29.1", "react-native-quick-base64": "2.2.2", - "react-native-quick-crypto": "1.0.1", + "react-native-quick-crypto": "1.0.0", "react-native-safe-area-context": "^5.2.2", "react-native-screens": "4.18.0", "react-native-vector-icons": "^10.3.0", @@ -69,7 +68,6 @@ "@types/react-test-renderer": "^19.1.0", "babel-jest": "29.7.0", "babel-plugin-module-resolver": "5.0.2", - "jose": "6.1.3", "react-test-renderer": "19.1.0", "typescript": "^5.8.3" }, diff --git a/example/src/hooks/useTestsList.ts b/example/src/hooks/useTestsList.ts index b7b4e347..b8b9d702 100644 --- a/example/src/hooks/useTestsList.ts +++ b/example/src/hooks/useTestsList.ts @@ -26,6 +26,7 @@ import '../tests/subtle/generateKey'; import '../tests/subtle/import_export'; import '../tests/subtle/jwk_rfc7517_tests'; import '../tests/subtle/sign_verify'; +import '../tests/subtle/x25519_x448'; export const useTestsList = (): [ TestSuites, diff --git a/example/src/tests/subtle/x25519_x448.ts b/example/src/tests/subtle/x25519_x448.ts new file mode 100644 index 00000000..3a69c7f5 --- /dev/null +++ b/example/src/tests/subtle/x25519_x448.ts @@ -0,0 +1,155 @@ +import { expect } from 'chai'; +import { + subtle, + type CryptoKeyPair, + type CryptoKey, +} from 'react-native-quick-crypto'; +import { test } from '../util'; + +const SUITE = 'subtle x25519/x448'; + +test( + SUITE, + 'X25519 - generateKey, exportKey, importKey, deriveBits', + async () => { + const format = 'raw'; + const algorithm = { name: 'X25519' } as const; + + // 1. Generate Keys + const aliceKeys = (await subtle.generateKey(algorithm, true, [ + 'deriveKey', + 'deriveBits', + ])) as CryptoKeyPair; + + const bobKeys = (await subtle.generateKey(algorithm, true, [ + 'deriveKey', + 'deriveBits', + ])) as CryptoKeyPair; + + expect((aliceKeys.publicKey as CryptoKey).algorithm.name).to.equal( + 'X25519', + ); + expect((aliceKeys.privateKey as CryptoKey).algorithm.name).to.equal( + 'X25519', + ); + + // 2. Export Keys + const alicePubRaw = await subtle.exportKey( + format, + aliceKeys.publicKey as CryptoKey, + ); + const bobPubRaw = await subtle.exportKey( + format, + bobKeys.publicKey as CryptoKey, + ); + + // 3. Import Keys + const alicePubImported = await subtle.importKey( + format, + alicePubRaw, + algorithm, + true, + [], + ); + + const bobPubImported = await subtle.importKey( + format, + bobPubRaw, + algorithm, + true, + [], + ); + + // 4. Derive Bits + const bitsLength = 256; + const aliceShared = await subtle.deriveBits( + { name: 'X25519', public: bobPubImported } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + aliceKeys.privateKey as CryptoKey, + bitsLength, + ); + + const bobShared = await subtle.deriveBits( + { name: 'X25519', public: alicePubImported } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + bobKeys.privateKey as CryptoKey, + bitsLength, + ); + + // Verify shared secrets match + const aliceSharedView = new Uint8Array(aliceShared); + const bobSharedView = new Uint8Array(bobShared); + + expect(aliceSharedView.length).to.equal(bitsLength / 8); + expect(aliceSharedView).to.deep.equal(bobSharedView); + }, +); + +test( + SUITE, + 'X448 - generateKey, exportKey, importKey, deriveBits', + async () => { + const format = 'spki'; // Use SPKI for X448 public key export test + const algorithm = { name: 'X448' } as const; + + // 1. Generate Keys + const aliceKeys = (await subtle.generateKey(algorithm, true, [ + 'deriveKey', + 'deriveBits', + ])) as CryptoKeyPair; + + const bobKeys = (await subtle.generateKey(algorithm, true, [ + 'deriveKey', + 'deriveBits', + ])) as CryptoKeyPair; + + expect((aliceKeys.publicKey as CryptoKey).algorithm.name).to.equal('X448'); + expect((aliceKeys.privateKey as CryptoKey).algorithm.name).to.equal('X448'); + + // 2. Export Keys + const alicePubSpki = await subtle.exportKey( + format, + aliceKeys.publicKey as CryptoKey, + ); + const bobPubSpki = await subtle.exportKey( + format, + bobKeys.publicKey as CryptoKey, + ); + + // 3. Import Keys + const alicePubImported = await subtle.importKey( + format, + alicePubSpki, + algorithm, + true, + [], + ); + + const bobPubImported = await subtle.importKey( + format, + bobPubSpki, + algorithm, + true, + [], + ); + + // 4. Derive Bits + const bitsLength = 448; // X448 produces 56 bytes + const aliceShared = await subtle.deriveBits( + { name: 'X448', public: bobPubImported } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + aliceKeys.privateKey as CryptoKey, + bitsLength, + ); + + const bobShared = await subtle.deriveBits( + { name: 'X448', public: alicePubImported } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + bobKeys.privateKey as CryptoKey, + bitsLength, + ); + + // Verify shared secrets match + const aliceSharedView = new Uint8Array(aliceShared); + const bobSharedView = new Uint8Array(bobShared); + + expect(aliceSharedView.length).to.equal(56); + expect(aliceSharedView).to.deep.equal(bobSharedView); + }, +); diff --git a/package.json b/package.json index 375e266e..106f6f65 100644 --- a/package.json +++ b/package.json @@ -105,4 +105,4 @@ "dependencies": { "caniuse-lite": "^1.0.30001757" } -} +} \ No newline at end of file diff --git a/packages/react-native-quick-crypto/cpp/ed25519/HybridEdKeyPair.cpp b/packages/react-native-quick-crypto/cpp/ed25519/HybridEdKeyPair.cpp index a38ea677..97cfa186 100644 --- a/packages/react-native-quick-crypto/cpp/ed25519/HybridEdKeyPair.cpp +++ b/packages/react-native-quick-crypto/cpp/ed25519/HybridEdKeyPair.cpp @@ -14,14 +14,20 @@ std::shared_ptr HybridEdKeyPair::diffieHellman(const std::shared_pt using EVP_PKEY_ptr = std::unique_ptr; using EVP_PKEY_CTX_ptr = std::unique_ptr; + // Determine key type from curve name + int keyType = EVP_PKEY_X25519; + if (this->curve == "x448" || this->curve == "X448") { + keyType = EVP_PKEY_X448; + } + // 1. Create EVP_PKEY for private key (our key) - EVP_PKEY_ptr pkey_priv(EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, NULL, privateKey->data(), privateKey->size()), EVP_PKEY_free); + EVP_PKEY_ptr pkey_priv(EVP_PKEY_new_raw_private_key(keyType, NULL, privateKey->data(), privateKey->size()), EVP_PKEY_free); if (!pkey_priv) { throw std::runtime_error("Failed to create private key: " + getOpenSSLError()); } // 2. Create EVP_PKEY for public key (peer's key) - EVP_PKEY_ptr pkey_pub(EVP_PKEY_new_raw_public_key(EVP_PKEY_X25519, NULL, publicKey->data(), publicKey->size()), EVP_PKEY_free); + EVP_PKEY_ptr pkey_pub(EVP_PKEY_new_raw_public_key(keyType, NULL, publicKey->data(), publicKey->size()), EVP_PKEY_free); if (!pkey_pub) { throw std::runtime_error("Failed to create public key: " + getOpenSSLError()); } diff --git a/packages/react-native-quick-crypto/package.json b/packages/react-native-quick-crypto/package.json index a28c2729..99a39335 100644 --- a/packages/react-native-quick-crypto/package.json +++ b/packages/react-native-quick-crypto/package.json @@ -1,6 +1,6 @@ { "name": "react-native-quick-crypto", - "version": "1.0.1", + "version": "1.0.0", "description": "A fast implementation of Node's `crypto` module written in C/C++ JSI", "main": "lib/commonjs/index", "module": "lib/module/index", @@ -74,6 +74,7 @@ "dependencies": { "@craftzdog/react-native-buffer": "6.1.0", "events": "3.3.0", + "expo-build-properties": "0.14.6", "react-native-quick-base64": "2.2.2", "readable-stream": "4.5.2", "safe-buffer": "^5.2.1", @@ -86,7 +87,6 @@ "@types/readable-stream": "4.0.18", "del-cli": "7.0.0", "expo": "^54.0.25", - "expo-build-properties": "^1.0.0", "jest": "29.7.0", "nitro-codegen": "0.29.1", "react-native-builder-bob": "0.40.15", @@ -96,15 +96,11 @@ "react": "*", "react-native": "*", "react-native-nitro-modules": ">=0.29.1", - "expo": ">=48.0.0", - "expo-build-properties": "*" + "expo": ">=48.0.0" }, "peerDependenciesMeta": { "expo": { "optional": true - }, - "expo-build-properties": { - "optional": true } }, "release-it": { @@ -153,4 +149,4 @@ "nitro-codegen", "react-native-nitro-modules" ] -} +} \ No newline at end of file diff --git a/packages/react-native-quick-crypto/src/ed.ts b/packages/react-native-quick-crypto/src/ed.ts index 2ebd4ad0..bee1c9a7 100644 --- a/packages/react-native-quick-crypto/src/ed.ts +++ b/packages/react-native-quick-crypto/src/ed.ts @@ -361,3 +361,105 @@ export async function ed_generateKeyPairWebCrypto( return { publicKey, privateKey }; } + +export async function x_generateKeyPairWebCrypto( + type: 'x25519' | 'x448', + extractable: boolean, + keyUsages: KeyUsage[], +): Promise { + if (hasAnyNotIn(keyUsages, ['deriveKey', 'deriveBits'])) { + throw lazyDOMException(`Unsupported key usage for ${type}`, 'SyntaxError'); + } + + const publicUsages = getUsagesUnion(keyUsages); + const privateUsages = getUsagesUnion(keyUsages, 'deriveKey', 'deriveBits'); + + if (privateUsages.length === 0) { + throw lazyDOMException('Usages cannot be empty', 'SyntaxError'); + } + + // Request DER-encoded SPKI for public key, PKCS8 for private key + const config = { + publicFormat: KFormatType.DER, + publicType: KeyEncoding.SPKI, + privateFormat: KFormatType.DER, + privateType: KeyEncoding.PKCS8, + }; + const ed = new Ed(type, config); + await ed.generateKeyPair(); + + const algorithmName = type === 'x25519' ? 'X25519' : 'X448'; + + const publicKeyData = ed.getPublicKey(); + const privateKeyData = ed.getPrivateKey(); + + const pub = KeyObject.createKeyObject( + 'public', + publicKeyData, + KFormatType.DER, + KeyEncoding.SPKI, + ) as PublicKeyObject; + const publicKey = new CryptoKey( + pub, + { name: algorithmName } as SubtleAlgorithm, + publicUsages, + true, + ); + + const priv = KeyObject.createKeyObject( + 'private', + privateKeyData, + KFormatType.DER, + KeyEncoding.PKCS8, + ) as PrivateKeyObjectClass; + const privateKey = new CryptoKey( + priv, + { name: algorithmName } as SubtleAlgorithm, + privateUsages, + extractable, + ); + + return { publicKey, privateKey }; +} + +export function xDeriveBits( + algorithm: SubtleAlgorithm, + baseKey: CryptoKey, + length: number | null, +): ArrayBuffer { + const publicParams = algorithm as SubtleAlgorithm & { public?: CryptoKey }; + const publicKey = publicParams.public; + + if (!publicKey) { + throw new Error('Public key is required for X25519/X448 derivation'); + } + + if (baseKey.algorithm.name !== publicKey.algorithm.name) { + throw new Error('Keys must be of the same algorithm'); + } + + const type = baseKey.algorithm.name.toLowerCase() as 'x25519' | 'x448'; + const ed = new Ed(type, {}); + + // Export raw keys + const privateKeyBytes = baseKey.keyObject.handle.exportKey(); + const publicKeyBytes = publicKey.keyObject.handle.exportKey(); + + const privateKeyTyped = new Uint8Array(privateKeyBytes); + const publicKeyTyped = new Uint8Array(publicKeyBytes); + + const secret = ed.getSharedSecret(privateKeyTyped, publicKeyTyped); + + // If length is null, return the full secret + if (length === null) { + return secret; + } + + // If length is specified, truncate + const byteLength = Math.ceil(length / 8); + if (secret.byteLength >= byteLength) { + return secret.slice(0, byteLength); + } + + throw new Error('Derived key is shorter than requested length'); +} diff --git a/packages/react-native-quick-crypto/src/subtle.ts b/packages/react-native-quick-crypto/src/subtle.ts index 540dc270..f4b3f2c5 100644 --- a/packages/react-native-quick-crypto/src/subtle.ts +++ b/packages/react-native-quick-crypto/src/subtle.ts @@ -41,7 +41,12 @@ import { rsa_generateKeyPair } from './rsa'; import { getRandomValues } from './random'; import { createHmac } from './hmac'; import { createSign, createVerify } from './keys/signVerify'; -import { ed_generateKeyPairWebCrypto, Ed } from './ed'; +import { + ed_generateKeyPairWebCrypto, + x_generateKeyPairWebCrypto, + xDeriveBits, + Ed, +} from './ed'; import { mldsa_generateKeyPairWebCrypto, type MlDsaVariant } from './mldsa'; // import { pbkdf2DeriveBits } from './pbkdf2'; // import { aesCipher, aesGenerateKey, aesImportKey, getAlgorithmName } from './aes'; @@ -737,7 +742,12 @@ function edImportKey( const { name } = algorithm; // Validate usages - if (hasAnyNotIn(keyUsages, ['sign', 'verify'])) { + const isX = name === 'X25519' || name === 'X448'; + const allowedUsages: KeyUsage[] = isX + ? ['deriveKey', 'deriveBits'] + : ['sign', 'verify']; + + if (hasAnyNotIn(keyUsages, allowedUsages)) { throw lazyDOMException( `Unsupported key usage for ${name} key`, 'SyntaxError', @@ -853,8 +863,12 @@ const exportKeySpki = async ( case 'Ed25519': // Fall through case 'Ed448': + // Fall through + case 'X25519': + // Fall through + case 'X448': if (key.type === 'public') { - // Export Ed key in SPKI DER format + // Export Ed/X key in SPKI DER format return bufferLikeToArrayBuffer( key.keyObject.handle.exportKey(KFormatType.DER, KeyEncoding.SPKI), ); @@ -902,8 +916,12 @@ const exportKeyPkcs8 = async ( case 'Ed25519': // Fall through case 'Ed448': + // Fall through + case 'X25519': + // Fall through + case 'X448': if (key.type === 'private') { - // Export Ed key in PKCS8 DER format + // Export Ed/X key in PKCS8 DER format return bufferLikeToArrayBuffer( key.keyObject.handle.exportKey(KFormatType.DER, KeyEncoding.PKCS8), ); @@ -937,6 +955,19 @@ const exportKeyRaw = (key: CryptoKey): ArrayBuffer | unknown => { return ecExportKey(key, KWebCryptoKeyFormat.kWebCryptoKeyFormatRaw); } break; + case 'Ed25519': + // Fall through + case 'Ed448': + // Fall through + case 'X25519': + // Fall through + case 'X448': + if (key.type === 'public') { + // Export raw public key + const exported = key.keyObject.handle.exportKey(); + return bufferLikeToArrayBuffer(exported); + } + break; case 'AES-CTR': // Fall through case 'AES-CBC': @@ -1333,6 +1364,10 @@ export class Subtle { switch (algorithm.name) { case 'PBKDF2': return pbkdf2DeriveBits(algorithm, baseKey, length); + case 'X25519': + // Fall through + case 'X448': + return xDeriveBits(algorithm, baseKey, length); } throw new Error( `'subtle.deriveBits()' for ${algorithm.name} is not implemented.`, @@ -1436,6 +1471,16 @@ export class Subtle { ); checkCryptoKeyPairUsages(result as CryptoKeyPair); break; + case 'X25519': + // Fall through + case 'X448': + result = await x_generateKeyPairWebCrypto( + algorithm.name.toLowerCase() as 'x25519' | 'x448', + extractable, + keyUsages, + ); + checkCryptoKeyPairUsages(result as CryptoKeyPair); + break; default: throw new Error( `'subtle.generateKey()' is not implemented for ${algorithm.name}. @@ -1513,6 +1558,10 @@ export class Subtle { keyUsages, ); break; + case 'X25519': + // Fall through + case 'X448': + // Fall through case 'Ed25519': // Fall through case 'Ed448': From faf6541db5dfa541c5358a9399cf2eca187f2253 Mon Sep 17 00:00:00 2001 From: Athex Web3 Date: Thu, 4 Dec 2025 17:36:55 +0600 Subject: [PATCH 02/11] feat: add ChaCha20-Poly1305 support Add support for ChaCha20-Poly1305 AEAD cipher to crypto.subtle API. Changes: - Add ChaCha20Poly1305Params type definitions - Implement chaCha20Poly1305Cipher for encrypt/decrypt - Add support to generateKey, importKey, exportKey - Add 13 comprehensive tests including RFC 8439 vectors - Test coverage: key operations, AAD, authentication failures, edge cases --- example/src/tests/subtle/encrypt_decrypt.ts | 343 ++++++++++++++++++ .../react-native-quick-crypto/src/subtle.ts | 125 +++++++ .../src/utils/types.ts | 13 +- 3 files changed, 479 insertions(+), 2 deletions(-) diff --git a/example/src/tests/subtle/encrypt_decrypt.ts b/example/src/tests/subtle/encrypt_decrypt.ts index 4e9764be..5f0fc0a7 100644 --- a/example/src/tests/subtle/encrypt_decrypt.ts +++ b/example/src/tests/subtle/encrypt_decrypt.ts @@ -529,6 +529,349 @@ test( }, ); +// Test ChaCha20-Poly1305 +test(SUITE, 'ChaCha20-Poly1305', async () => { + const buf = getRandomValues(new Uint8Array(50)); + const iv = getRandomValues(new Uint8Array(12)); // 96-bit nonce + + const key = await subtle.generateKey( + { + name: 'ChaCha20-Poly1305', + length: 256, + } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + true, + ['encrypt', 'decrypt'], + ); + + const ciphertext = await subtle.encrypt( + { name: 'ChaCha20-Poly1305', iv } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + key as CryptoKey, + buf, + ); + + const plaintext = await subtle.decrypt( + { name: 'ChaCha20-Poly1305', iv } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + key as CryptoKey, + ciphertext, + ); + + expect(Buffer.from(plaintext).toString('hex')).to.equal( + Buffer.from(buf as Uint8Array).toString('hex'), + ); +}); + +// Test ChaCha20-Poly1305 with AAD +test(SUITE, 'ChaCha20-Poly1305 with AAD', async () => { + const plaintext = getRandomValues(new Uint8Array(32)); + const iv = getRandomValues(new Uint8Array(12)); + const aad = getRandomValues(new Uint8Array(16)); + + const key = await subtle.generateKey( + { name: 'ChaCha20-Poly1305', length: 256 } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + true, + ['encrypt', 'decrypt'], + ); + + const ciphertext = await subtle.encrypt( + { name: 'ChaCha20-Poly1305', iv, additionalData: aad } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + key as CryptoKey, + plaintext, + ); + + const decrypted = await subtle.decrypt( + { name: 'ChaCha20-Poly1305', iv, additionalData: aad } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + key as CryptoKey, + ciphertext, + ); + + expect(Buffer.from(decrypted).toString('hex')).to.equal( + Buffer.from(plaintext as Uint8Array).toString('hex'), + ); +}); + +// RFC 8439 test vector for ChaCha20-Poly1305 +test(SUITE, 'ChaCha20-Poly1305 RF C 8439 vector', async () => { + const keyHex = + '808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f'; + const nonceHex = '070000004041424344454647'; + const plaintextHex = + '4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a204966204920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73637265656e20776f756c642062652069742e'; + const aadHex = '50515253c0c1c2c3c4c5c6c7'; + const expectedCiphertextHex = + 'd31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b6116'; + const expectedTagHex = '1ae10b594f09e26a7e902ecbd0600691'; + + const key = await subtle.importKey( + 'raw', + Buffer.from(keyHex, 'hex'), + { name: 'ChaCha20-Poly1305' } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + true, + ['encrypt', 'decrypt'], + ); + + const ciphertext = await subtle.encrypt( + { + name: 'ChaCha20-Poly1305', + iv: Buffer.from(nonceHex, 'hex'), + additionalData: Buffer.from(aadHex, 'hex'), + } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + key as CryptoKey, + Buffer.from(plaintextHex, 'hex'), + ); + + // Ciphertext includes tag at the end (last 16 bytes) + const ctWithTag = Buffer.from(ciphertext); + const ct = ctWithTag.subarray(0, -16); + const tag = ctWithTag.subarray(-16); + + expect(ct.toString('hex')).to.equal(expectedCiphertextHex); + expect(tag.toString('hex')).to.equal(expectedTagHex); +}); + +// ChaCha20-Poly1305 comprehensive tests (similar to AES) +test(SUITE, 'ChaCha20-Poly1305 wrong key usage encrypt', async () => { + const key = await subtle.generateKey( + { name: 'ChaCha20-Poly1305', length: 256 } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + true, + ['decrypt'], // Only decrypt, not encrypt + ); + + await assertThrowsAsync( + async () => + await subtle.encrypt( + { + name: 'ChaCha20-Poly1305', + iv: getRandomValues(new Uint8Array(12)), + } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + key as CryptoKey, + getRandomValues(new Uint8Array(32)), + ), + 'The requested operation is not valid for the provided key', + ); +}); + +test(SUITE, 'ChaCha20-Poly1305 wrong key usage decrypt', async () => { + const key = await subtle.generateKey( + { name: 'ChaCha20-Poly1305', length: 256 } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + true, + ['encrypt'], // Only encrypt, not decrypt + ); + + const iv = getRandomValues(new Uint8Array(12)); + const plaintext = getRandomValues(new Uint8Array(32)); + + const ciphertext = await subtle.encrypt( + { name: 'ChaCha20-Poly1305', iv } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + key as CryptoKey, + plaintext, + ); + + await assertThrowsAsync( + async () => + await subtle.decrypt( + { name: 'ChaCha20-Poly1305', iv } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + key as CryptoKey, + ciphertext, + ), + 'The requested operation is not valid for the provided key', + ); +}); + +test(SUITE, 'ChaCha20-Poly1305 invalid IV length', async () => { + const key = await subtle.generateKey( + { name: 'ChaCha20-Poly1305', length: 256 } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + true, + ['encrypt', 'decrypt'], + ); + + // Test with wrong IV lengths + const invalidIVs = [ + getRandomValues(new Uint8Array(8)), // Too short + getRandomValues(new Uint8Array(16)), // Too long + getRandomValues(new Uint8Array(24)), // Way too long + ]; + + for (const iv of invalidIVs) { + await assertThrowsAsync( + async () => + await subtle.encrypt( + { name: 'ChaCha20-Poly1305', iv } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + key as CryptoKey, + getRandomValues(new Uint8Array(32)), + ), + 'ChaCha20-Poly1305 IV must be exactly 12 bytes', + ); + } +}); + +test(SUITE, 'ChaCha20-Poly1305 empty plaintext', async () => { + const key = await subtle.generateKey( + { name: 'ChaCha20-Poly1305', length: 256 } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + true, + ['encrypt', 'decrypt'], + ); + + const iv = getRandomValues(new Uint8Array(12)); + const plaintext = new Uint8Array(0); // Empty + + const ciphertext = await subtle.encrypt( + { name: 'ChaCha20-Poly1305', iv } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + key as CryptoKey, + plaintext, + ); + + // Should still include auth tag (16 bytes) + expect(ciphertext.byteLength).to.equal(16); + + const decrypted = await subtle.decrypt( + { name: 'ChaCha20-Poly1305', iv } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + key as CryptoKey, + ciphertext, + ); + + expect(decrypted.byteLength).to.equal(0); +}); + +test(SUITE, 'ChaCha20-Poly1305 large plaintext', async () => { + const key = await subtle.generateKey( + { name: 'ChaCha20-Poly1305', length: 256 } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + true, + ['encrypt', 'decrypt'], + ); + + const iv = getRandomValues(new Uint8Array(12)); + const plaintext = getRandomValues(new Uint8Array(1024 * 64)); // 64KB + + const ciphertext = await subtle.encrypt( + { name: 'ChaCha20-Poly1305', iv } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + key as CryptoKey, + plaintext, + ); + + // Ciphertext = plaintext + 16-byte tag + expect(ciphertext.byteLength).to.equal(plaintext.byteLength + 16); + + const decrypted = await subtle.decrypt( + { name: 'ChaCha20-Poly1305', iv } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + key as CryptoKey, + ciphertext, + ); + + expect(Buffer.from(decrypted).toString('hex')).to.equal( + Buffer.from(plaintext as Uint8Array).toString('hex'), + ); +}); + +test(SUITE, 'ChaCha20-Poly1305 key import/export raw', async () => { + const keyMaterial = getRandomValues(new Uint8Array(32)); // 256 bits + + const key = await subtle.importKey( + 'raw', + keyMaterial, + { name: 'ChaCha20-Poly1305' } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + true, + ['encrypt', 'decrypt'], + ); + + const exported = await subtle.exportKey('raw', key as CryptoKey); + + expect(Buffer.from(exported as ArrayBuffer).toString('hex')).to.equal( + Buffer.from(keyMaterial as Uint8Array).toString('hex'), + ); +}); + +test(SUITE, 'ChaCha20-Poly1305 tampered ciphertext', async () => { + const key = await subtle.generateKey( + { name: 'ChaCha20-Poly1305', length: 256 } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + true, + ['encrypt', 'decrypt'], + ); + + const iv = getRandomValues(new Uint8Array(12)); + const plaintext = getRandomValues(new Uint8Array(32)); + + const ciphertext = await subtle.encrypt( + { name: 'ChaCha20-Poly1305', iv } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + key as CryptoKey, + plaintext, + ); + + // Tamper with the ciphertext + const tamperedCiphertext = new Uint8Array(ciphertext); + tamperedCiphertext[0]! ^= 1; // Flip a bit + + await assertThrowsAsync( + async () => + await subtle.decrypt( + { name: 'ChaCha20-Poly1305', iv } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + key as CryptoKey, + tamperedCiphertext, + ), + 'Failed to finalize', + ); +}); + +test(SUITE, 'ChaCha20-Poly1305 tampered tag', async () => { + const key = await subtle.generateKey( + { name: 'ChaCha20-Poly1305', length: 256 } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + true, + ['encrypt', 'decrypt'], + ); + + const iv = getRandomValues(new Uint8Array(12)); + const plaintext = getRandomValues(new Uint8Array(32)); + + const ciphertext = await subtle.encrypt( + { name: 'ChaCha20-Poly1305', iv } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + key as CryptoKey, + plaintext, + ); + + // Tamper with the auth tag (last 16 bytes) + const tamperedCiphertext = new Uint8Array(ciphertext); + tamperedCiphertext[tamperedCiphertext.length - 1]! ^= 1; // Flip a bit in tag + + await assertThrowsAsync( + async () => + await subtle.decrypt( + { name: 'ChaCha20-Poly1305', iv } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + key as CryptoKey, + tamperedCiphertext, + ), + 'Failed to finalize', + ); +}); + +test(SUITE, 'ChaCha20-Poly1305 wrong AAD', async () => { + const key = await subtle.generateKey( + { name: 'ChaCha20-Poly1305', length: 256 } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + true, + ['encrypt', 'decrypt'], + ); + + const iv = getRandomValues(new Uint8Array(12)); + const plaintext = getRandomValues(new Uint8Array(32)); + const aad1 = getRandomValues(new Uint8Array(16)); + const aad2 = getRandomValues(new Uint8Array(16)); + + const ciphertext = await subtle.encrypt( + { name: 'ChaCha20-Poly1305', iv, additionalData: aad1 } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + key as CryptoKey, + plaintext, + ); + + // Try to decrypt with different AAD + await assertThrowsAsync( + async () => + await subtle.decrypt( + { name: 'ChaCha20-Poly1305', iv, additionalData: aad2 } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + key as CryptoKey, + ciphertext, + ), + 'Failed to finalize', + ); +}); + // from https://github.com/nodejs/node/blob/main/test/parallel/test-webcrypto-encrypt-decrypt-aes.js async function testAESEncrypt({ keyBuffer, diff --git a/packages/react-native-quick-crypto/src/subtle.ts b/packages/react-native-quick-crypto/src/subtle.ts index f4b3f2c5..d11d4e09 100644 --- a/packages/react-native-quick-crypto/src/subtle.ts +++ b/packages/react-native-quick-crypto/src/subtle.ts @@ -15,6 +15,7 @@ import type { AesCbcParams, AesGcmParams, RsaOaepParams, + ChaCha20Poly1305Params, } from './utils'; import { KFormatType, KeyEncoding } from './utils'; import { @@ -374,6 +375,97 @@ async function aesGcmCipher( } } +async function chaCha20Poly1305Cipher( + mode: CipherOrWrapMode, + key: CryptoKey, + data: ArrayBuffer, + algorithm: ChaCha20Poly1305Params, +): Promise { + const { iv, additionalData, tagLength = 128 } = algorithm; + + // Validate IV (must be 12 bytes for ChaCha20-Poly1305) + const ivBuffer = bufferLikeToArrayBuffer(iv); + if (!ivBuffer || ivBuffer.byteLength !== 12) { + throw lazyDOMException( + 'ChaCha20-Poly1305 IV must be exactly 12 bytes', + 'OperationError', + ); + } + + // Validate tag length (only 128-bit supported) + if (tagLength !== 128) { + throw lazyDOMException( + 'ChaCha20-Poly1305 only supports 128-bit auth tags', + 'NotSupportedError', + ); + } + + const tagByteLength = 16; // 128 bits = 16 bytes + + // Create cipher using existing ChaCha20-Poly1305 implementation + const factory = + NitroModules.createHybridObject('CipherFactory'); + const cipher = factory.createCipher({ + isCipher: mode === CipherOrWrapMode.kWebCryptoCipherEncrypt, + cipherType: 'chacha20-poly1305', + cipherKey: bufferLikeToArrayBuffer(key.keyObject.export()), + iv: ivBuffer, + authTagLen: tagByteLength, + }); + + let processData: ArrayBuffer; + let authTag: ArrayBuffer | undefined; + + if (mode === CipherOrWrapMode.kWebCryptoCipherDecrypt) { + // For decryption, extract auth tag from end of data + const dataView = new Uint8Array(data); + + if (dataView.byteLength < tagByteLength) { + throw lazyDOMException( + 'The provided data is too small.', + 'OperationError', + ); + } + + // Split data and tag + const ciphertextLength = dataView.byteLength - tagByteLength; + processData = dataView.slice(0, ciphertextLength).buffer; + authTag = dataView.slice(ciphertextLength).buffer; + + // Set auth tag for verification + cipher.setAuthTag(authTag); + } else { + processData = data; + } + + // Set additional authenticated data if provided + if (additionalData) { + cipher.setAAD(bufferLikeToArrayBuffer(additionalData)); + } + + // Process data + const updated = cipher.update(processData); + const final = cipher.final(); + + if (mode === CipherOrWrapMode.kWebCryptoCipherEncrypt) { + // For encryption, append auth tag to result + const tag = cipher.getAuthTag(); + const result = new Uint8Array( + updated.byteLength + final.byteLength + tag.byteLength, + ); + result.set(new Uint8Array(updated), 0); + result.set(new Uint8Array(final), updated.byteLength); + result.set(new Uint8Array(tag), updated.byteLength + final.byteLength); + return result.buffer; + } else { + // For decryption, just concatenate plaintext + const result = new Uint8Array(updated.byteLength + final.byteLength); + result.set(new Uint8Array(updated), 0); + result.set(new Uint8Array(final), updated.byteLength); + return result.buffer; + } +} + async function aesGenerateKey( algorithm: AesKeyGenParams, extractable: boolean, @@ -976,6 +1068,8 @@ const exportKeyRaw = (key: CryptoKey): ArrayBuffer | unknown => { // Fall through case 'AES-KW': // Fall through + case 'ChaCha20-Poly1305': + // Fall through case 'HMAC': { const exported = key.keyObject.export(); // Convert Buffer to ArrayBuffer @@ -1025,6 +1119,8 @@ const exportKeyJWK = (key: CryptoKey): ArrayBuffer | unknown => { case 'AES-GCM': // Fall through case 'AES-KW': + // Fall through + case 'ChaCha20-Poly1305': if (key.algorithm.length === undefined) { throw lazyDOMException( `Algorithm ${key.algorithm.name} missing required length property`, @@ -1321,6 +1417,13 @@ const cipherOrWrap = async ( // Fall through case 'AES-GCM': return aesCipher(mode, key, data, algorithm); + case 'ChaCha20-Poly1305': + return chaCha20Poly1305Cipher( + mode, + key, + data, + algorithm as ChaCha20Poly1305Params, + ); } }; @@ -1446,6 +1549,26 @@ export class Subtle { keyUsages, ); break; + case 'ChaCha20-Poly1305': { + const length = (algorithm as AesKeyGenParams).length ?? 256; + + if (length !== 256) { + throw lazyDOMException( + 'ChaCha20-Poly1305 only supports 256-bit keys', + 'NotSupportedError', + ); + } + + result = await aesGenerateKey( + { + name: 'ChaCha20-Poly1305', + length: 256, + } as unknown as AesKeyGenParams, + extractable, + keyUsages, + ); + break; + } case 'HMAC': result = await hmacGenerateKey(algorithm, extractable, keyUsages); break; @@ -1541,6 +1664,8 @@ export class Subtle { case 'AES-GCM': // Fall through case 'AES-KW': + // Fall through + case 'ChaCha20-Poly1305': result = await aesImportKey( normalizedAlgorithm, format, diff --git a/packages/react-native-quick-crypto/src/utils/types.ts b/packages/react-native-quick-crypto/src/utils/types.ts index 164a68d6..ecdda36f 100644 --- a/packages/react-native-quick-crypto/src/utils/types.ts +++ b/packages/react-native-quick-crypto/src/utils/types.ts @@ -106,7 +106,8 @@ export type EncryptDecryptAlgorithm = | 'RSA-OAEP' | 'AES-CTR' | 'AES-CBC' - | 'AES-GCM'; + | 'AES-GCM' + | 'ChaCha20-Poly1305'; export type RsaOaepParams = { name: 'RSA-OAEP'; @@ -131,6 +132,13 @@ export type AesGcmParams = { additionalData?: BufferLike; }; +export type ChaCha20Poly1305Params = { + name: 'ChaCha20-Poly1305'; + iv: BufferLike; + tagLength?: 128; + additionalData?: BufferLike; +}; + export type AesKwParams = { name: 'AES-KW'; wrappingKey?: BufferLike; @@ -149,7 +157,8 @@ export type EncryptDecryptParams = | AesCbcParams | AesCtrParams | AesGcmParams - | RsaOaepParams; + | RsaOaepParams + | ChaCha20Poly1305Params; export type AnyAlgorithm = | DigestAlgorithm From c8f90ca1c5c34cffe2f69798e3e404ffedfc8c8b Mon Sep 17 00:00:00 2001 From: Athex Web3 Date: Thu, 4 Dec 2025 18:06:46 +0600 Subject: [PATCH 03/11] feat: implement wrapKey, unwrapKey, and deriveKey WebCrypto methods Add support for three high-priority WebCrypto methods to crypto.subtle API. Changes: - Implement wrapKey() for secure key export and encryption - Implement unwrapKey() for decrypting and importing wrapped keys - Implement deriveKey() for key derivation (PBKDF2, X25519, X448) Key Technical Improvements: - Use cipherOrWrap() with correct operation flags to fix usage checks - Add AES-KW cipher support (aesKwCipher function) - Replace TextEncoder/TextDecoder with SBuffer for React Native compatibility - Call internal derivation functions (pbkdf2DeriveBits, xDeriveBits) directly Type Definitions: - Add 'wrapKey' and 'unwrapKey' to Operation type - Add 'AES-KW' to EncryptDecryptAlgorithm type - Add AesKwParams to EncryptDecryptParams union type Tests: - Add comprehensive wrapKey/unwrapKey tests (AES-KW, AES-GCM, JWK format) - Add deriveKey tests (PBKDF2, X25519) - All tests passing on iOS Docs: - Update implementation-coverage.md to reflect new features --- docs/implementation-coverage.md | 214 +++++++++--------- example/src/hooks/useTestsList.ts | 2 + example/src/tests/subtle/derive_key.ts | 100 ++++++++ example/src/tests/subtle/wrap_unwrap.ts | 151 ++++++++++++ .../react-native-quick-crypto/src/subtle.ts | 196 ++++++++++++++++ .../src/utils/types.ts | 6 +- 6 files changed, 559 insertions(+), 110 deletions(-) create mode 100644 example/src/tests/subtle/derive_key.ts create mode 100644 example/src/tests/subtle/wrap_unwrap.ts diff --git a/docs/implementation-coverage.md b/docs/implementation-coverage.md index 73ace9e2..4614861a 100644 --- a/docs/implementation-coverage.md +++ b/docs/implementation-coverage.md @@ -1,17 +1,11 @@ # Implementation Coverage - NodeJS This document attempts to describe the implementation status of Crypto APIs/Interfaces from Node.js in the `react-native-quick-crypto` library. +> Note: This is the status for version 1.x and higher. For version `0.x` see [this document](https://github.com/margelo/react-native-quick-crypto/blob/0.x/docs/implementation-coverage.md) and the [0.x branch](https://github.com/margelo/react-native-quick-crypto/tree/0.x). + * ` ` - not implemented in Node * ❌ - implemented in Node, not RNQC * ✅ - implemented in Node and RNQC -* 🚧 - work in progress - -## Post-Quantum Cryptography (PQC) - -- **ML-DSA** (Module Lattice Digital Signature Algorithm, FIPS 204) - ML-DSA-44, ML-DSA-65, ML-DSA-87 -- **ML-KEM** (Module Lattice Key Encapsulation Mechanism, FIPS 203) - ML-KEM-512, ML-KEM-768, ML-KEM-1024 - -These algorithms provide quantum-resistant cryptography. # `Crypto` @@ -153,11 +147,13 @@ These algorithms provide quantum-resistant cryptography. * ❌ `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.webcrypto` (see below) + * 🚧 `crypto.verify(algorithm, data, key, signature[, callback])` + * ❌ `crypto.webcrypto` (see below) + +🚧 Details below still a work in progress 🚧 ## `crypto.diffieHellman` | type | Status | @@ -208,22 +204,22 @@ These algorithms provide quantum-resistant cryptography. ## `crypto.sign` | Algorithm | Status | | --------- | :----: | -| `RSASSA-PKCS1-v1_5` | ✅ | -| `RSA-PSS` | ✅ | -| `ECDSA` | ✅ | +| `RSASSA-PKCS1-v1_5` | ❌ | +| `RSA-PSS` | ❌ | +| `ECDSA` | ❌ | | `Ed25519` | ✅ | | `Ed448` | ✅ | -| `HMAC` | ✅ | +| `HMAC` | ❌ | ## `crypto.verify` | Algorithm | Status | | --------- | :----: | -| `RSASSA-PKCS1-v1_5` | ✅ | -| `RSA-PSS` | ✅ | -| `ECDSA` | ✅ | +| `RSASSA-PKCS1-v1_5` | ❌ | +| `RSA-PSS` | ❌ | +| `ECDSA` | ❌ | | `Ed25519` | ✅ | | `Ed448` | ✅ | -| `HMAC` | ✅ | +| `HMAC` | ❌ | # `WebCrypto` @@ -244,13 +240,13 @@ These algorithms provide quantum-resistant cryptography. # `SubtleCrypto` -* ❌ Class: `SubtleCrypto` +* 🚧 Class: `SubtleCrypto` * ❌ static `supports(operation, algorithm[, lengthOrAdditionalAlgorithm])` * ❌ `subtle.decapsulateBits(decapsulationAlgorithm, decapsulationKey, ciphertext)` * ❌ `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)` @@ -259,10 +255,10 @@ These algorithms provide quantum-resistant cryptography. * 🚧 `subtle.generateKey(algorithm, extractable, keyUsages)` * ❌ `subtle.getPublicKey(key, keyUsages)` * 🚧 `subtle.importKey(format, keyData, algorithm, extractable, keyUsages)` - * ✅ `subtle.sign(algorithm, key, data)` - * ❌ `subtle.unwrapKey(format, wrappedKey, unwrappingKey, unwrapAlgo, unwrappedKeyAlgo, extractable, keyUsages)` - * ✅ `subtle.verify(algorithm, key, signature, data)` - * ❌ `subtle.wrapKey(format, key, wrappingKey, wrapAlgo)` + * 🚧 `subtle.sign(algorithm, key, data)` + * ✅ `subtle.unwrapKey(format, wrappedKey, unwrappingKey, unwrapAlgo, unwrappedKeyAlgo, extractable, keyUsages)` + * 🚧 `subtle.verify(algorithm, key, signature, data)` + * ✅ `subtle.wrapKey(format, key, wrappingKey, wrapAlgo)` ## `subtle.decrypt` | Algorithm | Status | @@ -286,9 +282,9 @@ These algorithms provide quantum-resistant cryptography. | --------- | :----: | | `ECDH` | ❌ | | `HKDF` | ❌ | -| `PBKDF2` | ❌ | -| `X25519` | ❌ | -| `X448` | ❌ | +| `PBKDF2` | ✅ | +| `X25519` | ✅ | +| `X448` | ✅ | ## `subtle.digest` | Algorithm | Status | @@ -310,32 +306,32 @@ These algorithms provide quantum-resistant cryptography. | `AES-CBC` | ✅ | | `AES-GCM` | ✅ | | `AES-OCB` | ❌ | -| `ChaCha20-Poly1305` | ❌ | +| `ChaCha20-Poly1305` | ✅ | | `RSA-OAEP` | ✅ | ## `subtle.exportKey` | Key Type | `spki` | `pkcs8` | `jwk` | `raw` | `raw-secret` | `raw-public` | `raw-seed` | -| ------------------- | :----: | :-----: | :---: | :---: | :----------: | :----------: | :--------: | -| `AES-CBC` | | | ✅ | ✅ | ✅ | | | -| `AES-CTR` | | | ✅ | ✅ | ✅ | | | -| `AES-GCM` | | | ✅ | ✅ | ✅ | | | -| `AES-KW` | | | ✅ | ✅ | ✅ | | | -| `AES-OCB` | | | ❌ | | ❌ | | | -| `ChaCha20-Poly1305` | | | ❌ | | ❌ | | | -| `ECDH` | ✅ | ✅ | ✅ | ✅ | | ✅ | | -| `ECDSA` | ✅ | ✅ | ✅ | ✅ | | ✅ | | -| `Ed25519` | ✅ | ✅ | ❌ | ❌ | | ❌ | | -| `Ed448` | ✅ | ✅ | ❌ | ❌ | | ❌ | | -| `HMAC` | | | ✅ | ✅ | ✅ | | | -| `ML-DSA-44` | ✅ | ✅ | ✅ | | | ✅ | ✅ | -| `ML-DSA-65` | ✅ | ✅ | ✅ | | | ✅ | ✅ | -| `ML-DSA-87` | ✅ | ✅ | ✅ | | | ✅ | ✅ | -| `ML-KEM-512` | ❌ | ❌ | | | | ❌ | ❌ | -| `ML-KEM-768` | ❌ | ❌ | | | | ❌ | ❌ | -| `ML-KEM-1024` | ❌ | ❌ | | | | ❌ | ❌ | -| `RSA-OAEP` | ✅ | ✅ | ✅ | | | | | -| `RSA-PSS` | ✅ | ✅ | ✅ | | | | | -| `RSASSA-PKCS1-v1_5` | ✅ | ✅ | ✅ | | | | | +| ------------------- | :----: | :-----: | :---: | :---: | :---: | :---: | :---: | +| `AES-CBC` | | | ✅ | ✅ | ✅ | | | +| `AES-CTR` | | | ✅ | ✅ | ✅ | | | +| `AES-GCM` | | | ✅ | ✅ | ✅ | | | +| `AES-KW` | | | ✅ | ✅ | ✅ | | | +| `AES-OCB` | | | ❌ | | ❌ | | | +| `ChaCha20-Poly1305` | | | ✅ | ✅ | ✅ | | | +| `ECDH` | ✅ | ✅ | ✅ | ✅ | | ✅ | | +| `ECDSA` | ✅ | ✅ | ✅ | ✅ | | ✅ | | +| `Ed25519` | ❌ | ❌ | ❌ | ❌ | | ❌ | | +| `Ed448` | ❌ | ❌ | ❌ | ❌ | | ❌ | | +| `HMAC` | | | ✅ | ✅ | ✅ | | | +| `ML-DSA-44` | ❌ | ❌ | ❌ | | | ❌ | ❌ | +| `ML-DSA-65` | ❌ | ❌ | ❌ | | | ❌ | ❌ | +| `ML-DSA-87` | ❌ | ❌ | ❌ | | | ❌ | ❌ | +| `ML-KEM-512` | ❌ | ❌ | | | | ❌ | ❌ | +| `ML-KEM-768` | ❌ | ❌ | | | | ❌ | ❌ | +| `ML-KEM-1024` | ❌ | ❌ | | | | ❌ | ❌ | +| `RSA-OAEP` | ✅ | ✅ | ✅ | | | | | +| `RSA-PSS` | ✅ | ✅ | ✅ | | | | | +| `RSASSA-PKCS1-v1_5` | ✅ | ✅ | ✅ | | | | | * ` ` - not implemented in Node * ❌ - implemented in Node, not RNQC @@ -350,9 +346,9 @@ These algorithms provide quantum-resistant cryptography. | `ECDSA` | ✅ | | `Ed25519` | ✅ | | `Ed448` | ✅ | -| `ML-DSA-44` | ✅ | -| `ML-DSA-65` | ✅ | -| `ML-DSA-87` | ✅ | +| `ML-DSA-44` | ❌ | +| `ML-DSA-65` | ❌ | +| `ML-DSA-87` | ❌ | | `ML-KEM-512` | ❌ | | `ML-KEM-768` | ❌ | | `ML-KEM-1024` | ❌ | @@ -368,64 +364,64 @@ These algorithms provide quantum-resistant cryptography. | `AES-CTR` | ✅ | | `AES-CBC` | ✅ | | `AES-GCM` | ✅ | -| `AES-KW` | ❌ | +| `AES-KW` | ✅ | | `AES-OCB` | ❌ | -| `ChaCha20-Poly1305` | ❌ | -| `HMAC` | ✅ | +| `ChaCha20-Poly1305` | ✅ | +| `HMAC` | ❌ | ## `subtle.importKey` | Key Type | `spki` | `pkcs8` | `jwk` | `raw` | `raw-secret` | `raw-public` | `raw-seed` | -| ------------------- | :----: | :-----: | :---: | :---: | :----------: | :----------: | :--------: | -| `AES-CBC` | | | ✅ | ✅ | ✅ | | | -| `AES-CTR` | | | ✅ | ✅ | ✅ | | | -| `AES-GCM` | | | ✅ | ✅ | ✅ | | | -| `AES-KW` | | | ✅ | ✅ | ✅ | | | -| `AES-OCB` | | | ❌ | | ❌ | | | -| `ChaCha20-Poly1305` | | | ❌ | | ❌ | | | -| `ECDH` | ✅ | ✅ | ✅ | ✅ | | ✅ | | -| `ECDSA` | ✅ | ✅ | ✅ | ✅ | | ✅ | | -| `Ed25519` | ✅ | ✅ | ❌ | ❌ | | ❌ | | -| `Ed448` | ✅ | ✅ | ❌ | ❌ | | ❌ | | -| `HKDF` | | | | ❌ | ❌ | | | -| `HMAC` | | | ✅ | ✅ | ✅ | | | -| `ML-DSA-44` | ✅ | ✅ | ✅ | | | ✅ | ✅ | -| `ML-DSA-65` | ✅ | ✅ | ✅ | | | ✅ | ✅ | -| `ML-DSA-87` | ✅ | ✅ | ✅ | | | ✅ | ✅ | -| `ML-KEM-512` | ❌ | ❌ | | | | ❌ | ❌ | -| `ML-KEM-768` | ❌ | ❌ | | | | ❌ | ❌ | -| `ML-KEM-1024` | ❌ | ❌ | | | | ❌ | ❌ | -| `PBKDF2` | | | | ✅ | ✅ | | | -| `RSA-OAEP` | ✅ | ✅ | ✅ | | | | | -| `RSA-PSS` | ✅ | ✅ | ✅ | | | | | -| `RSASSA-PKCS1-v1_5` | ✅ | ✅ | ✅ | | | | | -| `X25519` | ❌ | ❌ | ❌ | ❌ | | ❌ | | -| `X448` | ❌ | ❌ | ❌ | ❌ | | ❌ | | +| ------------------- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | +| `AES-CBC` | | | ✅ | ✅ | ✅ | | | +| `AES-CTR` | | | ✅ | ✅ | ✅ | | | +| `AES-GCM` | | | ✅ | ✅ | ✅ | | | +| `AES-KW` | | | ✅ | ✅ | ✅ | | | +| `AES-OCB` | | | ❌ | | ❌ | | | +| `ChaCha20-Poly1305` | | | ✅ | ✅ | ✅ | | | +| `ECDH` | ✅ | ✅ | ✅ | ✅ | | ✅ | | +| `ECDSA` | ✅ | ✅ | ✅ | ✅ | | ✅ | | +| `Ed25519` | ❌ | ❌ | ❌ | ❌ | | ❌ | | +| `Ed448` | ❌ | ❌ | ❌ | ❌ | | ❌ | | +| `HDKF` | | | | ❌ | ❌ | | | +| `HMAC` | | | ✅ | ✅ | ✅ | | | +| `ML-DSA-44` | ❌ | ❌ | ❌ | | | ❌ | ❌ | +| `ML-DSA-65` | ❌ | ❌ | ❌ | | | ❌ | ❌ | +| `ML-DSA-87` | ❌ | ❌ | ❌ | | | ❌ | ❌ | +| `ML-KEM-512` | ❌ | ❌ | | | | ❌ | ❌ | +| `ML-KEM-768` | ❌ | ❌ | | | | ❌ | ❌ | +| `ML-KEM-1024` | ❌ | ❌ | | | | ❌ | ❌ | +| `PBKDF2` | | | | ✅ | ✅ | | | +| `RSA-OAEP` | ✅ | ❌ | ✅ | | | | | +| `RSA-PSS` | ✅ | ❌ | ✅ | | | | | +| `RSASSA-PKCS1-v1_5` | ✅ | ❌ | ✅ | | | | | +| `X25519` | ❌ | ❌ | ❌ | ❌ | | ❌ | | +| `X448` | ❌ | ❌ | ❌ | ❌ | | ❌ | | ## `subtle.sign` | Algorithm | Status | | --------- | :----: | | `ECDSA` | ✅ | -| `Ed25519` | ✅ | -| `Ed448` | ✅ | -| `HMAC` | ✅ | -| `ML-DSA-44` | ✅ | -| `ML-DSA-65` | ✅ | -| `ML-DSA-87` | ✅ | -| `RSA-PSS` | ✅ | -| `RSASSA-PKCS1-v1_5` | ✅ | +| `Ed25519` | ❌ | +| `Ed448` | ❌ | +| `HMAC` | ❌ | +| `ML-DSA-44` | ❌ | +| `ML-DSA-65` | ❌ | +| `ML-DSA-87` | ❌ | +| `RSA-PSS` | ❌ | +| `RSASSA-PKCS1-v1_5` | ❌ | ## `subtle.unwrapKey` ### wrapping algorithms | Algorithm | Status | | ------------------- | :----: | -| `AES-CBC` | ❌ | -| `AES-CTR` | ❌ | -| `AES-GCM` | ❌ | -| `AES-KW` | ❌ | +| `AES-CBC` | ✅ | +| `AES-CTR` | ✅ | +| `AES-GCM` | ✅ | +| `AES-KW` | ✅ | | `AES-OCB` | ❌ | -| `ChaCha20-Poly1305` | ❌ | -| `RSA-OAEP` | ❌ | +| `ChaCha20-Poly1305` | ✅ | +| `RSA-OAEP` | ✅ | ### unwrapped key algorithms | Algorithm | Status | @@ -457,24 +453,24 @@ These algorithms provide quantum-resistant cryptography. | Algorithm | Status | | --------- | :----: | | `ECDSA` | ✅ | -| `Ed25519` | ✅ | -| `Ed448` | ✅ | -| `HMAC` | ✅ | -| `ML-DSA-44` | ✅ | -| `ML-DSA-65` | ✅ | -| `ML-DSA-87` | ✅ | -| `RSA-PSS` | ✅ | -| `RSASSA-PKCS1-v1_5` | ✅ | +| `Ed25519` | ❌ | +| `Ed448` | ❌ | +| `HMAC` | ❌ | +| `ML-DSA-44` | ❌ | +| `ML-DSA-65` | ❌ | +| `ML-DSA-87` | ❌ | +| `RSA-PSS` | ❌ | +| `RSASSA-PKCS1-v1_5` | ❌ | ## `subtle.wrapKey` ### wrapping algorithms | Algorithm | Status | | ------------------- | :----: | -| `AES-CBC` | ❌ | -| `AES-CTR` | ❌ | -| `AES-GCM` | ❌ | -| `AES-KW` | ❌ | +| `AES-CBC` | ✅ | +| `AES-CTR` | ✅ | +| `AES-GCM` | ✅ | +| `AES-KW` | ✅ | | `AES-OCB` | ❌ | -| `ChaCha20-Poly1305` | ❌ | -| `RSA-OAEP` | ❌ | +| `ChaCha20-Poly1305` | ✅ | +| `RSA-OAEP` | ✅ | diff --git a/example/src/hooks/useTestsList.ts b/example/src/hooks/useTestsList.ts index b8b9d702..de162bce 100644 --- a/example/src/hooks/useTestsList.ts +++ b/example/src/hooks/useTestsList.ts @@ -27,6 +27,8 @@ import '../tests/subtle/import_export'; import '../tests/subtle/jwk_rfc7517_tests'; import '../tests/subtle/sign_verify'; import '../tests/subtle/x25519_x448'; +import '../tests/subtle/wrap_unwrap'; +import '../tests/subtle/derive_key'; export const useTestsList = (): [ TestSuites, diff --git a/example/src/tests/subtle/derive_key.ts b/example/src/tests/subtle/derive_key.ts new file mode 100644 index 00000000..b91d0157 --- /dev/null +++ b/example/src/tests/subtle/derive_key.ts @@ -0,0 +1,100 @@ +import { test } from '../util'; +import { expect } from 'chai'; +import { subtle, getRandomValues } from 'react-native-quick-crypto'; +import { CryptoKey } from 'react-native-quick-crypto'; +import type { CryptoKeyPair } from 'react-native-quick-crypto'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const subtleAny = subtle as any; + +const SUITE = 'subtle.deriveKey'; + +// Test 1: PBKDF2 deriveKey +test(SUITE, 'PBKDF2 deriveKey to AES-GCM', async () => { + const password = new TextEncoder().encode('my-password'); + const salt = getRandomValues(new Uint8Array(16)); + + const baseKey = await subtle.importKey( + 'raw', + password, + { name: 'PBKDF2' }, + false, + ['deriveKey'], + ); + + const derivedKey = await subtleAny.deriveKey( + { + name: 'PBKDF2', + salt, + iterations: 100000, + hash: 'SHA-256', + }, + baseKey as CryptoKey, + { name: 'AES-GCM', length: 256 }, + true, + ['encrypt', 'decrypt'], + ); + + // Verify key can encrypt/decrypt + const plaintext = new Uint8Array([1, 2, 3, 4]); + const iv = getRandomValues(new Uint8Array(12)); + + const ciphertext = await subtle.encrypt( + { name: 'AES-GCM', iv }, + derivedKey as CryptoKey, + plaintext, + ); + + const decrypted = await subtle.decrypt( + { name: 'AES-GCM', iv }, + derivedKey as CryptoKey, + ciphertext, + ); + + expect(Buffer.from(decrypted).toString('hex')).to.equal( + Buffer.from(plaintext).toString('hex'), + ); +}); + +// Test 2: X25519 deriveKey +test(SUITE, 'X25519 deriveKey to AES-GCM', async () => { + const aliceKeyPair = await subtle.generateKey({ name: 'X25519' }, false, [ + 'deriveKey', + 'deriveBits', + ]); + + const bobKeyPair = await subtle.generateKey({ name: 'X25519' }, false, [ + 'deriveKey', + 'deriveBits', + ]); + + const aliceDerivedKey = await subtleAny.deriveKey( + { + name: 'X25519', + public: (bobKeyPair as CryptoKeyPair).publicKey, + }, + (aliceKeyPair as CryptoKeyPair).privateKey, + { name: 'AES-GCM', length: 256 }, + true, + ['encrypt', 'decrypt'], + ); + + const bobDerivedKey = await subtleAny.deriveKey( + { + name: 'X25519', + public: (aliceKeyPair as CryptoKeyPair).publicKey, + }, + (bobKeyPair as CryptoKeyPair).privateKey, + { name: 'AES-GCM', length: 256 }, + true, + ['encrypt', 'decrypt'], + ); + + // Both should derive the same key + const aliceRaw = await subtle.exportKey('raw', aliceDerivedKey as CryptoKey); + const bobRaw = await subtle.exportKey('raw', bobDerivedKey as CryptoKey); + + expect(Buffer.from(aliceRaw as ArrayBuffer).toString('hex')).to.equal( + Buffer.from(bobRaw as ArrayBuffer).toString('hex'), + ); +}); diff --git a/example/src/tests/subtle/wrap_unwrap.ts b/example/src/tests/subtle/wrap_unwrap.ts new file mode 100644 index 00000000..73e3bcca --- /dev/null +++ b/example/src/tests/subtle/wrap_unwrap.ts @@ -0,0 +1,151 @@ +import { test } from '../util'; +import { expect } from 'chai'; +import { subtle, getRandomValues } from 'react-native-quick-crypto'; +import { CryptoKey } from 'react-native-quick-crypto'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const subtleAny = subtle as any; + +const SUITE = 'subtle.wrapKey/unwrapKey'; + +// Test 1: Wrap/unwrap AES key with AES-KW +test(SUITE, 'wrap/unwrap AES-256 with AES-KW', async () => { + const keyToWrap = await subtle.generateKey( + { name: 'AES-GCM', length: 256 }, + true, + ['encrypt', 'decrypt'], + ); + + const wrappingKey = await subtle.generateKey( + { name: 'AES-KW', length: 256 }, + true, + ['wrapKey', 'unwrapKey'], + ); + + const wrapped = await subtleAny.wrapKey( + 'raw', + keyToWrap as CryptoKey, + wrappingKey as CryptoKey, + { name: 'AES-KW' }, + ); + + const unwrapped = await subtleAny.unwrapKey( + 'raw', + wrapped, + wrappingKey as CryptoKey, + { name: 'AES-KW' }, + { name: 'AES-GCM', length: 256 }, + true, + ['encrypt', 'decrypt'], + ); + + // Verify keys are functionally identical + const plaintext = getRandomValues(new Uint8Array(32)); + const iv = getRandomValues(new Uint8Array(12)); + + const ct1 = await subtle.encrypt( + { name: 'AES-GCM', iv }, + keyToWrap as CryptoKey, + plaintext, + ); + + const pt2 = await subtle.decrypt( + { name: 'AES-GCM', iv }, + unwrapped as CryptoKey, + ct1, + ); + + expect(Buffer.from(pt2).toString('hex')).to.equal( + Buffer.from(plaintext).toString('hex'), + ); +}); + +// Test 2: Wrap with AES-GCM +test(SUITE, 'wrap/unwrap with AES-GCM', async () => { + const keyToWrap = await subtle.generateKey( + { name: 'HMAC', hash: 'SHA-256', length: 256 }, + true, + ['sign', 'verify'], + ); + + const wrappingKey = await subtle.generateKey( + { name: 'AES-GCM', length: 256 }, + true, + ['wrapKey', 'unwrapKey'], + ); + + const iv = getRandomValues(new Uint8Array(12)); + + const wrapped = await subtleAny.wrapKey( + 'raw', + keyToWrap as CryptoKey, + wrappingKey as CryptoKey, + { name: 'AES-GCM', iv }, + ); + + const unwrapped = await subtleAny.unwrapKey( + 'raw', + wrapped, + wrappingKey as CryptoKey, + { name: 'AES-GCM', iv }, + { name: 'HMAC', hash: 'SHA-256' }, + true, + ['sign', 'verify'], + ); + + // Verify functionality + const data = new Uint8Array([1, 2, 3, 4]); + const sig1 = await subtle.sign( + { name: 'HMAC' }, + keyToWrap as CryptoKey, + data, + ); + const sig2 = await subtle.sign( + { name: 'HMAC' }, + unwrapped as CryptoKey, + data, + ); + + expect(Buffer.from(sig1).toString('hex')).to.equal( + Buffer.from(sig2).toString('hex'), + ); +}); + +// Test 3: Wrap/unwrap JWK format +test(SUITE, 'wrap/unwrap JWK format', async () => { + const keyToWrap = await subtle.generateKey( + { name: 'AES-CBC', length: 128 }, + true, + ['encrypt', 'decrypt'], + ); + + const wrappingKey = await subtle.generateKey( + { name: 'AES-KW', length: 256 }, + true, + ['wrapKey', 'unwrapKey'], + ); + + const wrapped = await subtleAny.wrapKey( + 'jwk', + keyToWrap as CryptoKey, + wrappingKey as CryptoKey, + { name: 'AES-KW' }, + ); + + const unwrapped = await subtleAny.unwrapKey( + 'jwk', + wrapped, + wrappingKey as CryptoKey, + { name: 'AES-KW' }, + { name: 'AES-CBC' }, + true, + ['encrypt', 'decrypt'], + ); + + const exported1 = await subtle.exportKey('raw', keyToWrap as CryptoKey); + const exported2 = await subtle.exportKey('raw', unwrapped as CryptoKey); + + expect(Buffer.from(exported1 as ArrayBuffer).toString('hex')).to.equal( + Buffer.from(exported2 as ArrayBuffer).toString('hex'), + ); +}); diff --git a/packages/react-native-quick-crypto/src/subtle.ts b/packages/react-native-quick-crypto/src/subtle.ts index d11d4e09..c890897d 100644 --- a/packages/react-native-quick-crypto/src/subtle.ts +++ b/packages/react-native-quick-crypto/src/subtle.ts @@ -375,6 +375,42 @@ async function aesGcmCipher( } } +async function aesKwCipher( + mode: CipherOrWrapMode, + key: CryptoKey, + data: ArrayBuffer, +): Promise { + // Get cipher type based on key length + const keyLength = (key.algorithm as { length: number }).length; + const isWrap = mode === CipherOrWrapMode.kWebCryptoCipherEncrypt; + const cipherType = isWrap + ? `id-aes${keyLength}-wrap` + : `id-aes${keyLength}-wrap`; + + // AES-KW uses the same cipher for both wrap and unwrap, + // but Node.js distinguishes with different cipher names + const factory = + NitroModules.createHybridObject('CipherFactory'); + + const cipher = factory.createCipher({ + isCipher: isWrap, + cipherType, + cipherKey: bufferLikeToArrayBuffer(key.keyObject.export()), + iv: new ArrayBuffer(0), // AES-KW doesn't use IV + }); + + // Process data + const updated = cipher.update(data); + const final = cipher.final(); + + // Concatenate results + const result = new Uint8Array(updated.byteLength + final.byteLength); + result.set(new Uint8Array(updated), 0); + result.set(new Uint8Array(final), updated.byteLength); + + return result.buffer; +} + async function chaCha20Poly1305Cipher( mode: CipherOrWrapMode, key: CryptoKey, @@ -1417,6 +1453,8 @@ const cipherOrWrap = async ( // Fall through case 'AES-GCM': return aesCipher(mode, key, data, algorithm); + case 'AES-KW': + return aesKwCipher(mode, key, data); case 'ChaCha20-Poly1305': return chaCha20Poly1305Cipher( mode, @@ -1477,6 +1515,57 @@ export class Subtle { ); } + async deriveKey( + algorithm: SubtleAlgorithm, + baseKey: CryptoKey, + derivedKeyAlgorithm: SubtleAlgorithm, + extractable: boolean, + keyUsages: KeyUsage[], + ): Promise { + // Validate baseKey usage + if ( + !baseKey.usages.includes('deriveKey') && + !baseKey.usages.includes('deriveBits') + ) { + throw lazyDOMException( + 'baseKey does not have deriveKey or deriveBits usage', + 'InvalidAccessError', + ); + } + + // Calculate required key length + const length = getKeyLength(derivedKeyAlgorithm); + + // Step 1: Derive bits + let derivedBits: ArrayBuffer; + if (baseKey.algorithm.name !== algorithm.name) + throw new Error('Key algorithm mismatch'); + + switch (algorithm.name) { + case 'PBKDF2': + derivedBits = await pbkdf2DeriveBits(algorithm, baseKey, length); + break; + case 'X25519': + // Fall through + case 'X448': + derivedBits = await xDeriveBits(algorithm, baseKey, length); + break; + default: + throw new Error( + `'subtle.deriveKey()' for ${algorithm.name} is not implemented.`, + ); + } + + // Step 2: Import as key + return this.importKey( + 'raw', + derivedBits, + derivedKeyAlgorithm, + extractable, + keyUsages, + ); + } + async encrypt( algorithm: EncryptDecryptParams, key: CryptoKey, @@ -1510,6 +1599,89 @@ export class Subtle { } } + async wrapKey( + format: ImportFormat, + key: CryptoKey, + wrappingKey: CryptoKey, + wrapAlgorithm: EncryptDecryptParams, + ): Promise { + // Validate wrappingKey usage + if (!wrappingKey.usages.includes('wrapKey')) { + throw lazyDOMException( + 'wrappingKey does not have wrapKey usage', + 'InvalidAccessError', + ); + } + + // Step 1: Export the key + const exported = await this.exportKey(format, key); + + // Step 2: Convert to ArrayBuffer if JWK + let keyData: ArrayBuffer; + if (format === 'jwk') { + const jwkString = JSON.stringify(exported); + const buffer = SBuffer.from(jwkString, 'utf8'); + keyData = bufferLikeToArrayBuffer(buffer); + } else { + keyData = exported as ArrayBuffer; + } + + // Step 3: Encrypt the exported key + return cipherOrWrap( + CipherOrWrapMode.kWebCryptoCipherEncrypt, + wrapAlgorithm, + wrappingKey, + keyData, + 'wrapKey', + ); + } + + async unwrapKey( + format: ImportFormat, + wrappedKey: BufferLike, + unwrappingKey: CryptoKey, + unwrapAlgorithm: EncryptDecryptParams, + unwrappedKeyAlgorithm: SubtleAlgorithm | AnyAlgorithm, + extractable: boolean, + keyUsages: KeyUsage[], + ): Promise { + // Validate unwrappingKey usage + if (!unwrappingKey.usages.includes('unwrapKey')) { + throw lazyDOMException( + 'unwrappingKey does not have unwrapKey usage', + 'InvalidAccessError', + ); + } + + // Step 1: Decrypt the wrapped key + const decrypted = await cipherOrWrap( + CipherOrWrapMode.kWebCryptoCipherDecrypt, + unwrapAlgorithm, + unwrappingKey, + bufferLikeToArrayBuffer(wrappedKey), + 'unwrapKey', + ); + + // Step 2: Convert to appropriate format + let keyData: BufferLike | JWK; + if (format === 'jwk') { + const buffer = SBuffer.from(decrypted); + const jwkString = buffer.toString('utf8'); + keyData = JSON.parse(jwkString) as JWK; + } else { + keyData = decrypted; + } + + // Step 3: Import the key + return this.importKey( + format, + keyData, + unwrappedKeyAlgorithm, + extractable, + keyUsages, + ); + } + async generateKey( algorithm: SubtleAlgorithm, extractable: boolean, @@ -1748,3 +1920,27 @@ export class Subtle { } export const subtle = new Subtle(); + +function getKeyLength(algorithm: SubtleAlgorithm): number { + const name = algorithm.name; + + switch (name) { + case 'AES-CTR': + case 'AES-CBC': + case 'AES-GCM': + case 'AES-KW': + case 'ChaCha20-Poly1305': + return (algorithm as AesKeyGenParams).length || 256; + + case 'HMAC': { + const hmacAlg = algorithm as { length?: number }; + return hmacAlg.length || 256; + } + + default: + throw lazyDOMException( + `Cannot determine key length for ${name}`, + 'NotSupportedError', + ); + } +} diff --git a/packages/react-native-quick-crypto/src/utils/types.ts b/packages/react-native-quick-crypto/src/utils/types.ts index ecdda36f..0c14c036 100644 --- a/packages/react-native-quick-crypto/src/utils/types.ts +++ b/packages/react-native-quick-crypto/src/utils/types.ts @@ -107,6 +107,7 @@ export type EncryptDecryptAlgorithm = | 'AES-CTR' | 'AES-CBC' | 'AES-GCM' + | 'AES-KW' | 'ChaCha20-Poly1305'; export type RsaOaepParams = { @@ -157,6 +158,7 @@ export type EncryptDecryptParams = | AesCbcParams | AesCtrParams | AesGcmParams + | AesKwParams | RsaOaepParams | ChaCha20Poly1305Params; @@ -455,7 +457,9 @@ export type Operation = | 'generateKey' | 'importKey' | 'exportKey' - | 'deriveBits'; + | 'deriveBits' + | 'wrapKey' + | 'unwrapKey'; export interface KeyPairOptions { namedCurve: string; From 245b0f78b2bae9b6a4b1c21165f0c0faa36def85 Mon Sep 17 00:00:00 2001 From: Athex Web3 Date: Thu, 4 Dec 2025 18:25:56 +0600 Subject: [PATCH 04/11] feat: implement HMAC sign/verify and fix X25519/X448 deriveBits HMAC: - Add HMAC support to subtle.sign() and subtle.verify() - Constant-time verification - Supports SHA-256 / SHA-384 / SHA-512 - Handles both string and object-style hash formats - Reuses existing createHmac() implementation (no C++ changes) deriveBits fix: - Allow both 'deriveBits' and 'deriveKey' usages - Enable X25519/X448 deriveBits() for deriveKey-usage keys - Align implementation with WebCrypto spec Tests: - Add full HMAC test suite (8 test cases) - Covers SHA-256/384/512, incorrect sig/data checks, usage validation Docs: - Updated implementation-coverage.md to mark HMAC + X25519/X448 deriveBits implemented --- docs/implementation-coverage.md | 8 +- example/src/hooks/useTestsList.ts | 1 + example/src/tests/subtle/hmac_sign_verify.ts | 204 ++++++++++++++++++ .../react-native-quick-crypto/src/subtle.ts | 101 ++++++++- 4 files changed, 305 insertions(+), 9 deletions(-) create mode 100644 example/src/tests/subtle/hmac_sign_verify.ts diff --git a/docs/implementation-coverage.md b/docs/implementation-coverage.md index 4614861a..fa0a9b49 100644 --- a/docs/implementation-coverage.md +++ b/docs/implementation-coverage.md @@ -272,8 +272,8 @@ This document attempts to describe the implementation status of Crypto APIs/Inte | Algorithm | Status | | --------- | :----: | | `ECDH` | ❌ | -| `X25519` | ❌ | -| `X448` | ❌ | +| `X25519` | ✅ | +| `X448` | ✅ | | `HKDF` | ❌ | | `PBKDF2` | ✅ | @@ -403,7 +403,7 @@ This document attempts to describe the implementation status of Crypto APIs/Inte | `ECDSA` | ✅ | | `Ed25519` | ❌ | | `Ed448` | ❌ | -| `HMAC` | ❌ | +| `HMAC` | ✅ | | `ML-DSA-44` | ❌ | | `ML-DSA-65` | ❌ | | `ML-DSA-87` | ❌ | @@ -455,7 +455,7 @@ This document attempts to describe the implementation status of Crypto APIs/Inte | `ECDSA` | ✅ | | `Ed25519` | ❌ | | `Ed448` | ❌ | -| `HMAC` | ❌ | +| `HMAC` | ✅ | | `ML-DSA-44` | ❌ | | `ML-DSA-65` | ❌ | | `ML-DSA-87` | ❌ | diff --git a/example/src/hooks/useTestsList.ts b/example/src/hooks/useTestsList.ts index de162bce..b516f9ba 100644 --- a/example/src/hooks/useTestsList.ts +++ b/example/src/hooks/useTestsList.ts @@ -29,6 +29,7 @@ import '../tests/subtle/sign_verify'; import '../tests/subtle/x25519_x448'; import '../tests/subtle/wrap_unwrap'; import '../tests/subtle/derive_key'; +import '../tests/subtle/hmac_sign_verify'; export const useTestsList = (): [ TestSuites, diff --git a/example/src/tests/subtle/hmac_sign_verify.ts b/example/src/tests/subtle/hmac_sign_verify.ts new file mode 100644 index 00000000..fe42f405 --- /dev/null +++ b/example/src/tests/subtle/hmac_sign_verify.ts @@ -0,0 +1,204 @@ +import { test } from '../util'; +import { expect } from 'chai'; +import { subtle, getRandomValues } from 'react-native-quick-crypto'; +import { CryptoKey } from 'react-native-quick-crypto'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const subtleAny = subtle as any; + +const SUITE = 'subtle.sign/verify HMAC'; + +// Test 1: Basic HMAC sign/verify with SHA-256 +test(SUITE, 'HMAC-SHA256 sign and verify', async () => { + const key = await subtle.generateKey( + { name: 'HMAC', hash: 'SHA-256', length: 256 }, + false, + ['sign', 'verify'], + ); + + const data = new Uint8Array([1, 2, 3, 4, 5]); + + const signature = await subtleAny.sign( + { name: 'HMAC' }, + key as CryptoKey, + data, + ); + + expect(signature).to.be.instanceOf(ArrayBuffer); + expect(signature.byteLength).to.equal(32); // SHA-256 = 32 bytes + + const valid = await subtleAny.verify( + { name: 'HMAC' }, + key as CryptoKey, + signature, + data, + ); + + expect(valid).to.equal(true); +}); + +// Test 2: HMAC with different hash algorithms +test(SUITE, 'HMAC with SHA-384', async () => { + const key = await subtle.generateKey( + { name: 'HMAC', hash: 'SHA-384' }, + false, + ['sign', 'verify'], + ); + + const data = new Uint8Array([1, 2, 3, 4]); + + const signature = await subtleAny.sign( + { name: 'HMAC' }, + key as CryptoKey, + data, + ); + + expect(signature.byteLength).to.equal(48); // SHA-384 = 48 bytes + + const valid = await subtleAny.verify( + { name: 'HMAC' }, + key as CryptoKey, + signature, + data, + ); + + expect(valid).to.equal(true); +}); + +// Test 3: HMAC with SHA-512 +test(SUITE, 'HMAC with SHA-512', async () => { + const key = await subtle.generateKey( + { name: 'HMAC', hash: 'SHA-512' }, + false, + ['sign', 'verify'], + ); + + const data = new Uint8Array([1, 2, 3, 4]); + + const signature = await subtleAny.sign( + { name: 'HMAC' }, + key as CryptoKey, + data, + ); + + expect(signature.byteLength).to.equal(64); // SHA-512 = 64 bytes + + const valid = await subtleAny.verify( + { name: 'HMAC' }, + key as CryptoKey, + signature, + data, + ); + + expect(valid).to.equal(true); +}); + +// Test 4: Verify with wrong signature +test(SUITE, 'HMAC verify fails with wrong signature', async () => { + const key = await subtle.generateKey( + { name: 'HMAC', hash: 'SHA-256' }, + false, + ['sign', 'verify'], + ); + + const data = new Uint8Array([1, 2, 3, 4]); + const wrongSignature = getRandomValues(new Uint8Array(32)); + + const valid = await subtleAny.verify( + { name: 'HMAC' }, + key as CryptoKey, + wrongSignature, + data, + ); + + expect(valid).to.equal(false); +}); + +// Test 5: Verify with wrong data +test(SUITE, 'HMAC verify fails with wrong data', async () => { + const key = await subtle.generateKey( + { name: 'HMAC', hash: 'SHA-256' }, + false, + ['sign', 'verify'], + ); + + const data = new Uint8Array([1, 2, 3, 4]); + const signature = await subtleAny.sign( + { name: 'HMAC' }, + key as CryptoKey, + data, + ); + + const wrongData = new Uint8Array([5, 6, 7, 8]); + const valid = await subtleAny.verify( + { name: 'HMAC' }, + key as CryptoKey, + signature, + wrongData, + ); + + expect(valid).to.equal(false); +}); + +// Test 6: Wrong key usage for sign +test(SUITE, 'HMAC sign fails without sign usage', async () => { + const key = await subtle.generateKey( + { name: 'HMAC', hash: 'SHA-256' }, + false, + ['verify'], // Only verify, not sign + ); + + const data = new Uint8Array([1, 2, 3, 4]); + + try { + await subtleAny.sign({ name: 'HMAC' }, key as CryptoKey, data); + expect.fail('Should have thrown'); + } catch (error) { + expect((error as Error).message).to.include('sign usage'); + } +}); + +// Test 7: Wrong key usage for verify +test(SUITE, 'HMAC verify fails without verify usage', async () => { + const key = await subtle.generateKey( + { name: 'HMAC', hash: 'SHA-256' }, + false, + ['sign'], // Only sign, not verify + ); + + const data = new Uint8Array([1, 2, 3, 4]); + const signature = getRandomValues(new Uint8Array(32)); + + try { + await subtleAny.verify({ name: 'HMAC' }, key as CryptoKey, signature, data); + expect.fail('Should have thrown'); + } catch (error) { + expect((error as Error).message).to.include('verify usage'); + } +}); + +// Test 8: Large data +test(SUITE, 'HMAC with large data', async () => { + const key = await subtle.generateKey( + { name: 'HMAC', hash: 'SHA-256' }, + false, + ['sign', 'verify'], + ); + + const data = getRandomValues(new Uint8Array(10000)); + + const signature = await subtleAny.sign( + { name: 'HMAC' }, + key as CryptoKey, + data, + ); + + const valid = await subtleAny.verify( + { name: 'HMAC' }, + key as CryptoKey, + signature, + data, + ); + + expect(valid).to.equal(true); +}); diff --git a/packages/react-native-quick-crypto/src/subtle.ts b/packages/react-native-quick-crypto/src/subtle.ts index c890897d..e591a1f5 100644 --- a/packages/react-native-quick-crypto/src/subtle.ts +++ b/packages/react-native-quick-crypto/src/subtle.ts @@ -1497,8 +1497,12 @@ export class Subtle { baseKey: CryptoKey, length: number, ): Promise { - if (!baseKey.keyUsages.includes('deriveBits')) { - throw new Error('baseKey does not have deriveBits usage'); + // Allow either deriveBits OR deriveKey usage (WebCrypto spec allows both) + if ( + !baseKey.keyUsages.includes('deriveBits') && + !baseKey.keyUsages.includes('deriveKey') + ) { + throw new Error('baseKey does not have deriveBits or deriveKey usage'); } if (baseKey.algorithm.name !== algorithm.name) throw new Error('Key algorithm mismatch'); @@ -1906,7 +1910,43 @@ export class Subtle { key: CryptoKey, data: BufferLike, ): Promise { - return signVerify(algorithm, key, data) as ArrayBuffer; + const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'sign'); + + if (normalizedAlgorithm.name === 'HMAC') { + // Validate key usage + if (!key.usages.includes('sign')) { + throw lazyDOMException( + 'Key does not have sign usage', + 'InvalidAccessError', + ); + } + + // Get hash algorithm from key or algorithm params + // Hash can be either a string or an object with name property + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const alg = normalizedAlgorithm as any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const keyAlg = key.algorithm as any; + let hashAlgorithm = 'SHA-256'; + + if (typeof alg.hash === 'string') { + hashAlgorithm = alg.hash; + } else if (alg.hash?.name) { + hashAlgorithm = alg.hash.name; + } else if (typeof keyAlg.hash === 'string') { + hashAlgorithm = keyAlg.hash; + } else if (keyAlg.hash?.name) { + hashAlgorithm = keyAlg.hash.name; + } + + // Create HMAC and sign + const keyData = key.keyObject.export(); + const hmac = createHmac(hashAlgorithm, keyData); + hmac.update(bufferLikeToArrayBuffer(data)); + return bufferLikeToArrayBuffer(hmac.digest()); + } + + return signVerify(normalizedAlgorithm, key, data) as ArrayBuffer; } async verify( @@ -1914,8 +1954,59 @@ export class Subtle { key: CryptoKey, signature: BufferLike, data: BufferLike, - ): Promise { - return signVerify(algorithm, key, data, signature) as ArrayBuffer; + ): Promise { + const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'verify'); + + if (normalizedAlgorithm.name === 'HMAC') { + // Validate key usage + if (!key.usages.includes('verify')) { + throw lazyDOMException( + 'Key does not have verify usage', + 'InvalidAccessError', + ); + } + + // Get hash algorithm + // Hash can be either a string or an object with name property + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const alg = normalizedAlgorithm as any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const keyAlg = key.algorithm as any; + let hashAlgorithm = 'SHA-256'; + + if (typeof alg.hash === 'string') { + hashAlgorithm = alg.hash; + } else if (alg.hash?.name) { + hashAlgorithm = alg.hash.name; + } else if (typeof keyAlg.hash === 'string') { + hashAlgorithm = keyAlg.hash; + } else if (keyAlg.hash?.name) { + hashAlgorithm = keyAlg.hash.name; + } + + // Create HMAC and compute expected signature + const keyData = key.keyObject.export(); + const hmac = createHmac(hashAlgorithm, keyData); + const dataBuffer = bufferLikeToArrayBuffer(data); + hmac.update(dataBuffer); + const expectedDigest = hmac.digest(); + const expected = new Uint8Array(bufferLikeToArrayBuffer(expectedDigest)); + + // Constant-time comparison + const signatureArray = new Uint8Array(bufferLikeToArrayBuffer(signature)); + if (expected.length !== signatureArray.length) { + return false; + } + + // Manual constant-time comparison + let result = 0; + for (let i = 0; i < expected.length; i++) { + result |= expected[i]! ^ signatureArray[i]!; + } + return result === 0; + } + + return signVerify(normalizedAlgorithm, key, data, signature) as boolean; } } From e8e60b3cfbe24023f5e6b88362002ce4559a5dac Mon Sep 17 00:00:00 2001 From: Athex Web3 Date: Thu, 4 Dec 2025 18:34:36 +0600 Subject: [PATCH 05/11] chore: use workspace protocol for example app dependency --- example/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/package.json b/example/package.json index 460eaef2..22d68779 100644 --- a/example/package.json +++ b/example/package.json @@ -38,7 +38,7 @@ "react-native-bouncy-checkbox": "4.1.2", "react-native-nitro-modules": "0.29.1", "react-native-quick-base64": "2.2.2", - "react-native-quick-crypto": "1.0.0", + "react-native-quick-crypto": "workspace:*", "react-native-safe-area-context": "^5.2.2", "react-native-screens": "4.18.0", "react-native-vector-icons": "^10.3.0", From de37c5d80999117713a1481ac283213dc876c172 Mon Sep 17 00:00:00 2001 From: Athex Web3 Date: Fri, 5 Dec 2025 20:58:48 +0600 Subject: [PATCH 06/11] fix: address review feedback from rebase Cleaned up a few things after the rebase onto v1.0.1: - Restored implementation-coverage.md properly (was too aggressive, deleted upstream's green checks) - Fixed XCode project path (../../ -> ../../../ node_modules) - Restored Podfile.lock from upstream (had old OpenSSL reference) - Moved expo-build-properties to devDeps + peerDeps where it belongs - Removed duplicate HMAC tests (upstream already has them in sign_verify.ts) - Renamed x25519/x448 test suite to 'cfrg' (Crypto Forum Research Group) - Fixed package versions (1.0.0 -> 1.0.1) - Added back jose dependency that got lost in rebase - Added missing line endings No code duplication - our HMAC implementation in sign()/verify() is separate from upstream's HMAC in generateKey()/importKey(). They work together. All static checks passing now. --- .gitignore | 2 +- docs/implementation-coverage.md | 175 +++++---- example/ios/Podfile.lock | 348 ++++++++++-------- .../project.pbxproj | 4 +- example/package.json | 5 +- example/src/hooks/useTestsList.ts | 1 - example/src/tests/subtle/hmac_sign_verify.ts | 204 ---------- example/src/tests/subtle/x25519_x448.ts | 2 +- package.json | 2 +- .../react-native-quick-crypto/package.json | 10 +- 10 files changed, 290 insertions(+), 463 deletions(-) delete mode 100644 example/src/tests/subtle/hmac_sign_verify.ts diff --git a/.gitignore b/.gitignore index 9aefc126..3ea4b7ba 100644 --- a/.gitignore +++ b/.gitignore @@ -187,4 +187,4 @@ tsconfig.tsbuildinfo # development stuffs *scratch* -.agent/ \ No newline at end of file +.agent/ diff --git a/docs/implementation-coverage.md b/docs/implementation-coverage.md index fa0a9b49..606b7517 100644 --- a/docs/implementation-coverage.md +++ b/docs/implementation-coverage.md @@ -147,13 +147,11 @@ 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.webcrypto` (see below) - -🚧 Details below still a work in progress 🚧 + * ✅ `crypto.verify(algorithm, data, key, signature[, callback])` + * 🚧 `crypto.webcrypto` (see below) ## `crypto.diffieHellman` | type | Status | @@ -204,22 +202,22 @@ This document attempts to describe the implementation status of Crypto APIs/Inte ## `crypto.sign` | Algorithm | Status | | --------- | :----: | -| `RSASSA-PKCS1-v1_5` | ❌ | -| `RSA-PSS` | ❌ | -| `ECDSA` | ❌ | +| `RSASSA-PKCS1-v1_5` | ✅ | +| `RSA-PSS` | ✅ | +| `ECDSA` | ✅ | | `Ed25519` | ✅ | | `Ed448` | ✅ | -| `HMAC` | ❌ | +| `HMAC` | ✅ | ## `crypto.verify` | Algorithm | Status | | --------- | :----: | -| `RSASSA-PKCS1-v1_5` | ❌ | -| `RSA-PSS` | ❌ | -| `ECDSA` | ❌ | +| `RSASSA-PKCS1-v1_5` | ✅ | +| `RSA-PSS` | ✅ | +| `ECDSA` | ✅ | | `Ed25519` | ✅ | | `Ed448` | ✅ | -| `HMAC` | ❌ | +| `HMAC` | ✅ | # `WebCrypto` @@ -240,13 +238,13 @@ This document attempts to describe the implementation status of Crypto APIs/Inte # `SubtleCrypto` -* 🚧 Class: `SubtleCrypto` +* ❌ Class: `SubtleCrypto` * ❌ static `supports(operation, algorithm[, lengthOrAdditionalAlgorithm])` * ❌ `subtle.decapsulateBits(decapsulationAlgorithm, decapsulationKey, ciphertext)` * ❌ `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)` @@ -255,7 +253,7 @@ This document attempts to describe the implementation status of Crypto APIs/Inte * 🚧 `subtle.generateKey(algorithm, extractable, keyUsages)` * ❌ `subtle.getPublicKey(key, keyUsages)` * 🚧 `subtle.importKey(format, keyData, algorithm, extractable, keyUsages)` - * 🚧 `subtle.sign(algorithm, key, data)` + * ✅ `subtle.sign(algorithm, key, data)` * ✅ `subtle.unwrapKey(format, wrappedKey, unwrappingKey, unwrapAlgo, unwrappedKeyAlgo, extractable, keyUsages)` * 🚧 `subtle.verify(algorithm, key, signature, data)` * ✅ `subtle.wrapKey(format, key, wrappingKey, wrapAlgo)` @@ -267,6 +265,7 @@ This document attempts to describe the implementation status of Crypto APIs/Inte | `AES-CTR` | ✅ | | `AES-CBC` | ✅ | | `AES-GCM` | ✅ | +| `ChaCha20-Poly1305` | ✅ | ## `subtle.deriveBits` | Algorithm | Status | @@ -311,27 +310,27 @@ This document attempts to describe the implementation status of Crypto APIs/Inte ## `subtle.exportKey` | Key Type | `spki` | `pkcs8` | `jwk` | `raw` | `raw-secret` | `raw-public` | `raw-seed` | -| ------------------- | :----: | :-----: | :---: | :---: | :---: | :---: | :---: | -| `AES-CBC` | | | ✅ | ✅ | ✅ | | | -| `AES-CTR` | | | ✅ | ✅ | ✅ | | | -| `AES-GCM` | | | ✅ | ✅ | ✅ | | | -| `AES-KW` | | | ✅ | ✅ | ✅ | | | -| `AES-OCB` | | | ❌ | | ❌ | | | -| `ChaCha20-Poly1305` | | | ✅ | ✅ | ✅ | | | -| `ECDH` | ✅ | ✅ | ✅ | ✅ | | ✅ | | -| `ECDSA` | ✅ | ✅ | ✅ | ✅ | | ✅ | | -| `Ed25519` | ❌ | ❌ | ❌ | ❌ | | ❌ | | -| `Ed448` | ❌ | ❌ | ❌ | ❌ | | ❌ | | -| `HMAC` | | | ✅ | ✅ | ✅ | | | -| `ML-DSA-44` | ❌ | ❌ | ❌ | | | ❌ | ❌ | -| `ML-DSA-65` | ❌ | ❌ | ❌ | | | ❌ | ❌ | -| `ML-DSA-87` | ❌ | ❌ | ❌ | | | ❌ | ❌ | -| `ML-KEM-512` | ❌ | ❌ | | | | ❌ | ❌ | -| `ML-KEM-768` | ❌ | ❌ | | | | ❌ | ❌ | -| `ML-KEM-1024` | ❌ | ❌ | | | | ❌ | ❌ | -| `RSA-OAEP` | ✅ | ✅ | ✅ | | | | | -| `RSA-PSS` | ✅ | ✅ | ✅ | | | | | -| `RSASSA-PKCS1-v1_5` | ✅ | ✅ | ✅ | | | | | +| ------------------- | :----: | :-----: | :---: | :---: | :----------: | :----------: | :--------: | +| `AES-CBC` | | | ✅ | ✅ | ✅ | | | +| `AES-CTR` | | | ✅ | ✅ | ✅ | | | +| `AES-GCM` | | | ✅ | ✅ | ✅ | | | +| `AES-KW` | | | ✅ | ✅ | ✅ | | | +| `AES-OCB` | | | ❌ | | ❌ | | | +| `ChaCha20-Poly1305` | | | ❌ | | ❌ | | | +| `ECDH` | ✅ | ✅ | ✅ | ✅ | | ✅ | | +| `ECDSA` | ✅ | ✅ | ✅ | ✅ | | ✅ | | +| `Ed25519` | ✅ | ✅ | ❌ | ❌ | | ❌ | | +| `Ed448` | ✅ | ✅ | ❌ | ❌ | | ❌ | | +| `HMAC` | | | ✅ | ✅ | ✅ | | | +| `ML-DSA-44` | ✅ | ✅ | ✅ | | | ✅ | ✅ | +| `ML-DSA-65` | ✅ | ✅ | ✅ | | | ✅ | ✅ | +| `ML-DSA-87` | ✅ | ✅ | ✅ | | | ✅ | ✅ | +| `ML-KEM-512` | ❌ | ❌ | | | | ❌ | ❌ | +| `ML-KEM-768` | ❌ | ❌ | | | | ❌ | ❌ | +| `ML-KEM-1024` | ❌ | ❌ | | | | ❌ | ❌ | +| `RSA-OAEP` | ✅ | ✅ | ✅ | | | | | +| `RSA-PSS` | ✅ | ✅ | ✅ | | | | | +| `RSASSA-PKCS1-v1_5` | ✅ | ✅ | ✅ | | | | | * ` ` - not implemented in Node * ❌ - implemented in Node, not RNQC @@ -346,9 +345,9 @@ This document attempts to describe the implementation status of Crypto APIs/Inte | `ECDSA` | ✅ | | `Ed25519` | ✅ | | `Ed448` | ✅ | -| `ML-DSA-44` | ❌ | -| `ML-DSA-65` | ❌ | -| `ML-DSA-87` | ❌ | +| `ML-DSA-44` | ✅ | +| `ML-DSA-65` | ✅ | +| `ML-DSA-87` | ✅ | | `ML-KEM-512` | ❌ | | `ML-KEM-768` | ❌ | | `ML-KEM-1024` | ❌ | @@ -364,64 +363,64 @@ This document attempts to describe the implementation status of Crypto APIs/Inte | `AES-CTR` | ✅ | | `AES-CBC` | ✅ | | `AES-GCM` | ✅ | -| `AES-KW` | ✅ | +| `AES-KW` | ❌ | | `AES-OCB` | ❌ | -| `ChaCha20-Poly1305` | ✅ | -| `HMAC` | ❌ | +| `ChaCha20-Poly1305` | ❌ | +| `HMAC` | ✅ | ## `subtle.importKey` | Key Type | `spki` | `pkcs8` | `jwk` | `raw` | `raw-secret` | `raw-public` | `raw-seed` | -| ------------------- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | -| `AES-CBC` | | | ✅ | ✅ | ✅ | | | -| `AES-CTR` | | | ✅ | ✅ | ✅ | | | -| `AES-GCM` | | | ✅ | ✅ | ✅ | | | -| `AES-KW` | | | ✅ | ✅ | ✅ | | | -| `AES-OCB` | | | ❌ | | ❌ | | | -| `ChaCha20-Poly1305` | | | ✅ | ✅ | ✅ | | | -| `ECDH` | ✅ | ✅ | ✅ | ✅ | | ✅ | | -| `ECDSA` | ✅ | ✅ | ✅ | ✅ | | ✅ | | -| `Ed25519` | ❌ | ❌ | ❌ | ❌ | | ❌ | | -| `Ed448` | ❌ | ❌ | ❌ | ❌ | | ❌ | | -| `HDKF` | | | | ❌ | ❌ | | | -| `HMAC` | | | ✅ | ✅ | ✅ | | | -| `ML-DSA-44` | ❌ | ❌ | ❌ | | | ❌ | ❌ | -| `ML-DSA-65` | ❌ | ❌ | ❌ | | | ❌ | ❌ | -| `ML-DSA-87` | ❌ | ❌ | ❌ | | | ❌ | ❌ | -| `ML-KEM-512` | ❌ | ❌ | | | | ❌ | ❌ | -| `ML-KEM-768` | ❌ | ❌ | | | | ❌ | ❌ | -| `ML-KEM-1024` | ❌ | ❌ | | | | ❌ | ❌ | -| `PBKDF2` | | | | ✅ | ✅ | | | -| `RSA-OAEP` | ✅ | ❌ | ✅ | | | | | -| `RSA-PSS` | ✅ | ❌ | ✅ | | | | | -| `RSASSA-PKCS1-v1_5` | ✅ | ❌ | ✅ | | | | | -| `X25519` | ❌ | ❌ | ❌ | ❌ | | ❌ | | -| `X448` | ❌ | ❌ | ❌ | ❌ | | ❌ | | +| ------------------- | :----: | :-----: | :---: | :---: | :----------: | :----------: | :--------: | +| `AES-CBC` | | | ✅ | ✅ | ✅ | | | +| `AES-CTR` | | | ✅ | ✅ | ✅ | | | +| `AES-GCM` | | | ✅ | ✅ | ✅ | | | +| `AES-KW` | | | ✅ | ✅ | ✅ | | | +| `AES-OCB` | | | ❌ | | ❌ | | | +| `ChaCha20-Poly1305` | | | ❌ | | ❌ | | | +| `ECDH` | ✅ | ✅ | ✅ | ✅ | | ✅ | | +| `ECDSA` | ✅ | ✅ | ✅ | ✅ | | ✅ | | +| `Ed25519` | ✅ | ✅ | ❌ | ❌ | | ❌ | | +| `Ed448` | ✅ | ✅ | ❌ | ❌ | | ❌ | | +| `HKDF` | | | | ❌ | ❌ | | | +| `HMAC` | | | ✅ | ✅ | ✅ | | | +| `ML-DSA-44` | ✅ | ✅ | ✅ | | | ✅ | ✅ | +| `ML-DSA-65` | ✅ | ✅ | ✅ | | | ✅ | ✅ | +| `ML-DSA-87` | ✅ | ✅ | ✅ | | | ✅ | ✅ | +| `ML-KEM-512` | ❌ | ❌ | | | | ❌ | ❌ | +| `ML-KEM-768` | ❌ | ❌ | | | | ❌ | ❌ | +| `ML-KEM-1024` | ❌ | ❌ | | | | ❌ | ❌ | +| `PBKDF2` | | | | ✅ | ✅ | | | +| `RSA-OAEP` | ✅ | ✅ | ✅ | | | | | +| `RSA-PSS` | ✅ | ✅ | ✅ | | | | | +| `RSASSA-PKCS1-v1_5` | ✅ | ✅ | ✅ | | | | | +| `X25519` | ✅ | ✅ | ✅ | ✅ | | ✅ | | +| `X448` | ✅ | ✅ | ✅ | ✅ | | ✅ | | ## `subtle.sign` | Algorithm | Status | | --------- | :----: | | `ECDSA` | ✅ | -| `Ed25519` | ❌ | -| `Ed448` | ❌ | +| `Ed25519` | ✅ | +| `Ed448` | ✅ | | `HMAC` | ✅ | -| `ML-DSA-44` | ❌ | -| `ML-DSA-65` | ❌ | -| `ML-DSA-87` | ❌ | -| `RSA-PSS` | ❌ | -| `RSASSA-PKCS1-v1_5` | ❌ | +| `ML-DSA-44` | ✅ | +| `ML-DSA-65` | ✅ | +| `ML-DSA-87` | ✅ | +| `RSA-PSS` | ✅ | +| `RSASSA-PKCS1-v1_5` | ✅ | ## `subtle.unwrapKey` ### wrapping algorithms | Algorithm | Status | | ------------------- | :----: | -| `AES-CBC` | ✅ | -| `AES-CTR` | ✅ | +| `AES-CBC` | ❌ | +| `AES-CTR` | ❌ | | `AES-GCM` | ✅ | | `AES-KW` | ✅ | | `AES-OCB` | ❌ | | `ChaCha20-Poly1305` | ✅ | -| `RSA-OAEP` | ✅ | +| `RSA-OAEP` | ❌ | ### unwrapped key algorithms | Algorithm | Status | @@ -453,24 +452,24 @@ This document attempts to describe the implementation status of Crypto APIs/Inte | Algorithm | Status | | --------- | :----: | | `ECDSA` | ✅ | -| `Ed25519` | ❌ | -| `Ed448` | ❌ | +| `Ed25519` | ✅ | +| `Ed448` | ✅ | | `HMAC` | ✅ | -| `ML-DSA-44` | ❌ | -| `ML-DSA-65` | ❌ | -| `ML-DSA-87` | ❌ | -| `RSA-PSS` | ❌ | -| `RSASSA-PKCS1-v1_5` | ❌ | +| `ML-DSA-44` | ✅ | +| `ML-DSA-65` | ✅ | +| `ML-DSA-87` | ✅ | +| `RSA-PSS` | ✅ | +| `RSASSA-PKCS1-v1_5` | ✅ | ## `subtle.wrapKey` ### wrapping algorithms | Algorithm | Status | | ------------------- | :----: | -| `AES-CBC` | ✅ | -| `AES-CTR` | ✅ | +| `AES-CBC` | ❌ | +| `AES-CTR` | ❌ | | `AES-GCM` | ✅ | | `AES-KW` | ✅ | | `AES-OCB` | ❌ | | `ChaCha20-Poly1305` | ✅ | -| `RSA-OAEP` | ✅ | +| `RSA-OAEP` | ❌ | diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 41bbfb17..a97b75e1 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -37,8 +37,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - OpenSSL-Universal (3.3.3001) - - QuickCrypto (1.0.0): + - QuickCrypto (1.0.1): - boost - DoubleConversion - fast_float @@ -46,7 +45,6 @@ PODS: - glog - hermes-engine - NitroModules - - OpenSSL-Universal (= 3.3.3001) - RCT-Folly - RCT-Folly/Fabric - RCTRequired @@ -1809,6 +1807,34 @@ PODS: - React-RCTFBReactNativeSpec - ReactCommon/turbomodule/core - SocketRocket + - react-native-fast-encoder (0.3.1): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga - react-native-quick-base64 (2.2.2): - React-Core - react-native-safe-area-context (5.6.2): @@ -2494,247 +2520,249 @@ PODS: - Yoga (0.0.0) DEPENDENCIES: - - boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`) - - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - - fast_float (from `../node_modules/react-native/third-party-podspecs/fast_float.podspec`) - - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - - fmt (from `../node_modules/react-native/third-party-podspecs/fmt.podspec`) - - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) - - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) - - NitroModules (from `../node_modules/react-native-nitro-modules`) - - QuickCrypto (from `../node_modules/react-native-quick-crypto`) - - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - - RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`) - - RCTRequired (from `../node_modules/react-native/Libraries/Required`) - - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`) - - React (from `../node_modules/react-native/`) - - React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`) - - React-Core (from `../node_modules/react-native/`) - - React-Core/RCTWebSocket (from `../node_modules/react-native/`) - - React-CoreModules (from `../node_modules/react-native/React/CoreModules`) - - React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`) - - React-debug (from `../node_modules/react-native/ReactCommon/react/debug`) - - React-defaultsnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/defaults`) - - React-domnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/dom`) - - React-Fabric (from `../node_modules/react-native/ReactCommon`) - - React-FabricComponents (from `../node_modules/react-native/ReactCommon`) - - React-FabricImage (from `../node_modules/react-native/ReactCommon`) - - React-featureflags (from `../node_modules/react-native/ReactCommon/react/featureflags`) - - React-featureflagsnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/featureflags`) - - React-graphics (from `../node_modules/react-native/ReactCommon/react/renderer/graphics`) - - React-hermes (from `../node_modules/react-native/ReactCommon/hermes`) - - React-idlecallbacksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks`) - - React-ImageManager (from `../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios`) - - React-jserrorhandler (from `../node_modules/react-native/ReactCommon/jserrorhandler`) - - React-jsi (from `../node_modules/react-native/ReactCommon/jsi`) - - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) - - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector-modern`) - - React-jsinspectorcdp (from `../node_modules/react-native/ReactCommon/jsinspector-modern/cdp`) - - React-jsinspectornetwork (from `../node_modules/react-native/ReactCommon/jsinspector-modern/network`) - - React-jsinspectortracing (from `../node_modules/react-native/ReactCommon/jsinspector-modern/tracing`) - - React-jsitooling (from `../node_modules/react-native/ReactCommon/jsitooling`) - - React-jsitracing (from `../node_modules/react-native/ReactCommon/hermes/executor/`) - - React-logger (from `../node_modules/react-native/ReactCommon/logger`) - - React-Mapbuffer (from `../node_modules/react-native/ReactCommon`) - - React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`) - - react-native-quick-base64 (from `../node_modules/react-native-quick-base64`) - - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - - React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) - - React-oscompat (from `../node_modules/react-native/ReactCommon/oscompat`) - - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) - - React-performancetimeline (from `../node_modules/react-native/ReactCommon/react/performance/timeline`) - - React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`) - - React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`) - - React-RCTAppDelegate (from `../node_modules/react-native/Libraries/AppDelegate`) - - React-RCTBlob (from `../node_modules/react-native/Libraries/Blob`) - - React-RCTFabric (from `../node_modules/react-native/React`) - - React-RCTFBReactNativeSpec (from `../node_modules/react-native/React`) - - React-RCTImage (from `../node_modules/react-native/Libraries/Image`) - - React-RCTLinking (from `../node_modules/react-native/Libraries/LinkingIOS`) - - React-RCTNetwork (from `../node_modules/react-native/Libraries/Network`) - - React-RCTRuntime (from `../node_modules/react-native/React/Runtime`) - - React-RCTSettings (from `../node_modules/react-native/Libraries/Settings`) - - React-RCTText (from `../node_modules/react-native/Libraries/Text`) - - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) - - React-rendererconsistency (from `../node_modules/react-native/ReactCommon/react/renderer/consistency`) - - React-renderercss (from `../node_modules/react-native/ReactCommon/react/renderer/css`) - - React-rendererdebug (from `../node_modules/react-native/ReactCommon/react/renderer/debug`) - - React-RuntimeApple (from `../node_modules/react-native/ReactCommon/react/runtime/platform/ios`) - - React-RuntimeCore (from `../node_modules/react-native/ReactCommon/react/runtime`) - - React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`) - - React-RuntimeHermes (from `../node_modules/react-native/ReactCommon/react/runtime`) - - React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`) - - React-timing (from `../node_modules/react-native/ReactCommon/react/timing`) - - React-utils (from `../node_modules/react-native/ReactCommon/react/utils`) + - boost (from `../../node_modules/react-native/third-party-podspecs/boost.podspec`) + - DoubleConversion (from `../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) + - fast_float (from `../../node_modules/react-native/third-party-podspecs/fast_float.podspec`) + - FBLazyVector (from `../../node_modules/react-native/Libraries/FBLazyVector`) + - fmt (from `../../node_modules/react-native/third-party-podspecs/fmt.podspec`) + - glog (from `../../node_modules/react-native/third-party-podspecs/glog.podspec`) + - hermes-engine (from `../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) + - NitroModules (from `../../node_modules/react-native-nitro-modules`) + - QuickCrypto (from `../../node_modules/react-native-quick-crypto`) + - RCT-Folly (from `../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) + - RCTDeprecation (from `../../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`) + - RCTRequired (from `../../node_modules/react-native/Libraries/Required`) + - RCTTypeSafety (from `../../node_modules/react-native/Libraries/TypeSafety`) + - React (from `../../node_modules/react-native/`) + - React-callinvoker (from `../../node_modules/react-native/ReactCommon/callinvoker`) + - React-Core (from `../../node_modules/react-native/`) + - React-Core/RCTWebSocket (from `../../node_modules/react-native/`) + - React-CoreModules (from `../../node_modules/react-native/React/CoreModules`) + - React-cxxreact (from `../../node_modules/react-native/ReactCommon/cxxreact`) + - React-debug (from `../../node_modules/react-native/ReactCommon/react/debug`) + - React-defaultsnativemodule (from `../../node_modules/react-native/ReactCommon/react/nativemodule/defaults`) + - React-domnativemodule (from `../../node_modules/react-native/ReactCommon/react/nativemodule/dom`) + - React-Fabric (from `../../node_modules/react-native/ReactCommon`) + - React-FabricComponents (from `../../node_modules/react-native/ReactCommon`) + - React-FabricImage (from `../../node_modules/react-native/ReactCommon`) + - React-featureflags (from `../../node_modules/react-native/ReactCommon/react/featureflags`) + - React-featureflagsnativemodule (from `../../node_modules/react-native/ReactCommon/react/nativemodule/featureflags`) + - React-graphics (from `../../node_modules/react-native/ReactCommon/react/renderer/graphics`) + - React-hermes (from `../../node_modules/react-native/ReactCommon/hermes`) + - React-idlecallbacksnativemodule (from `../../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks`) + - React-ImageManager (from `../../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios`) + - React-jserrorhandler (from `../../node_modules/react-native/ReactCommon/jserrorhandler`) + - React-jsi (from `../../node_modules/react-native/ReactCommon/jsi`) + - React-jsiexecutor (from `../../node_modules/react-native/ReactCommon/jsiexecutor`) + - React-jsinspector (from `../../node_modules/react-native/ReactCommon/jsinspector-modern`) + - React-jsinspectorcdp (from `../../node_modules/react-native/ReactCommon/jsinspector-modern/cdp`) + - React-jsinspectornetwork (from `../../node_modules/react-native/ReactCommon/jsinspector-modern/network`) + - React-jsinspectortracing (from `../../node_modules/react-native/ReactCommon/jsinspector-modern/tracing`) + - React-jsitooling (from `../../node_modules/react-native/ReactCommon/jsitooling`) + - React-jsitracing (from `../../node_modules/react-native/ReactCommon/hermes/executor/`) + - React-logger (from `../../node_modules/react-native/ReactCommon/logger`) + - React-Mapbuffer (from `../../node_modules/react-native/ReactCommon`) + - React-microtasksnativemodule (from `../../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`) + - react-native-fast-encoder (from `../../node_modules/react-native-fast-encoder`) + - react-native-quick-base64 (from `../../node_modules/react-native-quick-base64`) + - react-native-safe-area-context (from `../../node_modules/react-native-safe-area-context`) + - React-NativeModulesApple (from `../../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) + - React-oscompat (from `../../node_modules/react-native/ReactCommon/oscompat`) + - React-perflogger (from `../../node_modules/react-native/ReactCommon/reactperflogger`) + - React-performancetimeline (from `../../node_modules/react-native/ReactCommon/react/performance/timeline`) + - React-RCTActionSheet (from `../../node_modules/react-native/Libraries/ActionSheetIOS`) + - React-RCTAnimation (from `../../node_modules/react-native/Libraries/NativeAnimation`) + - React-RCTAppDelegate (from `../../node_modules/react-native/Libraries/AppDelegate`) + - React-RCTBlob (from `../../node_modules/react-native/Libraries/Blob`) + - React-RCTFabric (from `../../node_modules/react-native/React`) + - React-RCTFBReactNativeSpec (from `../../node_modules/react-native/React`) + - React-RCTImage (from `../../node_modules/react-native/Libraries/Image`) + - React-RCTLinking (from `../../node_modules/react-native/Libraries/LinkingIOS`) + - React-RCTNetwork (from `../../node_modules/react-native/Libraries/Network`) + - React-RCTRuntime (from `../../node_modules/react-native/React/Runtime`) + - React-RCTSettings (from `../../node_modules/react-native/Libraries/Settings`) + - React-RCTText (from `../../node_modules/react-native/Libraries/Text`) + - React-RCTVibration (from `../../node_modules/react-native/Libraries/Vibration`) + - React-rendererconsistency (from `../../node_modules/react-native/ReactCommon/react/renderer/consistency`) + - React-renderercss (from `../../node_modules/react-native/ReactCommon/react/renderer/css`) + - React-rendererdebug (from `../../node_modules/react-native/ReactCommon/react/renderer/debug`) + - React-RuntimeApple (from `../../node_modules/react-native/ReactCommon/react/runtime/platform/ios`) + - React-RuntimeCore (from `../../node_modules/react-native/ReactCommon/react/runtime`) + - React-runtimeexecutor (from `../../node_modules/react-native/ReactCommon/runtimeexecutor`) + - React-RuntimeHermes (from `../../node_modules/react-native/ReactCommon/react/runtime`) + - React-runtimescheduler (from `../../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`) + - React-timing (from `../../node_modules/react-native/ReactCommon/react/timing`) + - React-utils (from `../../node_modules/react-native/ReactCommon/react/utils`) - ReactAppDependencyProvider (from `build/generated/ios`) - ReactCodegen (from `build/generated/ios`) - - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) - - RNScreens (from `../node_modules/react-native-screens`) - - RNVectorIcons (from `../node_modules/react-native-vector-icons`) + - ReactCommon/turbomodule/core (from `../../node_modules/react-native/ReactCommon`) + - RNScreens (from `../../node_modules/react-native-screens`) + - RNVectorIcons (from `../../node_modules/react-native-vector-icons`) - SocketRocket (~> 0.7.1) - - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) + - Yoga (from `../../node_modules/react-native/ReactCommon/yoga`) SPEC REPOS: trunk: - - OpenSSL-Universal - SocketRocket EXTERNAL SOURCES: boost: - :podspec: "../node_modules/react-native/third-party-podspecs/boost.podspec" + :podspec: "../../node_modules/react-native/third-party-podspecs/boost.podspec" DoubleConversion: - :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" + :podspec: "../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" fast_float: - :podspec: "../node_modules/react-native/third-party-podspecs/fast_float.podspec" + :podspec: "../../node_modules/react-native/third-party-podspecs/fast_float.podspec" FBLazyVector: - :path: "../node_modules/react-native/Libraries/FBLazyVector" + :path: "../../node_modules/react-native/Libraries/FBLazyVector" fmt: - :podspec: "../node_modules/react-native/third-party-podspecs/fmt.podspec" + :podspec: "../../node_modules/react-native/third-party-podspecs/fmt.podspec" glog: - :podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec" + :podspec: "../../node_modules/react-native/third-party-podspecs/glog.podspec" hermes-engine: - :podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" + :podspec: "../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" :tag: hermes-2025-07-07-RNv0.81.0-e0fc67142ec0763c6b6153ca2bf96df815539782 NitroModules: - :path: "../node_modules/react-native-nitro-modules" + :path: "../../node_modules/react-native-nitro-modules" QuickCrypto: - :path: "../node_modules/react-native-quick-crypto" + :path: "../../node_modules/react-native-quick-crypto" RCT-Folly: - :podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec" + :podspec: "../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec" RCTDeprecation: - :path: "../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation" + :path: "../../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation" RCTRequired: - :path: "../node_modules/react-native/Libraries/Required" + :path: "../../node_modules/react-native/Libraries/Required" RCTTypeSafety: - :path: "../node_modules/react-native/Libraries/TypeSafety" + :path: "../../node_modules/react-native/Libraries/TypeSafety" React: - :path: "../node_modules/react-native/" + :path: "../../node_modules/react-native/" React-callinvoker: - :path: "../node_modules/react-native/ReactCommon/callinvoker" + :path: "../../node_modules/react-native/ReactCommon/callinvoker" React-Core: - :path: "../node_modules/react-native/" + :path: "../../node_modules/react-native/" React-CoreModules: - :path: "../node_modules/react-native/React/CoreModules" + :path: "../../node_modules/react-native/React/CoreModules" React-cxxreact: - :path: "../node_modules/react-native/ReactCommon/cxxreact" + :path: "../../node_modules/react-native/ReactCommon/cxxreact" React-debug: - :path: "../node_modules/react-native/ReactCommon/react/debug" + :path: "../../node_modules/react-native/ReactCommon/react/debug" React-defaultsnativemodule: - :path: "../node_modules/react-native/ReactCommon/react/nativemodule/defaults" + :path: "../../node_modules/react-native/ReactCommon/react/nativemodule/defaults" React-domnativemodule: - :path: "../node_modules/react-native/ReactCommon/react/nativemodule/dom" + :path: "../../node_modules/react-native/ReactCommon/react/nativemodule/dom" React-Fabric: - :path: "../node_modules/react-native/ReactCommon" + :path: "../../node_modules/react-native/ReactCommon" React-FabricComponents: - :path: "../node_modules/react-native/ReactCommon" + :path: "../../node_modules/react-native/ReactCommon" React-FabricImage: - :path: "../node_modules/react-native/ReactCommon" + :path: "../../node_modules/react-native/ReactCommon" React-featureflags: - :path: "../node_modules/react-native/ReactCommon/react/featureflags" + :path: "../../node_modules/react-native/ReactCommon/react/featureflags" React-featureflagsnativemodule: - :path: "../node_modules/react-native/ReactCommon/react/nativemodule/featureflags" + :path: "../../node_modules/react-native/ReactCommon/react/nativemodule/featureflags" React-graphics: - :path: "../node_modules/react-native/ReactCommon/react/renderer/graphics" + :path: "../../node_modules/react-native/ReactCommon/react/renderer/graphics" React-hermes: - :path: "../node_modules/react-native/ReactCommon/hermes" + :path: "../../node_modules/react-native/ReactCommon/hermes" React-idlecallbacksnativemodule: - :path: "../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks" + :path: "../../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks" React-ImageManager: - :path: "../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios" + :path: "../../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios" React-jserrorhandler: - :path: "../node_modules/react-native/ReactCommon/jserrorhandler" + :path: "../../node_modules/react-native/ReactCommon/jserrorhandler" React-jsi: - :path: "../node_modules/react-native/ReactCommon/jsi" + :path: "../../node_modules/react-native/ReactCommon/jsi" React-jsiexecutor: - :path: "../node_modules/react-native/ReactCommon/jsiexecutor" + :path: "../../node_modules/react-native/ReactCommon/jsiexecutor" React-jsinspector: - :path: "../node_modules/react-native/ReactCommon/jsinspector-modern" + :path: "../../node_modules/react-native/ReactCommon/jsinspector-modern" React-jsinspectorcdp: - :path: "../node_modules/react-native/ReactCommon/jsinspector-modern/cdp" + :path: "../../node_modules/react-native/ReactCommon/jsinspector-modern/cdp" React-jsinspectornetwork: - :path: "../node_modules/react-native/ReactCommon/jsinspector-modern/network" + :path: "../../node_modules/react-native/ReactCommon/jsinspector-modern/network" React-jsinspectortracing: - :path: "../node_modules/react-native/ReactCommon/jsinspector-modern/tracing" + :path: "../../node_modules/react-native/ReactCommon/jsinspector-modern/tracing" React-jsitooling: - :path: "../node_modules/react-native/ReactCommon/jsitooling" + :path: "../../node_modules/react-native/ReactCommon/jsitooling" React-jsitracing: - :path: "../node_modules/react-native/ReactCommon/hermes/executor/" + :path: "../../node_modules/react-native/ReactCommon/hermes/executor/" React-logger: - :path: "../node_modules/react-native/ReactCommon/logger" + :path: "../../node_modules/react-native/ReactCommon/logger" React-Mapbuffer: - :path: "../node_modules/react-native/ReactCommon" + :path: "../../node_modules/react-native/ReactCommon" React-microtasksnativemodule: - :path: "../node_modules/react-native/ReactCommon/react/nativemodule/microtasks" + :path: "../../node_modules/react-native/ReactCommon/react/nativemodule/microtasks" + react-native-fast-encoder: + :path: "../../node_modules/react-native-fast-encoder" react-native-quick-base64: - :path: "../node_modules/react-native-quick-base64" + :path: "../../node_modules/react-native-quick-base64" react-native-safe-area-context: - :path: "../node_modules/react-native-safe-area-context" + :path: "../../node_modules/react-native-safe-area-context" React-NativeModulesApple: - :path: "../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios" + :path: "../../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios" React-oscompat: - :path: "../node_modules/react-native/ReactCommon/oscompat" + :path: "../../node_modules/react-native/ReactCommon/oscompat" React-perflogger: - :path: "../node_modules/react-native/ReactCommon/reactperflogger" + :path: "../../node_modules/react-native/ReactCommon/reactperflogger" React-performancetimeline: - :path: "../node_modules/react-native/ReactCommon/react/performance/timeline" + :path: "../../node_modules/react-native/ReactCommon/react/performance/timeline" React-RCTActionSheet: - :path: "../node_modules/react-native/Libraries/ActionSheetIOS" + :path: "../../node_modules/react-native/Libraries/ActionSheetIOS" React-RCTAnimation: - :path: "../node_modules/react-native/Libraries/NativeAnimation" + :path: "../../node_modules/react-native/Libraries/NativeAnimation" React-RCTAppDelegate: - :path: "../node_modules/react-native/Libraries/AppDelegate" + :path: "../../node_modules/react-native/Libraries/AppDelegate" React-RCTBlob: - :path: "../node_modules/react-native/Libraries/Blob" + :path: "../../node_modules/react-native/Libraries/Blob" React-RCTFabric: - :path: "../node_modules/react-native/React" + :path: "../../node_modules/react-native/React" React-RCTFBReactNativeSpec: - :path: "../node_modules/react-native/React" + :path: "../../node_modules/react-native/React" React-RCTImage: - :path: "../node_modules/react-native/Libraries/Image" + :path: "../../node_modules/react-native/Libraries/Image" React-RCTLinking: - :path: "../node_modules/react-native/Libraries/LinkingIOS" + :path: "../../node_modules/react-native/Libraries/LinkingIOS" React-RCTNetwork: - :path: "../node_modules/react-native/Libraries/Network" + :path: "../../node_modules/react-native/Libraries/Network" React-RCTRuntime: - :path: "../node_modules/react-native/React/Runtime" + :path: "../../node_modules/react-native/React/Runtime" React-RCTSettings: - :path: "../node_modules/react-native/Libraries/Settings" + :path: "../../node_modules/react-native/Libraries/Settings" React-RCTText: - :path: "../node_modules/react-native/Libraries/Text" + :path: "../../node_modules/react-native/Libraries/Text" React-RCTVibration: - :path: "../node_modules/react-native/Libraries/Vibration" + :path: "../../node_modules/react-native/Libraries/Vibration" React-rendererconsistency: - :path: "../node_modules/react-native/ReactCommon/react/renderer/consistency" + :path: "../../node_modules/react-native/ReactCommon/react/renderer/consistency" React-renderercss: - :path: "../node_modules/react-native/ReactCommon/react/renderer/css" + :path: "../../node_modules/react-native/ReactCommon/react/renderer/css" React-rendererdebug: - :path: "../node_modules/react-native/ReactCommon/react/renderer/debug" + :path: "../../node_modules/react-native/ReactCommon/react/renderer/debug" React-RuntimeApple: - :path: "../node_modules/react-native/ReactCommon/react/runtime/platform/ios" + :path: "../../node_modules/react-native/ReactCommon/react/runtime/platform/ios" React-RuntimeCore: - :path: "../node_modules/react-native/ReactCommon/react/runtime" + :path: "../../node_modules/react-native/ReactCommon/react/runtime" React-runtimeexecutor: - :path: "../node_modules/react-native/ReactCommon/runtimeexecutor" + :path: "../../node_modules/react-native/ReactCommon/runtimeexecutor" React-RuntimeHermes: - :path: "../node_modules/react-native/ReactCommon/react/runtime" + :path: "../../node_modules/react-native/ReactCommon/react/runtime" React-runtimescheduler: - :path: "../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler" + :path: "../../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler" React-timing: - :path: "../node_modules/react-native/ReactCommon/react/timing" + :path: "../../node_modules/react-native/ReactCommon/react/timing" React-utils: - :path: "../node_modules/react-native/ReactCommon/react/utils" + :path: "../../node_modules/react-native/ReactCommon/react/utils" ReactAppDependencyProvider: :path: build/generated/ios ReactCodegen: :path: build/generated/ios ReactCommon: - :path: "../node_modules/react-native/ReactCommon" + :path: "../../node_modules/react-native/ReactCommon" RNScreens: - :path: "../node_modules/react-native-screens" + :path: "../../node_modules/react-native-screens" RNVectorIcons: - :path: "../node_modules/react-native-vector-icons" + :path: "../../node_modules/react-native-vector-icons" Yoga: - :path: "../node_modules/react-native/ReactCommon/yoga" + :path: "../../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90 @@ -2745,8 +2773,7 @@ SPEC CHECKSUMS: glog: 5683914934d5b6e4240e497e0f4a3b42d1854183 hermes-engine: 4f8246b1f6d79f625e0d99472d1f3a71da4d28ca NitroModules: 1715fe0e22defd9e2cdd48fb5e0dbfd01af54bec - OpenSSL-Universal: 6082b0bf950e5636fe0d78def171184e2b3899c2 - QuickCrypto: 4e82c6565ea7b5f9d4c3f0ad3f19b785a676b4cc + QuickCrypto: 389afd05e4f7d62ec5e2be215aeaf0dd4745b5e1 RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669 RCTDeprecation: c4b9e2fd0ab200e3af72b013ed6113187c607077 RCTRequired: e97dd5dafc1db8094e63bc5031e0371f092ae92a @@ -2780,6 +2807,7 @@ SPEC CHECKSUMS: React-logger: 7aef4d74123e5e3d267e5af1fbf5135b5a0d8381 React-Mapbuffer: 91e0eab42a6ae7f3e34091a126d70fc53bd3823e React-microtasksnativemodule: 1ead4fe154df3b1ba34b5a9e35ef3c4bdfa72ccb + react-native-fast-encoder: f2728ab5e520601ba04df15716722941d941495e react-native-quick-base64: 6568199bb2ac8e72ecdfdc73a230fbc5c1d3aac4 react-native-safe-area-context: c00143b4823773bba23f2f19f85663ae89ceb460 React-NativeModulesApple: eff2eba56030eb0d107b1642b8f853bc36a833ac @@ -2810,13 +2838,13 @@ SPEC CHECKSUMS: React-timing: 97ada2c47b4c5932e7f773c7d239c52b90d6ca68 React-utils: f0949d247a46b4c09f03e5a3cb1167602d0b729a ReactAppDependencyProvider: 3eb9096cb139eb433965693bbe541d96eb3d3ec9 - ReactCodegen: cb29ed8113eb9f7821e0b80d42a7b1535ce3a52b + ReactCodegen: c881aa301b4194d7d08b2a1afcb0b9e108917629 ReactCommon: ce5d4226dfaf9d5dacbef57b4528819e39d3a120 RNScreens: d821082c6dd1cb397cc0c98b026eeafaa68be479 RNVectorIcons: 791f13226ec4a3fd13062eda9e892159f0981fae SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Yoga: 11c9686a21e2cd82a094a723649d9f4507200fb0 -PODFILE CHECKSUM: 8bf59f4e86b38489f786b2878e119cdf1824ca75 +PODFILE CHECKSUM: bc958092bb9060694d04c6fcf716262b0549cded -COCOAPODS: 1.16.2 +COCOAPODS: 1.15.2 diff --git a/example/ios/QuickCryptoExample.xcodeproj/project.pbxproj b/example/ios/QuickCryptoExample.xcodeproj/project.pbxproj index 040aaf19..bcfe455e 100644 --- a/example/ios/QuickCryptoExample.xcodeproj/project.pbxproj +++ b/example/ios/QuickCryptoExample.xcodeproj/project.pbxproj @@ -415,7 +415,7 @@ "-DFOLLY_HAVE_CLOCK_GETTIME=1", ); OTHER_LDFLAGS = "$(inherited)"; - REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; SWIFT_ENABLE_EXPLICIT_MODULES = NO; @@ -500,7 +500,7 @@ "-DFOLLY_HAVE_CLOCK_GETTIME=1", ); OTHER_LDFLAGS = "$(inherited)"; - REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_ENABLE_EXPLICIT_MODULES = NO; USE_HERMES = true; diff --git a/example/package.json b/example/package.json index 22d68779..735992e8 100644 --- a/example/package.json +++ b/example/package.json @@ -1,6 +1,6 @@ { "name": "react-native-quick-crypto-example", - "version": "1.0.0", + "version": "1.0.1", "private": true, "type": "module", "scripts": { @@ -68,10 +68,11 @@ "@types/react-test-renderer": "^19.1.0", "babel-jest": "29.7.0", "babel-plugin-module-resolver": "5.0.2", + "jose": "6.1.3", "react-test-renderer": "19.1.0", "typescript": "^5.8.3" }, "engines": { "node": ">=18" } -} +} \ No newline at end of file diff --git a/example/src/hooks/useTestsList.ts b/example/src/hooks/useTestsList.ts index b516f9ba..de162bce 100644 --- a/example/src/hooks/useTestsList.ts +++ b/example/src/hooks/useTestsList.ts @@ -29,7 +29,6 @@ import '../tests/subtle/sign_verify'; import '../tests/subtle/x25519_x448'; import '../tests/subtle/wrap_unwrap'; import '../tests/subtle/derive_key'; -import '../tests/subtle/hmac_sign_verify'; export const useTestsList = (): [ TestSuites, diff --git a/example/src/tests/subtle/hmac_sign_verify.ts b/example/src/tests/subtle/hmac_sign_verify.ts deleted file mode 100644 index fe42f405..00000000 --- a/example/src/tests/subtle/hmac_sign_verify.ts +++ /dev/null @@ -1,204 +0,0 @@ -import { test } from '../util'; -import { expect } from 'chai'; -import { subtle, getRandomValues } from 'react-native-quick-crypto'; -import { CryptoKey } from 'react-native-quick-crypto'; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const subtleAny = subtle as any; - -const SUITE = 'subtle.sign/verify HMAC'; - -// Test 1: Basic HMAC sign/verify with SHA-256 -test(SUITE, 'HMAC-SHA256 sign and verify', async () => { - const key = await subtle.generateKey( - { name: 'HMAC', hash: 'SHA-256', length: 256 }, - false, - ['sign', 'verify'], - ); - - const data = new Uint8Array([1, 2, 3, 4, 5]); - - const signature = await subtleAny.sign( - { name: 'HMAC' }, - key as CryptoKey, - data, - ); - - expect(signature).to.be.instanceOf(ArrayBuffer); - expect(signature.byteLength).to.equal(32); // SHA-256 = 32 bytes - - const valid = await subtleAny.verify( - { name: 'HMAC' }, - key as CryptoKey, - signature, - data, - ); - - expect(valid).to.equal(true); -}); - -// Test 2: HMAC with different hash algorithms -test(SUITE, 'HMAC with SHA-384', async () => { - const key = await subtle.generateKey( - { name: 'HMAC', hash: 'SHA-384' }, - false, - ['sign', 'verify'], - ); - - const data = new Uint8Array([1, 2, 3, 4]); - - const signature = await subtleAny.sign( - { name: 'HMAC' }, - key as CryptoKey, - data, - ); - - expect(signature.byteLength).to.equal(48); // SHA-384 = 48 bytes - - const valid = await subtleAny.verify( - { name: 'HMAC' }, - key as CryptoKey, - signature, - data, - ); - - expect(valid).to.equal(true); -}); - -// Test 3: HMAC with SHA-512 -test(SUITE, 'HMAC with SHA-512', async () => { - const key = await subtle.generateKey( - { name: 'HMAC', hash: 'SHA-512' }, - false, - ['sign', 'verify'], - ); - - const data = new Uint8Array([1, 2, 3, 4]); - - const signature = await subtleAny.sign( - { name: 'HMAC' }, - key as CryptoKey, - data, - ); - - expect(signature.byteLength).to.equal(64); // SHA-512 = 64 bytes - - const valid = await subtleAny.verify( - { name: 'HMAC' }, - key as CryptoKey, - signature, - data, - ); - - expect(valid).to.equal(true); -}); - -// Test 4: Verify with wrong signature -test(SUITE, 'HMAC verify fails with wrong signature', async () => { - const key = await subtle.generateKey( - { name: 'HMAC', hash: 'SHA-256' }, - false, - ['sign', 'verify'], - ); - - const data = new Uint8Array([1, 2, 3, 4]); - const wrongSignature = getRandomValues(new Uint8Array(32)); - - const valid = await subtleAny.verify( - { name: 'HMAC' }, - key as CryptoKey, - wrongSignature, - data, - ); - - expect(valid).to.equal(false); -}); - -// Test 5: Verify with wrong data -test(SUITE, 'HMAC verify fails with wrong data', async () => { - const key = await subtle.generateKey( - { name: 'HMAC', hash: 'SHA-256' }, - false, - ['sign', 'verify'], - ); - - const data = new Uint8Array([1, 2, 3, 4]); - const signature = await subtleAny.sign( - { name: 'HMAC' }, - key as CryptoKey, - data, - ); - - const wrongData = new Uint8Array([5, 6, 7, 8]); - const valid = await subtleAny.verify( - { name: 'HMAC' }, - key as CryptoKey, - signature, - wrongData, - ); - - expect(valid).to.equal(false); -}); - -// Test 6: Wrong key usage for sign -test(SUITE, 'HMAC sign fails without sign usage', async () => { - const key = await subtle.generateKey( - { name: 'HMAC', hash: 'SHA-256' }, - false, - ['verify'], // Only verify, not sign - ); - - const data = new Uint8Array([1, 2, 3, 4]); - - try { - await subtleAny.sign({ name: 'HMAC' }, key as CryptoKey, data); - expect.fail('Should have thrown'); - } catch (error) { - expect((error as Error).message).to.include('sign usage'); - } -}); - -// Test 7: Wrong key usage for verify -test(SUITE, 'HMAC verify fails without verify usage', async () => { - const key = await subtle.generateKey( - { name: 'HMAC', hash: 'SHA-256' }, - false, - ['sign'], // Only sign, not verify - ); - - const data = new Uint8Array([1, 2, 3, 4]); - const signature = getRandomValues(new Uint8Array(32)); - - try { - await subtleAny.verify({ name: 'HMAC' }, key as CryptoKey, signature, data); - expect.fail('Should have thrown'); - } catch (error) { - expect((error as Error).message).to.include('verify usage'); - } -}); - -// Test 8: Large data -test(SUITE, 'HMAC with large data', async () => { - const key = await subtle.generateKey( - { name: 'HMAC', hash: 'SHA-256' }, - false, - ['sign', 'verify'], - ); - - const data = getRandomValues(new Uint8Array(10000)); - - const signature = await subtleAny.sign( - { name: 'HMAC' }, - key as CryptoKey, - data, - ); - - const valid = await subtleAny.verify( - { name: 'HMAC' }, - key as CryptoKey, - signature, - data, - ); - - expect(valid).to.equal(true); -}); diff --git a/example/src/tests/subtle/x25519_x448.ts b/example/src/tests/subtle/x25519_x448.ts index 3a69c7f5..dca7fde2 100644 --- a/example/src/tests/subtle/x25519_x448.ts +++ b/example/src/tests/subtle/x25519_x448.ts @@ -6,7 +6,7 @@ import { } from 'react-native-quick-crypto'; import { test } from '../util'; -const SUITE = 'subtle x25519/x448'; +const SUITE = 'subtle.cfrg'; test( SUITE, diff --git a/package.json b/package.json index 106f6f65..375e266e 100644 --- a/package.json +++ b/package.json @@ -105,4 +105,4 @@ "dependencies": { "caniuse-lite": "^1.0.30001757" } -} \ No newline at end of file +} diff --git a/packages/react-native-quick-crypto/package.json b/packages/react-native-quick-crypto/package.json index 99a39335..33b47d0f 100644 --- a/packages/react-native-quick-crypto/package.json +++ b/packages/react-native-quick-crypto/package.json @@ -1,6 +1,6 @@ { "name": "react-native-quick-crypto", - "version": "1.0.0", + "version": "1.0.1", "description": "A fast implementation of Node's `crypto` module written in C/C++ JSI", "main": "lib/commonjs/index", "module": "lib/module/index", @@ -74,7 +74,6 @@ "dependencies": { "@craftzdog/react-native-buffer": "6.1.0", "events": "3.3.0", - "expo-build-properties": "0.14.6", "react-native-quick-base64": "2.2.2", "readable-stream": "4.5.2", "safe-buffer": "^5.2.1", @@ -87,6 +86,7 @@ "@types/readable-stream": "4.0.18", "del-cli": "7.0.0", "expo": "^54.0.25", + "expo-build-properties": "^1.0.0", "jest": "29.7.0", "nitro-codegen": "0.29.1", "react-native-builder-bob": "0.40.15", @@ -96,11 +96,15 @@ "react": "*", "react-native": "*", "react-native-nitro-modules": ">=0.29.1", - "expo": ">=48.0.0" + "expo": ">=48.0.0", + "expo-build-properties": "*" }, "peerDependenciesMeta": { "expo": { "optional": true + }, + "expo-build-properties": { + "optional": true } }, "release-it": { From d3cca970243f04fa8f47222c06025aeb909c0ba1 Mon Sep 17 00:00:00 2001 From: Athex Web3 Date: Fri, 5 Dec 2025 21:03:08 +0600 Subject: [PATCH 07/11] fix: sort subtle test imports alphabetically --- example/src/hooks/useTestsList.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/src/hooks/useTestsList.ts b/example/src/hooks/useTestsList.ts index de162bce..1ed86d7d 100644 --- a/example/src/hooks/useTestsList.ts +++ b/example/src/hooks/useTestsList.ts @@ -20,15 +20,15 @@ import '../tests/keys/sign_verify_streaming'; import '../tests/pbkdf2/pbkdf2_tests'; import '../tests/random/random_tests'; import '../tests/subtle/deriveBits'; +import '../tests/subtle/derive_key'; import '../tests/subtle/digest'; import '../tests/subtle/encrypt_decrypt'; import '../tests/subtle/generateKey'; import '../tests/subtle/import_export'; import '../tests/subtle/jwk_rfc7517_tests'; import '../tests/subtle/sign_verify'; -import '../tests/subtle/x25519_x448'; import '../tests/subtle/wrap_unwrap'; -import '../tests/subtle/derive_key'; +import '../tests/subtle/x25519_x448'; export const useTestsList = (): [ TestSuites, From 5f4759859f9876267b7572a832becc64e8d00fc4 Mon Sep 17 00:00:00 2001 From: Athex Web3 Date: Fri, 5 Dec 2025 21:10:31 +0600 Subject: [PATCH 08/11] fix: restored mistakenly removed lines --- docs/implementation-coverage.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/implementation-coverage.md b/docs/implementation-coverage.md index 606b7517..bdf3cbb6 100644 --- a/docs/implementation-coverage.md +++ b/docs/implementation-coverage.md @@ -6,6 +6,15 @@ This document attempts to describe the implementation status of Crypto APIs/Inte * ` ` - not implemented in Node * ❌ - implemented in Node, not RNQC * ✅ - implemented in Node and RNQC +* 🚧 - work in progress + +## Post-Quantum Cryptography (PQC) + +- **ML-DSA** (Module Lattice Digital Signature Algorithm, FIPS 204) - ML-DSA-44, ML-DSA-65, ML-DSA-87 +- **ML-KEM** (Module Lattice Key Encapsulation Mechanism, FIPS 203) - ML-KEM-512, ML-KEM-768, ML-KEM-1024 + +These algorithms provide quantum-resistant cryptography. + # `Crypto` From e2c1c80973047710a7c1ad23dccf989fc2191506 Mon Sep 17 00:00:00 2001 From: Brad Anderson Date: Fri, 5 Dec 2025 12:04:48 -0500 Subject: [PATCH 09/11] fix; final cleanup --- .gitignore | 2 ++ bun.lock | 23 ++++++++++++++++++----- docs/implementation-coverage.md | 2 +- example/package.json | 3 ++- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 3ea4b7ba..0690dd7c 100644 --- a/.gitignore +++ b/.gitignore @@ -187,4 +187,6 @@ tsconfig.tsbuildinfo # development stuffs *scratch* +# agents +.claude/settings.local.json .agent/ diff --git a/bun.lock b/bun.lock index a1e08560..1fea30d3 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "dependencies": { @@ -29,7 +30,7 @@ }, "example": { "name": "react-native-quick-crypto-example", - "version": "1.0.0", + "version": "1.0.1", "dependencies": { "@craftzdog/react-native-buffer": "6.1.0", "@noble/ciphers": "^2.0.1", @@ -46,9 +47,10 @@ "react": "19.1.0", "react-native": "0.81.1", "react-native-bouncy-checkbox": "4.1.2", + "react-native-fast-encoder": "0.3.1", "react-native-nitro-modules": "0.29.1", "react-native-quick-base64": "2.2.2", - "react-native-quick-crypto": "1.0.0", + "react-native-quick-crypto": "workspace:*", "react-native-safe-area-context": "^5.2.2", "react-native-screens": "4.18.0", "react-native-vector-icons": "^10.3.0", @@ -78,17 +80,17 @@ "@types/react-test-renderer": "^19.1.0", "babel-jest": "29.7.0", "babel-plugin-module-resolver": "5.0.2", + "jose": "6.1.3", "react-test-renderer": "19.1.0", "typescript": "^5.8.3", }, }, "packages/react-native-quick-crypto": { "name": "react-native-quick-crypto", - "version": "1.0.0", + "version": "1.0.1", "dependencies": { "@craftzdog/react-native-buffer": "6.1.0", "events": "3.3.0", - "expo-build-properties": "0.14.6", "react-native-quick-base64": "2.2.2", "readable-stream": "4.5.2", "safe-buffer": "^5.2.1", @@ -101,6 +103,7 @@ "@types/readable-stream": "4.0.18", "del-cli": "7.0.0", "expo": "^54.0.25", + "expo-build-properties": "^1.0.0", "jest": "29.7.0", "nitro-codegen": "0.29.1", "react-native-builder-bob": "0.40.15", @@ -108,12 +111,14 @@ }, "peerDependencies": { "expo": ">=48.0.0", + "expo-build-properties": "*", "react": "*", "react-native": "*", "react-native-nitro-modules": ">=0.29.1", }, "optionalPeers": [ "expo", + "expo-build-properties", ], }, }, @@ -1303,7 +1308,7 @@ "expo-asset": ["expo-asset@12.0.10", "", { "dependencies": { "@expo/image-utils": "^0.8.7", "expo-constants": "~18.0.10" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-pZyeJkoDsALh4gpCQDzTA/UCLaPH/1rjQNGubmLn/uDM27S4iYJb/YWw4+CNZOtd5bCUOhDPg5DtGQnydNFSXg=="], - "expo-build-properties": ["expo-build-properties@0.14.6", "", { "dependencies": { "ajv": "^8.11.0", "semver": "^7.6.0" }, "peerDependencies": { "expo": "*" } }, "sha512-46+gcnFxb2Dz2TFEhFlEJ11qT85THlPtFgkRKQ3a11S3+stgDzDBC2WwbXS5/GMINLIDdBFbbZlajgVND0tMnQ=="], + "expo-build-properties": ["expo-build-properties@1.0.10", "", { "dependencies": { "ajv": "^8.11.0", "semver": "^7.6.0" }, "peerDependencies": { "expo": "*" } }, "sha512-mFCZbrbrv0AP5RB151tAoRzwRJelqM7bCJzCkxpu+owOyH+p/rFC/q7H5q8B9EpVWj8etaIuszR+gKwohpmu1Q=="], "expo-constants": ["expo-constants@18.0.10", "", { "dependencies": { "@expo/config": "~12.0.10", "@expo/env": "~2.0.7" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-Rhtv+X974k0Cahmvx6p7ER5+pNhBC0XbP1lRviL2J1Xl4sT2FBaIuIxF/0I0CbhOsySf0ksqc5caFweAy9Ewiw=="], @@ -1361,6 +1366,8 @@ "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, ""], + "flatbuffers": ["flatbuffers@2.0.6", "", {}, "sha512-QTTZTXTbVfuOVQu2X6eLOw4vefUxnFJZxAKeN3rEPhjEzBtIbehimJLfVGHPM8iX0Na+9i76SBEg0skf0c0sCA=="], + "flatted": ["flatted@3.3.1", "", {}, ""], "flow-enums-runtime": ["flow-enums-runtime@0.0.6", "", {}, ""], @@ -1679,6 +1686,8 @@ "joi": ["joi@17.13.3", "", { "dependencies": { "@hapi/hoek": "^9.3.0", "@hapi/topo": "^5.1.0", "@sideway/address": "^4.1.5", "@sideway/formula": "^3.0.1", "@sideway/pinpoint": "^2.0.0" } }, ""], + "jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="], + "js-tokens": ["js-tokens@4.0.0", "", {}, ""], "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": "bin/js-yaml.js" }, ""], @@ -2101,6 +2110,8 @@ "react-native-builder-bob": ["react-native-builder-bob@0.40.15", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-transform-flow-strip-types": "^7.26.5", "@babel/plugin-transform-strict-mode": "^7.24.7", "@babel/preset-env": "^7.25.2", "@babel/preset-react": "^7.24.7", "@babel/preset-typescript": "^7.24.7", "arktype": "^2.1.15", "babel-plugin-syntax-hermes-parser": "^0.28.0", "browserslist": "^4.20.4", "cross-spawn": "^7.0.3", "dedent": "^0.7.0", "del": "^6.1.1", "escape-string-regexp": "^4.0.0", "fs-extra": "^10.1.0", "glob": "^8.0.3", "is-git-dirty": "^2.0.1", "json5": "^2.2.1", "kleur": "^4.1.4", "prompts": "^2.4.2", "react-native-monorepo-config": "^0.1.8", "which": "^2.0.2", "yargs": "^17.5.1" }, "bin": { "bob": "bin/bob" } }, "sha512-p70LXlYOe53bZeEQchbK1hIhBGQsuB13bT81E7KkOe4dQABwMV6c585A4np0c3nwTTaqQMDYUat4x7J0oLnjxQ=="], + "react-native-fast-encoder": ["react-native-fast-encoder@0.3.1", "", { "dependencies": { "big-integer": "^1.6.51", "flatbuffers": "2.0.6" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-D5ZEbffxayZImtUUErbNod+7IvJITkxTCPZAQTFg6lrSjl/443Mk5CvT9nKpJC1w4cg2llWk6QP8nczij5ClcQ=="], + "react-native-monorepo-config": ["react-native-monorepo-config@0.1.10", "", { "dependencies": { "escape-string-regexp": "^5.0.0", "fast-glob": "^3.3.3" } }, "sha512-v0rlaLZiCUg95Mpw6xNRQce5k9yio0qscKjNQaPtFYMNL75YugS2UPUItIPLIRbZubK+s2/LRzBjX+mdyUgh4g=="], "react-native-nitro-modules": ["react-native-nitro-modules@0.29.1", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-91A/Lc4Zc1Bvzj1iMSnD6vA5Swqv8aVcwGcv8ddjoPd9mahNvVS2arFh3o7kAqRH4RIh3KcQ0NpYslu7AYn55Q=="], @@ -3013,6 +3024,8 @@ "expo-build-properties/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], + "expo-build-properties/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], + "expo-modules-autolinking/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, ""], diff --git a/docs/implementation-coverage.md b/docs/implementation-coverage.md index bdf3cbb6..b9452cd1 100644 --- a/docs/implementation-coverage.md +++ b/docs/implementation-coverage.md @@ -264,7 +264,7 @@ These algorithms provide quantum-resistant cryptography. * 🚧 `subtle.importKey(format, keyData, algorithm, extractable, keyUsages)` * ✅ `subtle.sign(algorithm, key, data)` * ✅ `subtle.unwrapKey(format, wrappedKey, unwrappingKey, unwrapAlgo, unwrappedKeyAlgo, extractable, keyUsages)` - * 🚧 `subtle.verify(algorithm, key, signature, data)` + * ✅ `subtle.verify(algorithm, key, signature, data)` * ✅ `subtle.wrapKey(format, key, wrappingKey, wrapAlgo)` ## `subtle.decrypt` diff --git a/example/package.json b/example/package.json index 735992e8..4913bf05 100644 --- a/example/package.json +++ b/example/package.json @@ -36,6 +36,7 @@ "react": "19.1.0", "react-native": "0.81.1", "react-native-bouncy-checkbox": "4.1.2", + "react-native-fast-encoder": "0.3.1", "react-native-nitro-modules": "0.29.1", "react-native-quick-base64": "2.2.2", "react-native-quick-crypto": "workspace:*", @@ -75,4 +76,4 @@ "engines": { "node": ">=18" } -} \ No newline at end of file +} From dae1fb9ca2d3b579625efee155d2b19fbd0291ea Mon Sep 17 00:00:00 2001 From: Brad Anderson Date: Fri, 5 Dec 2025 12:26:36 -0500 Subject: [PATCH 10/11] fix: make screenshot upload optional when IMGBB_API_KEY is unavailable (for forks) --- .github/actions/post-maestro-screenshot/action.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/actions/post-maestro-screenshot/action.yml b/.github/actions/post-maestro-screenshot/action.yml index ad3dec4f..303495ba 100644 --- a/.github/actions/post-maestro-screenshot/action.yml +++ b/.github/actions/post-maestro-screenshot/action.yml @@ -14,7 +14,7 @@ inputs: default: 'unknown' imgbb-api-key: description: 'ImgBB API key for image hosting' - required: true + required: false runs: using: 'composite' @@ -69,7 +69,7 @@ runs: fi - name: Upload screenshot to ImgBB - if: steps.check-screenshot.outputs.exists == 'true' && github.event_name == 'pull_request' + if: steps.check-screenshot.outputs.exists == 'true' && github.event_name == 'pull_request' && inputs.imgbb-api-key != '' id: upload-screenshot uses: McCzarny/upload-image@v2.0.0 with: @@ -82,6 +82,7 @@ runs: uses: peter-evans/find-comment@v3 id: find-comment if: github.event_name == 'pull_request' + continue-on-error: true with: issue-number: ${{ github.event.pull_request.number }} comment-author: 'github-actions[bot]' @@ -89,6 +90,7 @@ runs: - name: Create or update PR comment (with screenshot) if: github.event_name == 'pull_request' && steps.check-screenshot.outputs.exists == 'true' && steps.upload-screenshot.outputs.url + continue-on-error: true uses: peter-evans/create-or-update-comment@v4 with: token: ${{ inputs.github-token }} @@ -113,6 +115,7 @@ runs: - name: Create or update PR comment (no screenshot) if: github.event_name == 'pull_request' && steps.check-screenshot.outputs.exists != 'true' + continue-on-error: true uses: peter-evans/create-or-update-comment@v4 with: token: ${{ inputs.github-token }} @@ -135,6 +138,7 @@ runs: - name: Create or update PR comment (upload failed) if: github.event_name == 'pull_request' && steps.check-screenshot.outputs.exists == 'true' && !steps.upload-screenshot.outputs.url + continue-on-error: true uses: peter-evans/create-or-update-comment@v4 with: token: ${{ inputs.github-token }} From cfb4b563c8c9189baf4368da794c69cace01bed0 Mon Sep 17 00:00:00 2001 From: Brad Anderson Date: Fri, 5 Dec 2025 14:25:05 -0600 Subject: [PATCH 11/11] fix: implement AES-KW (RFC 3394) for wrapKey/unwrapKey operations Fixes wrapKey/unwrapKey operations with AES-KW algorithm that were failing in e2e tests on both iOS and Android. C++ changes (HybridCipher.cpp): - Add EVP_CIPHER_CTX_FLAG_WRAP_ALLOW flag for wrap ciphers (required in OpenSSL 3.x) - Disable padding for AES-KW ciphers - Add error checking to EVP_CipherUpdate to catch OpenSSL failures TypeScript changes (subtle.ts): - Use RFC 3394 default IV (0xa6a6a6a6a6a6a6a6) instead of empty IV - Fix JWK format padding calculation to account for null terminator - Add input validation for AES-KW (8-byte alignment, minimum 16 bytes) - Fix cipher type naming to match Node.js (aes*-wrap) - Handle null terminator when unwrapping JWK format All wrapKey/unwrapKey tests now passing. --- example/src/hooks/useTestsList.ts | 2 +- example/src/tests/subtle/import_export.ts | 5 -- .../cpp/cipher/HybridCipher.cpp | 18 ++++- .../react-native-quick-crypto/src/subtle.ts | 69 ++++++++++++++++--- 4 files changed, 77 insertions(+), 17 deletions(-) diff --git a/example/src/hooks/useTestsList.ts b/example/src/hooks/useTestsList.ts index 1ed86d7d..9db850f1 100644 --- a/example/src/hooks/useTestsList.ts +++ b/example/src/hooks/useTestsList.ts @@ -19,6 +19,7 @@ import '../tests/keys/public_cipher'; import '../tests/keys/sign_verify_streaming'; import '../tests/pbkdf2/pbkdf2_tests'; import '../tests/random/random_tests'; +import '../tests/subtle/x25519_x448'; import '../tests/subtle/deriveBits'; import '../tests/subtle/derive_key'; import '../tests/subtle/digest'; @@ -28,7 +29,6 @@ import '../tests/subtle/import_export'; import '../tests/subtle/jwk_rfc7517_tests'; import '../tests/subtle/sign_verify'; import '../tests/subtle/wrap_unwrap'; -import '../tests/subtle/x25519_x448'; export const useTestsList = (): [ TestSuites, diff --git a/example/src/tests/subtle/import_export.ts b/example/src/tests/subtle/import_export.ts index 0c5fb678..fe5006d5 100644 --- a/example/src/tests/subtle/import_export.ts +++ b/example/src/tests/subtle/import_export.ts @@ -1483,11 +1483,6 @@ async function testImportSpki( expect(key.extractable).to.equal(extractable); expect(key.usages).to.deep.equal(publicUsages); expect(key.algorithm.name).to.equal(name); - console.log('[RSA TEST DEBUG]', { - modulusLength: key.algorithm.modulusLength, - expected: parseInt(size, 10), - algorithm: JSON.stringify(key.algorithm), - }); expect(key.algorithm.modulusLength).to.equal(parseInt(size, 10)); expect(key.algorithm.publicExponent).to.deep.equal(new Uint8Array([1, 0, 1])); expect((key.algorithm.hash as { name: string }).name).to.equal(hash); diff --git a/packages/react-native-quick-crypto/cpp/cipher/HybridCipher.cpp b/packages/react-native-quick-crypto/cpp/cipher/HybridCipher.cpp index bc194834..72d7dd18 100644 --- a/packages/react-native-quick-crypto/cpp/cipher/HybridCipher.cpp +++ b/packages/react-native-quick-crypto/cpp/cipher/HybridCipher.cpp @@ -86,6 +86,14 @@ void HybridCipher::init(const std::shared_ptr cipher_key, const std ctx = nullptr; throw std::runtime_error("HybridCipher: Failed to set key/IV: " + std::string(err_buf)); } + + // For AES-KW (wrap ciphers), set the WRAP_ALLOW flag and disable padding + std::string cipher_name(cipher_type); + if (cipher_name.find("-wrap") != std::string::npos) { + // This flag is required for AES-KW in OpenSSL 3.x + EVP_CIPHER_CTX_set_flags(ctx, EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); + EVP_CIPHER_CTX_set_padding(ctx, 0); + } } std::shared_ptr HybridCipher::update(const std::shared_ptr& data) { @@ -100,7 +108,15 @@ std::shared_ptr HybridCipher::update(const std::shared_ptrdata(), in_len); + int ret = EVP_CipherUpdate(ctx, out, &out_len, native_data->data(), in_len); + + if (!ret) { + unsigned long err = ERR_get_error(); + char err_buf[256]; + ERR_error_string_n(err, err_buf, sizeof(err_buf)); + delete[] out; + throw std::runtime_error("Cipher update failed: " + std::string(err_buf)); + } // Create and return a new buffer of exact size needed return std::make_shared(out, out_len, [=]() { delete[] out; }); diff --git a/packages/react-native-quick-crypto/src/subtle.ts b/packages/react-native-quick-crypto/src/subtle.ts index e591a1f5..53d5b8ea 100644 --- a/packages/react-native-quick-crypto/src/subtle.ts +++ b/packages/react-native-quick-crypto/src/subtle.ts @@ -380,23 +380,46 @@ async function aesKwCipher( key: CryptoKey, data: ArrayBuffer, ): Promise { + const isWrap = mode === CipherOrWrapMode.kWebCryptoCipherEncrypt; + + // AES-KW requires input to be a multiple of 8 bytes (64 bits) + if (data.byteLength % 8 !== 0) { + throw lazyDOMException( + `AES-KW input length must be a multiple of 8 bytes, got ${data.byteLength}`, + 'OperationError', + ); + } + + // AES-KW requires at least 16 bytes of input (128 bits) + if (isWrap && data.byteLength < 16) { + throw lazyDOMException( + `AES-KW input must be at least 16 bytes, got ${data.byteLength}`, + 'OperationError', + ); + } + // Get cipher type based on key length const keyLength = (key.algorithm as { length: number }).length; - const isWrap = mode === CipherOrWrapMode.kWebCryptoCipherEncrypt; - const cipherType = isWrap - ? `id-aes${keyLength}-wrap` - : `id-aes${keyLength}-wrap`; + // Use aes*-wrap for both operations (matching Node.js) + const cipherType = `aes${keyLength}-wrap`; + + // Export key material + const exportedKey = key.keyObject.export(); + const cipherKey = bufferLikeToArrayBuffer(exportedKey); + + // AES-KW uses a default IV as specified in RFC 3394 + const defaultWrapIV = new Uint8Array([ + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + ]); - // AES-KW uses the same cipher for both wrap and unwrap, - // but Node.js distinguishes with different cipher names const factory = NitroModules.createHybridObject('CipherFactory'); const cipher = factory.createCipher({ isCipher: isWrap, cipherType, - cipherKey: bufferLikeToArrayBuffer(key.keyObject.export()), - iv: new ArrayBuffer(0), // AES-KW doesn't use IV + cipherKey, + iv: defaultWrapIV.buffer, // RFC 3394 default IV for AES-KW }); // Process data @@ -1625,7 +1648,20 @@ export class Subtle { if (format === 'jwk') { const jwkString = JSON.stringify(exported); const buffer = SBuffer.from(jwkString, 'utf8'); - keyData = bufferLikeToArrayBuffer(buffer); + + // For AES-KW, pad to multiple of 8 bytes (accounting for null terminator) + if (wrapAlgorithm.name === 'AES-KW') { + const length = buffer.length; + // Add 1 for null terminator, then pad to multiple of 8 + const paddedLength = Math.ceil((length + 1) / 8) * 8; + const paddedBuffer = SBuffer.alloc(paddedLength); + buffer.copy(paddedBuffer); + // Null terminator for JSON string (remaining bytes are already zeros from alloc) + paddedBuffer.writeUInt8(0, length); + keyData = bufferLikeToArrayBuffer(paddedBuffer); + } else { + keyData = bufferLikeToArrayBuffer(buffer); + } } else { keyData = exported as ArrayBuffer; } @@ -1670,7 +1706,20 @@ export class Subtle { let keyData: BufferLike | JWK; if (format === 'jwk') { const buffer = SBuffer.from(decrypted); - const jwkString = buffer.toString('utf8'); + // For AES-KW, the data may be padded - find the null terminator + let jwkString: string; + if (unwrapAlgorithm.name === 'AES-KW') { + // Find the null terminator (if present) to get the original string + const nullIndex = buffer.indexOf(0); + if (nullIndex !== -1) { + jwkString = buffer.toString('utf8', 0, nullIndex); + } else { + // No null terminator, try to parse the whole buffer + jwkString = buffer.toString('utf8').trim(); + } + } else { + jwkString = buffer.toString('utf8'); + } keyData = JSON.parse(jwkString) as JWK; } else { keyData = decrypted;