Skip to content

Commit 479ac22

Browse files
committed
feat: Add DH and ECDH support
1 parent 52bbb36 commit 479ac22

31 files changed

Lines changed: 1608 additions & 204 deletions

bun.lock

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

example/ios/QuickCryptoExample.xcodeproj/project.pbxproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@
398398
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
399399
);
400400
OTHER_LDFLAGS = "$(inherited)";
401-
REACT_NATIVE_PATH = "${PODS_ROOT}/../../../node_modules/react-native";
401+
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
402402
SDKROOT = iphoneos;
403403
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
404404
SWIFT_ENABLE_EXPLICIT_MODULES = NO;

example/src/benchmarks/dh/dh.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import rnqc from 'react-native-quick-crypto';
2+
// @ts-expect-error crypto-browserify missing types
3+
import browserify from 'crypto-browserify';
4+
import type { BenchFn } from '../../types/benchmarks';
5+
import { Bench } from 'tinybench';
6+
7+
const dh_modp14_genKeys: BenchFn = () => {
8+
const bench = new Bench({
9+
name: 'DH modp14 KeyGen',
10+
time: 200,
11+
iterations: 10, // Cap iterations for slow JS implementations
12+
});
13+
14+
bench
15+
.add('rnqc', () => {
16+
const dh = rnqc.getDiffieHellman('modp14');
17+
dh.generateKeys();
18+
})
19+
.add('browserify', () => {
20+
const dh = browserify.getDiffieHellman('modp14');
21+
dh.generateKeys();
22+
});
23+
24+
bench.warmupTime = 100;
25+
return bench;
26+
};
27+
28+
const dh_modp14_computeSecret: BenchFn = () => {
29+
const bench = new Bench({
30+
name: 'DH modp14 Compute',
31+
time: 200,
32+
iterations: 10,
33+
});
34+
35+
const alice = rnqc.getDiffieHellman('modp14');
36+
alice.generateKeys();
37+
const bob = rnqc.getDiffieHellman('modp14');
38+
bob.generateKeys();
39+
const bobPub = bob.getPublicKey();
40+
41+
const bAlice = browserify.getDiffieHellman('modp14');
42+
bAlice.generateKeys();
43+
const bBob = browserify.getDiffieHellman('modp14');
44+
bBob.generateKeys();
45+
const bBobPub = bBob.getPublicKey();
46+
47+
bench
48+
.add('rnqc', () => {
49+
alice.computeSecret(bobPub);
50+
})
51+
.add('browserify', () => {
52+
bAlice.computeSecret(bBobPub);
53+
});
54+
55+
bench.warmupTime = 100;
56+
return bench;
57+
};
58+
59+
export default [dh_modp14_genKeys, dh_modp14_computeSecret];
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import rnqc from 'react-native-quick-crypto';
2+
import { p256 } from '@noble/curves/p256';
3+
import type { BenchFn } from '../../types/benchmarks';
4+
import { Bench } from 'tinybench';
5+
6+
const TIME_MS = 1000;
7+
8+
const ecdh_p256_genKeys: BenchFn = () => {
9+
const bench = new Bench({
10+
name: 'ECDH P-256 KeyGen',
11+
time: TIME_MS,
12+
});
13+
14+
bench
15+
.add('rnqc', () => {
16+
const ecdh = rnqc.createECDH('prime256v1');
17+
ecdh.generateKeys();
18+
})
19+
.add('@noble/curves', () => {
20+
p256.utils.randomPrivateKey();
21+
});
22+
23+
bench.warmupTime = 100;
24+
return bench;
25+
};
26+
27+
const ecdh_p256_computeSecret: BenchFn = () => {
28+
const bench = new Bench({
29+
name: 'ECDH P-256 Compute',
30+
time: TIME_MS,
31+
});
32+
33+
const alice = rnqc.createECDH('prime256v1');
34+
alice.generateKeys();
35+
const bob = rnqc.createECDH('prime256v1');
36+
bob.generateKeys();
37+
const bobPub = bob.getPublicKey();
38+
39+
const nobleAlicePriv = p256.utils.randomPrivateKey();
40+
const nobleBobPriv = p256.utils.randomPrivateKey();
41+
const nobleBobPub = p256.getPublicKey(nobleBobPriv);
42+
43+
bench
44+
.add('rnqc', () => {
45+
alice.computeSecret(bobPub);
46+
})
47+
.add('@noble/curves', () => {
48+
p256.getSharedSecret(nobleAlicePriv, nobleBobPub);
49+
});
50+
51+
bench.warmupTime = 100;
52+
return bench;
53+
};
54+
55+
export default [ecdh_p256_genKeys, ecdh_p256_computeSecret];

example/src/hooks/useBenchmarks.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import hkdf from '../benchmarks/hkdf/hkdf';
77
import hash from '../benchmarks/hash/hash';
88
import hmac from '../benchmarks/hmac/hmac';
99
import pbkdf2 from '../benchmarks/pbkdf2/pbkdf2';
10+
import ecdh from '../benchmarks/ecdh/ecdh';
11+
import dh from '../benchmarks/dh/dh';
1012
import random from '../benchmarks/random/randomBytes';
1113
import scrypt from '../benchmarks/scrypt/scrypt';
1214
import xsalsa20 from '../benchmarks/cipher/xsalsa20';
@@ -32,6 +34,8 @@ export const useBenchmarks = (): [
3234
newSuites.push(new BenchmarkSuite('hash', hash));
3335
newSuites.push(new BenchmarkSuite('hmac', hmac));
3436
newSuites.push(new BenchmarkSuite('hkdf', hkdf));
37+
newSuites.push(new BenchmarkSuite('ecdh', ecdh));
38+
newSuites.push(new BenchmarkSuite('dh', dh));
3539
newSuites.push(
3640
new BenchmarkSuite('random', random, {
3741
'browserify/randombytes':

example/src/hooks/useTestsList.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import '../tests/keys/generate_keypair';
1616
import '../tests/keys/public_cipher';
1717
import '../tests/keys/sign_verify_streaming';
1818
import '../tests/pbkdf2/pbkdf2_tests';
19+
import '../tests/ecdh/ecdh_tests';
20+
import '../tests/dh/dh_tests';
1921
import '../tests/random/random_tests';
2022
import '../tests/scrypt/scrypt_tests';
2123
import '../tests/subtle/deriveBits';

example/src/tests/dh/dh_tests.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { test } from '../util';
2+
import { Buffer } from '@craftzdog/react-native-buffer';
3+
import crypto from 'react-native-quick-crypto';
4+
import { assert } from 'chai';
5+
6+
const SUITE = 'dh';
7+
8+
test(SUITE, 'should create DiffieHellman with size', () => {
9+
const dh = crypto.createDiffieHellman(512);
10+
const prime = dh.getPrime();
11+
assert.isOk(prime);
12+
// Size check approx
13+
assert.isAtLeast(prime.length, 64);
14+
});
15+
16+
test(SUITE, 'should create DiffieHellman with prime', () => {
17+
// 512-bit prime (Group 1 from RFC 2409)
18+
const prime = Buffer.from(
19+
'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1' +
20+
'29024E088A67CC74020BBEA63B139B22514A08798E3404DD' +
21+
'EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245' +
22+
'E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' +
23+
'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381' +
24+
'FFFFFFFFFFFFFFFF',
25+
'hex',
26+
);
27+
const generator = Buffer.from([2]);
28+
const dh = crypto.createDiffieHellman(prime, generator);
29+
30+
assert.strictEqual(dh.getPrime('hex'), prime.toString('hex').toLowerCase());
31+
assert.strictEqual(
32+
dh.getGenerator('hex'),
33+
generator.toString('hex').toLowerCase(),
34+
);
35+
});
36+
37+
test(SUITE, 'should compute shared secret', () => {
38+
const alice = crypto.createDiffieHellman(512);
39+
const aliceKeys = alice.generateKeys();
40+
41+
const bob = crypto.createDiffieHellman(
42+
alice.getPrime(),
43+
alice.getGenerator(),
44+
);
45+
const bobKeys = bob.generateKeys();
46+
47+
const aliceSecret = alice.computeSecret(bobKeys);
48+
const bobSecret = bob.computeSecret(aliceKeys);
49+
50+
assert.strictEqual(aliceSecret.toString('hex'), bobSecret.toString('hex'));
51+
});
52+
53+
test(SUITE, 'should set keys', () => {
54+
const alice = crypto.createDiffieHellman(512);
55+
alice.generateKeys();
56+
57+
const dh2 = crypto.createDiffieHellman(
58+
alice.getPrime(),
59+
alice.getGenerator(),
60+
);
61+
dh2.setPublicKey(alice.getPublicKey());
62+
dh2.setPrivateKey(alice.getPrivateKey());
63+
64+
assert.strictEqual(dh2.getPublicKey('hex'), alice.getPublicKey('hex'));
65+
assert.strictEqual(dh2.getPrivateKey('hex'), alice.getPrivateKey('hex'));
66+
});
67+
68+
test(SUITE, 'should create DiffieHellman from standard group', () => {
69+
const dh = crypto.getDiffieHellman('modp14');
70+
assert.isOk(dh);
71+
const prime = dh.getPrime();
72+
assert.isTrue(Buffer.isBuffer(prime));
73+
// modp14 is 2048-bit group
74+
assert.strictEqual(prime.length, 256);
75+
assert.strictEqual(dh.getGenerator('hex'), '02');
76+
});
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { test } from '../util';
2+
import { Buffer } from '@craftzdog/react-native-buffer';
3+
import crypto from 'react-native-quick-crypto';
4+
import { assert } from 'chai';
5+
6+
const SUITE = 'ecdh';
7+
8+
test(SUITE, 'should create ECDH instance with P-256', () => {
9+
const ecdh = crypto.createECDH('prime256v1');
10+
assert.isOk(ecdh);
11+
});
12+
13+
test(SUITE, 'should generate keys for P-256', () => {
14+
const ecdh = crypto.createECDH('prime256v1');
15+
const keys = ecdh.generateKeys();
16+
assert.isOk(keys);
17+
assert.isTrue(Buffer.isBuffer(keys), 'keys should be a Buffer');
18+
assert.isOk(ecdh.getPublicKey());
19+
assert.isOk(ecdh.getPrivateKey());
20+
});
21+
22+
test(SUITE, 'should switch between curves', () => {
23+
const ecdh1 = crypto.createECDH('prime256v1');
24+
ecdh1.generateKeys();
25+
26+
const ecdh2 = crypto.createECDH('secp384r1');
27+
ecdh2.generateKeys();
28+
29+
assert.notEqual(
30+
ecdh1.getPrivateKey().toString('hex'),
31+
ecdh2.getPrivateKey().toString('hex'),
32+
);
33+
});
34+
35+
test(SUITE, 'should compute shared secret', () => {
36+
const alice = crypto.createECDH('prime256v1');
37+
alice.generateKeys();
38+
39+
const bob = crypto.createECDH('prime256v1');
40+
bob.generateKeys();
41+
42+
const aliceSecret = alice.computeSecret(bob.getPublicKey());
43+
const bobSecret = bob.computeSecret(alice.getPublicKey());
44+
45+
assert.strictEqual(aliceSecret.toString('hex'), bobSecret.toString('hex'));
46+
});
47+
48+
test(SUITE, 'should set private key', () => {
49+
const alice = crypto.createECDH('prime256v1');
50+
alice.generateKeys();
51+
const priv = alice.getPrivateKey();
52+
53+
const alice2 = crypto.createECDH('prime256v1');
54+
alice2.setPrivateKey(priv);
55+
56+
// Public key should be derived/set (depending on impl, but usually settable)
57+
// In our implementation setPrivateKey derives public key?
58+
// Let's check consistency.
59+
// If setPrivateKey derives public key, we can check it matches
60+
const pub1 = alice.getPublicKey();
61+
const pub2 = alice2.getPublicKey();
62+
63+
assert.strictEqual(pub1.toString('hex'), pub2.toString('hex'));
64+
});
65+
66+
test(SUITE, 'should work with string input', () => {
67+
const alice = crypto.createECDH('prime256v1');
68+
alice.generateKeys();
69+
const bob = crypto.createECDH('prime256v1');
70+
bob.generateKeys();
71+
72+
const bobPubHex = bob.getPublicKey().toString('hex');
73+
const secret = alice.computeSecret(bobPubHex, 'hex');
74+
assert.isOk(secret);
75+
});

packages/react-native-quick-crypto/QuickCrypto.podspec

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ Pod::Spec.new do |s|
130130
cpp_headers = [
131131
"\"$(PODS_TARGET_SRCROOT)/cpp/utils\"",
132132
"\"$(PODS_TARGET_SRCROOT)/cpp/hkdf\"",
133+
"\"$(PODS_TARGET_SRCROOT)/cpp/dh\"",
134+
"\"$(PODS_TARGET_SRCROOT)/cpp/ecdh\"",
135+
"\"$(PODS_TARGET_SRCROOT)/nitrogen/generated/shared/c++\"",
133136
"\"$(PODS_TARGET_SRCROOT)/deps/ncrypto/include\"",
134137
"\"$(PODS_TARGET_SRCROOT)/deps/blake3/c\"",
135138
"\"$(PODS_TARGET_SRCROOT)/deps/fastpbkdf2\""

packages/react-native-quick-crypto/android/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ add_library(
3434
../cpp/cipher/XSalsa20Cipher.cpp
3535
../cpp/cipher/ChaCha20Cipher.cpp
3636
../cpp/cipher/ChaCha20Poly1305Cipher.cpp
37+
../cpp/dh/HybridDiffieHellman.cpp
3738
../cpp/ec/HybridEcKeyPair.cpp
39+
../cpp/ecdh/HybridECDH.cpp
3840
../cpp/ed25519/HybridEdKeyPair.cpp
3941
../cpp/hash/HybridHash.cpp
4042
../cpp/hmac/HybridHmac.cpp
@@ -62,7 +64,9 @@ include_directories(
6264
"src/main/cpp"
6365
"../cpp/blake3"
6466
"../cpp/cipher"
67+
"../cpp/dh"
6568
"../cpp/ec"
69+
"../cpp/ecdh"
6670
"../cpp/ed25519"
6771
"../cpp/hash"
6872
"../cpp/hkdf"

0 commit comments

Comments
 (0)