diff --git a/.gitignore b/.gitignore index 9a92b272..5f68e322 100644 --- a/.gitignore +++ b/.gitignore @@ -184,6 +184,5 @@ tsconfig.tsbuildinfo .java-version # development stuffs -example0/ *scratch* diff --git a/bun.lockb b/bun.lockb index 1bd2c97f..987ab0ff 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index f34c463a..22d7dc98 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -31,7 +31,7 @@ PODS: - ReactCommon/turbomodule/core - Yoga - OpenSSL-Universal (3.3.2000) - - QuickCrypto (1.0.0-beta.5): + - QuickCrypto (1.0.0-beta.7): - DoubleConversion - glog - hermes-engine @@ -1720,7 +1720,7 @@ DEPENDENCIES: - 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`) + - QuickCrypto (from `../../node_modules/react-native-quick-crypto`) - RCT-Folly (from `../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - RCT-Folly/Fabric (from `../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - RCTDeprecation (from `../../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`) @@ -1807,7 +1807,7 @@ EXTERNAL SOURCES: NitroModules: :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" RCTDeprecation: @@ -1940,7 +1940,7 @@ SPEC CHECKSUMS: hermes-engine: 46f1ffbf0297f4298862068dd4c274d4ac17a1fd NitroModules: 47399393665e69228b29a17f501c7b453679ccc0 OpenSSL-Universal: b60a3702c9fea8b3145549d421fdb018e53ab7b4 - QuickCrypto: 8d76ae3a0bf60509f671193eb4ed666a80da34cb + QuickCrypto: 2a4bcacfec3cc81d56205e6b12f190d492f9a1e7 RCT-Folly: 84578c8756030547307e4572ab1947de1685c599 RCTDeprecation: fde92935b3caa6cb65cbff9fbb7d3a9867ffb259 RCTRequired: 75c6cee42d21c1530a6f204ba32ff57335d19007 diff --git a/example/package.json b/example/package.json index cf4609a5..dcd49506 100644 --- a/example/package.json +++ b/example/package.json @@ -47,7 +47,7 @@ "devDependencies": { "@babel/core": "7.25.2", "@babel/plugin-transform-class-static-block": "7.26.0", - "@babel/preset-env": "7.25.3", + "@babel/preset-env": "7.26.0", "@babel/runtime": "7.25.0", "@eslint/compat": "^1.1.1", "@eslint/js": "^9.9.0", diff --git a/example/src/tests/ed25519/ed25519_tests.ts b/example/src/tests/ed25519/ed25519_tests.ts index e27f8241..9302adce 100644 --- a/example/src/tests/ed25519/ed25519_tests.ts +++ b/example/src/tests/ed25519/ed25519_tests.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-unused-expressions */ import { Ed, randomBytes, ab2str } from 'react-native-quick-crypto'; +import { Buffer } from '@craftzdog/react-native-buffer'; // import type { // // KeyObject, // // CFRGKeyPairType, @@ -12,6 +13,10 @@ import { test } from '../util'; const SUITE = 'ed25519'; +const encoder = new TextEncoder(); +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const encode = (data: any): Uint8Array => encoder.encode(JSON.stringify(data)); + /* const jwkOptions: GenerateKeyPairOptions = { publicKeyEncoding: { @@ -75,25 +80,30 @@ test(SUITE, 'sign/verify - bad signature does not verify', async () => { expect(verified).to.be.false; }); -test( - SUITE, - 'sign/verify with non-internally generated private key', - async () => { - let ed1: Ed | null = new Ed('ed25519', {}); - await ed1.generateKeyPair(); - const priv = ed1.getPrivateKey(); - ed1 = null; +test(SUITE, 'sign/verify - switched args does not verify', async () => { + const ed = new Ed('ed25519', {}); + await ed.generateKeyPair(); + const signature = await ed.sign(data1.buffer); + // verify(message, signature) is switched + const verified = await ed.verify(data1.buffer, signature); + expect(verified).to.be.false; +}); - const ed2 = new Ed('ed25519', {}); - const signature = await ed2.sign(data1.buffer, priv); - const verified = await ed2.verify(signature, data1.buffer, priv); - expect(verified).to.be.true; - }, -); +test(SUITE, 'sign/verify - non-internally generated private key', async () => { + let ed1: Ed | null = new Ed('ed25519', {}); + await ed1.generateKeyPair(); + const priv = ed1.getPrivateKey(); + ed1 = null; + + const ed2 = new Ed('ed25519', {}); + const signature = await ed2.sign(data1.buffer, priv); + const verified = await ed2.verify(signature, data1.buffer, priv); + expect(verified).to.be.true; +}); test( SUITE, - 'sign/verify with bad non-internally generated private key', + 'sign/verify - bad non-internally generated private key', async () => { let ed1: Ed | null = new Ed('ed25519', {}); await ed1.generateKeyPair(); @@ -108,3 +118,16 @@ test( expect(verified).to.be.false; }, ); + +test(SUITE, 'sign/verify - Uint8Arrays', () => { + const data = { b: 'world', a: 'hello' }; + + const ed1 = new Ed('ed25519', {}); + ed1.generateKeyPairSync(); + const priv = new Uint8Array(ed1.getPrivateKey()); + + const ed2 = new Ed('ed25519', {}); + const signature = new Uint8Array(ed2.signSync(encode(data), priv)); + const verified = ed2.verifySync(signature, encode(data), priv); + expect(verified).to.be.true; +}); diff --git a/example/tsconfig.json b/example/tsconfig.json index 57aefe62..4e7294b3 100644 --- a/example/tsconfig.json +++ b/example/tsconfig.json @@ -11,6 +11,9 @@ "**.*.tsx", ], "compilerOptions": { - "jsx": "react" + "jsx": "react", + "paths": { + "react-native-quick-crypto": ["../packages/react-native-quick-crypto/src"], + }, }, } diff --git a/packages/react-native-quick-crypto/QuickCrypto.podspec b/packages/react-native-quick-crypto/QuickCrypto.podspec index a3072816..a3ecbd82 100644 --- a/packages/react-native-quick-crypto/QuickCrypto.podspec +++ b/packages/react-native-quick-crypto/QuickCrypto.podspec @@ -2,7 +2,7 @@ require "json" package = JSON.parse(File.read(File.join(__dir__, "package.json"))) -Pod::UI.puts "[QuickCrypto] crypto just got quicker 💨" +Pod::UI.puts "[QuickCrypto] 💨 crypto just got quicker" Pod::Spec.new do |s| s.name = "QuickCrypto" @@ -12,7 +12,11 @@ Pod::Spec.new do |s| s.license = package["license"] s.authors = package["authors"] - s.platforms = { :ios => min_ios_version_supported } + s.ios.deployment_target = min_ios_version_supported + s.visionos.deployment_target = 1.0 + s.macos.deployment_target = 10.13 + s.tvos.deployment_target = 13.4 + s.source = { :git => "https://github.com/margelo/react-native-quick-crypto.git", :tag => "#{s.version}" } s.source_files = [ @@ -32,7 +36,6 @@ Pod::Spec.new do |s| load 'nitrogen/generated/ios/QuickCrypto+autolinking.rb' add_nitrogen_files(s) - install_modules_dependencies(s) s.dependency "OpenSSL-Universal" - + install_modules_dependencies(s) end diff --git a/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridEdKeyPairSpec.hpp b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridEdKeyPairSpec.hpp index 294df208..059b2b8e 100644 --- a/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridEdKeyPairSpec.hpp +++ b/packages/react-native-quick-crypto/nitrogen/generated/shared/c++/HybridEdKeyPairSpec.hpp @@ -58,8 +58,8 @@ namespace margelo::nitro::crypto { virtual std::shared_ptr getPrivateKey() = 0; virtual std::shared_ptr>> sign(const std::shared_ptr& message, const std::optional>& key) = 0; virtual std::shared_ptr signSync(const std::shared_ptr& message, const std::optional>& key) = 0; - virtual std::shared_ptr> verify(const std::shared_ptr& message, const std::shared_ptr& signature, const std::optional>& key) = 0; - virtual bool verifySync(const std::shared_ptr& message, const std::shared_ptr& signature, const std::optional>& key) = 0; + virtual std::shared_ptr> verify(const std::shared_ptr& signature, const std::shared_ptr& message, const std::optional>& key) = 0; + virtual bool verifySync(const std::shared_ptr& signature, const std::shared_ptr& message, const std::optional>& key) = 0; virtual void setCurve(const std::string& curve) = 0; protected: diff --git a/packages/react-native-quick-crypto/package.json b/packages/react-native-quick-crypto/package.json index 235bd645..79d3b3dd 100644 --- a/packages/react-native-quick-crypto/package.json +++ b/packages/react-native-quick-crypto/package.json @@ -90,7 +90,7 @@ "jest": "29.7.0", "nitro-codegen": "0.18.2", "prettier": "3.3.3", - "react-native-builder-bob": "0.33.3", + "react-native-builder-bob": "0.35.2", "release-it": "17.6.0", "typescript": "5.1.6", "typescript-eslint": "^8.1.0" diff --git a/packages/react-native-quick-crypto/src/ed.ts b/packages/react-native-quick-crypto/src/ed.ts index 34863327..f553d579 100644 --- a/packages/react-native-quick-crypto/src/ed.ts +++ b/packages/react-native-quick-crypto/src/ed.ts @@ -1,6 +1,7 @@ import { NitroModules } from 'react-native-nitro-modules'; +import { binaryLikeToArrayBuffer as toAB } from './utils'; import type { EdKeyPair } from './specs/edKeyPair.nitro'; -import type { CFRGKeyPairType, KeyPairGenConfig } from './utils'; +import type { BinaryLike, CFRGKeyPairType, KeyPairGenConfig } from './utils'; export class Ed { type: CFRGKeyPairType; @@ -44,33 +45,35 @@ export class Ed { return this.native.getPrivateKey(); } - async sign(message: ArrayBuffer, key?: ArrayBuffer): Promise { - return key ? this.native.sign(message, key) : this.native.sign(message); + async sign(message: BinaryLike, key?: BinaryLike): Promise { + return key + ? this.native.sign(toAB(message), toAB(key)) + : this.native.sign(toAB(message)); } - signSync(message: ArrayBuffer, key?: ArrayBuffer): ArrayBuffer { + signSync(message: BinaryLike, key?: BinaryLike): ArrayBuffer { return key - ? this.native.signSync(message, key) - : this.native.signSync(message); + ? this.native.signSync(toAB(message), toAB(key)) + : this.native.signSync(toAB(message)); } async verify( - message: ArrayBuffer, - signature: ArrayBuffer, - key?: ArrayBuffer, + signature: BinaryLike, + message: BinaryLike, + key?: BinaryLike, ): Promise { return key - ? this.native.verify(message, signature, key) - : this.native.verify(message, signature); + ? this.native.verify(toAB(signature), toAB(message), toAB(key)) + : this.native.verify(toAB(signature), toAB(message)); } verifySync( - message: ArrayBuffer, - signature: ArrayBuffer, - key?: ArrayBuffer, + signature: BinaryLike, + message: BinaryLike, + key?: BinaryLike, ): boolean { return key - ? this.native.verifySync(message, signature, key) - : this.native.verifySync(message, signature); + ? this.native.verifySync(toAB(signature), toAB(message), toAB(key)) + : this.native.verifySync(toAB(signature), toAB(message)); } } diff --git a/packages/react-native-quick-crypto/src/random.ts b/packages/react-native-quick-crypto/src/random.ts index 7820830b..abe60998 100644 --- a/packages/react-native-quick-crypto/src/random.ts +++ b/packages/react-native-quick-crypto/src/random.ts @@ -1,5 +1,5 @@ import { Buffer } from '@craftzdog/react-native-buffer'; -import type { ArrayBufferView, RandomCallback } from './utils'; +import type { ABV, RandomCallback } from './utils'; import { abvToArrayBuffer } from './utils'; import { NitroModules } from 'react-native-nitro-modules'; import type { Random } from './specs/random.nitro'; @@ -14,25 +14,25 @@ function getNative(): Random { return random; } -export function randomFill( +export function randomFill( buffer: T, callback: RandomCallback, ): void; -export function randomFill( +export function randomFill( buffer: T, offset: number, callback: RandomCallback, ): void; -export function randomFill( +export function randomFill( buffer: T, offset: number, size: number, callback: RandomCallback, ): void; -export function randomFill(buffer: ArrayBufferView, ...rest: unknown[]): void { +export function randomFill(buffer: ABV, ...rest: unknown[]): void { if (typeof rest[rest.length - 1] !== 'function') { throw new Error('No callback provided to randomFill'); } @@ -65,17 +65,13 @@ export function randomFill(buffer: ArrayBufferView, ...rest: unknown[]): void { ); } -export function randomFillSync( +export function randomFillSync( buffer: T, offset?: number, size?: number, ): T; -export function randomFillSync( - buffer: ArrayBufferView, - offset: number = 0, - size?: number, -) { +export function randomFillSync(buffer: ABV, offset: number = 0, size?: number) { getNative(); buffer = abvToArrayBuffer(buffer); const res = random.randomFillSync(buffer, offset, size ?? buffer.byteLength); diff --git a/packages/react-native-quick-crypto/src/specs/edKeyPair.nitro.ts b/packages/react-native-quick-crypto/src/specs/edKeyPair.nitro.ts index 5e14d71c..58db2737 100644 --- a/packages/react-native-quick-crypto/src/specs/edKeyPair.nitro.ts +++ b/packages/react-native-quick-crypto/src/specs/edKeyPair.nitro.ts @@ -27,13 +27,13 @@ export interface EdKeyPair signSync(message: ArrayBuffer, key?: ArrayBuffer): ArrayBuffer; verify( - message: ArrayBuffer, signature: ArrayBuffer, + message: ArrayBuffer, key?: ArrayBuffer, ): Promise; verifySync( - message: ArrayBuffer, signature: ArrayBuffer, + message: ArrayBuffer, key?: ArrayBuffer, ): boolean; diff --git a/packages/react-native-quick-crypto/src/utils/conversion.ts b/packages/react-native-quick-crypto/src/utils/conversion.ts index 5e9e63ab..e6f46671 100644 --- a/packages/react-native-quick-crypto/src/utils/conversion.ts +++ b/packages/react-native-quick-crypto/src/utils/conversion.ts @@ -1,39 +1,62 @@ -import { Buffer } from '@craftzdog/react-native-buffer'; -import { Buffer as SBuffer } from 'safe-buffer'; -import type { ArrayBufferView, BinaryLikeNode, BufferLike } from './types'; +import { Buffer as CraftzdogBuffer } from '@craftzdog/react-native-buffer'; +import { Buffer as SafeBuffer } from 'safe-buffer'; +import type { ABV, BinaryLikeNode, BufferLike } from './types'; -export const abvToArrayBuffer = (buffer: ArrayBufferView) => { - if (Buffer.isBuffer(buffer)) { - return buffer.buffer; +/** + * Converts supplied argument to an ArrayBuffer. Note this does not copy the + * data so it is faster than toArrayBuffer. Not copying is important for + * functions like randomFill which need to be able to write to the underlying + * buffer. + * @param buf + * @returns ArrayBuffer + */ +export const abvToArrayBuffer = (buf: ABV) => { + if (CraftzdogBuffer.isBuffer(buf)) { + return buf.buffer as ArrayBuffer; } - if (ArrayBuffer.isView(buffer)) { - return buffer.buffer; + if (ArrayBuffer.isView(buf)) { + return buf.buffer as ArrayBuffer; } - return buffer; + return buf as ArrayBuffer; }; -export function toArrayBuffer(buf: Buffer | SBuffer): ArrayBuffer { - if (Buffer.isBuffer(buf) && buf?.buffer?.slice) { - return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); +/** + * Converts supplied argument to an ArrayBuffer. Note this copies data if the + * supplied buffer has the .slice() method, so can be a bit slow. + * @param buf + * @returns ArrayBuffer + */ +export function toArrayBuffer( + buf: CraftzdogBuffer | SafeBuffer | ArrayBufferView, +): ArrayBuffer { + if (CraftzdogBuffer.isBuffer(buf) || ArrayBuffer.isView(buf)) { + if (buf?.buffer?.slice) { + return buf.buffer.slice( + buf.byteOffset, + buf.byteOffset + buf.byteLength, + ) as ArrayBuffer; + } else { + return buf.buffer as ArrayBuffer; + } } const ab = new ArrayBuffer(buf.length); const view = new Uint8Array(ab); for (let i = 0; i < buf.length; ++i) { - view[i] = SBuffer.isBuffer(buf) ? buf.readUInt8(i) : buf[i]!; + view[i] = SafeBuffer.isBuffer(buf) ? buf.readUInt8(i) : buf[i]!; } return ab; } export function bufferLikeToArrayBuffer(buf: BufferLike): ArrayBuffer { - if (Buffer.isBuffer(buf)) { - return buf.buffer; - } - if (SBuffer.isBuffer(buf)) { + // Buffer + if (CraftzdogBuffer.isBuffer(buf) || SafeBuffer.isBuffer(buf)) { return toArrayBuffer(buf); } + // ArrayBufferView if (ArrayBuffer.isView(buf)) { - return buf.buffer; + return toArrayBuffer(buf); } + // ArrayBuffer return buf; } @@ -49,7 +72,7 @@ export function binaryLikeToArrayBuffer( ); } - const buffer = Buffer.from(input, encoding); + const buffer = CraftzdogBuffer.from(input, encoding); return buffer.buffer.slice( buffer.byteOffset, @@ -58,14 +81,14 @@ export function binaryLikeToArrayBuffer( } // Buffer - if (Buffer.isBuffer(input)) { + if (CraftzdogBuffer.isBuffer(input) || SafeBuffer.isBuffer(input)) { return toArrayBuffer(input); } // ArrayBufferView // TODO add further binary types to BinaryLike, UInt8Array and so for have this array as property if (ArrayBuffer.isView(input)) { - return input.buffer; + return toArrayBuffer(input); } // ArrayBuffer @@ -94,7 +117,7 @@ export function binaryLikeToArrayBuffer( } export function ab2str(buf: ArrayBuffer, encoding: string = 'hex') { - return Buffer.from(buf).toString(encoding); + return CraftzdogBuffer.from(buf).toString(encoding); } export const kEmptyObject = Object.freeze(Object.create(null)); diff --git a/packages/react-native-quick-crypto/src/utils/types.ts b/packages/react-native-quick-crypto/src/utils/types.ts index 8deca073..fba396b2 100644 --- a/packages/react-native-quick-crypto/src/utils/types.ts +++ b/packages/react-native-quick-crypto/src/utils/types.ts @@ -1,9 +1,9 @@ -import type { Buffer } from '@craftzdog/react-native-buffer'; -import type { Buffer as SBuffer } from 'safe-buffer'; +import type { Buffer as CraftzdogBuffer } from '@craftzdog/react-native-buffer'; +import type { Buffer as SafeBuffer } from 'safe-buffer'; import type { CipherKey } from 'crypto'; // @types/node import type { KeyObjectHandle } from '../specs/keyObjectHandle.nitro'; -export type ArrayBufferView = TypedArray | DataView | ArrayBufferLike | Buffer; +export type ABV = TypedArray | DataView | ArrayBufferLike | CraftzdogBuffer; export type TypedArray = | Uint8Array @@ -18,13 +18,17 @@ export type TypedArray = export type RandomCallback = (err: Error | null, value: T) => void; -export type BufferLike = ArrayBuffer | Buffer | SBuffer | ArrayBufferView; +export type BufferLike = + | ArrayBuffer + | CraftzdogBuffer + | SafeBuffer + | ArrayBufferView; export type BinaryLike = | string | ArrayBuffer - | Buffer - | SBuffer + | CraftzdogBuffer + | SafeBuffer | TypedArray | DataView; @@ -217,7 +221,7 @@ export type GenerateKeyPairOptions = { saltLength?: number; // Minimal salt length in bytes (RSA-PSS). divisorLength?: number; // Size of q in bits (DSA). namedCurve?: string; // Name of the curve to use (EC). - prime?: Buffer; // The prime parameter (DH). + prime?: CraftzdogBuffer; // The prime parameter (DH). primeLength?: number; // Prime length in bits (DH). generator?: number; // Custom generator (DH). Default: 2. groupName?: string; // Diffie-Hellman group name (DH). See crypto.getDiffieHellman().