Skip to content

Commit b50928b

Browse files
authored
feat: Add DH and ECDH support (#862)
1 parent 519e804 commit b50928b

34 files changed

Lines changed: 1994 additions & 309 deletions

.docs/implementation-coverage.md

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -34,25 +34,25 @@ These algorithms provide quantum-resistant cryptography.
3434
*`decipher.setAuthTag(buffer[, encoding])`
3535
*`decipher.setAutoPadding([autoPadding])`
3636
*`decipher.update(data[, inputEncoding][, outputEncoding])`
37-
* Class: `DiffieHellman`
38-
* `diffieHellman.computeSecret(otherPublicKey[, inputEncoding][, outputEncoding])`
39-
* `diffieHellman.generateKeys([encoding])`
40-
* `diffieHellman.getGenerator([encoding])`
41-
* `diffieHellman.getPrime([encoding])`
42-
* `diffieHellman.getPrivateKey([encoding])`
43-
* `diffieHellman.getPublicKey([encoding])`
44-
* `diffieHellman.setPrivateKey(privateKey[, encoding])`
45-
* `diffieHellman.setPublicKey(publicKey[, encoding])`
37+
* Class: `DiffieHellman`
38+
* `diffieHellman.computeSecret(otherPublicKey[, inputEncoding][, outputEncoding])`
39+
* `diffieHellman.generateKeys([encoding])`
40+
* `diffieHellman.getGenerator([encoding])`
41+
* `diffieHellman.getPrime([encoding])`
42+
* `diffieHellman.getPrivateKey([encoding])`
43+
* `diffieHellman.getPublicKey([encoding])`
44+
* `diffieHellman.setPrivateKey(privateKey[, encoding])`
45+
* `diffieHellman.setPublicKey(publicKey[, encoding])`
4646
*`diffieHellman.verifyError`
47-
* Class: `DiffieHellmanGroup`
48-
* Class: `ECDH`
47+
* Class: `DiffieHellmanGroup`
48+
* Class: `ECDH`
4949
* ❌ static `ECDH.convertKey(key, curve[, inputEncoding[, outputEncoding[, format]]])`
50-
* `ecdh.computeSecret(otherPublicKey[, inputEncoding][, outputEncoding])`
51-
* `ecdh.generateKeys([encoding[, format]])`
52-
* `ecdh.getPrivateKey([encoding])`
53-
* `ecdh.getPublicKey([encoding][, format])`
54-
* `ecdh.setPrivateKey(privateKey[, encoding])`
55-
* `ecdh.setPublicKey(publicKey[, encoding])`
50+
* `ecdh.computeSecret(otherPublicKey[, inputEncoding][, outputEncoding])`
51+
* `ecdh.generateKeys([encoding[, format]])`
52+
* `ecdh.getPrivateKey([encoding])`
53+
* `ecdh.getPublicKey([encoding][, format])`
54+
* `ecdh.setPrivateKey(privateKey[, encoding])`
55+
* `ecdh.setPublicKey(publicKey[, encoding])`
5656
* ✅ Class: `Hash`
5757
*`hash.copy([options])`
5858
*`hash.digest([encoding])`
@@ -109,10 +109,10 @@ These algorithms provide quantum-resistant cryptography.
109109
*`crypto.constants`
110110
*`crypto.createCipheriv(algorithm, key, iv[, options])`
111111
*`crypto.createDecipheriv(algorithm, key, iv[, options])`
112-
* `crypto.createDiffieHellman(prime[, primeEncoding][, generator][, generatorEncoding])`
113-
* `crypto.createDiffieHellman(primeLength[, generator])`
114-
* `crypto.createDiffieHellmanGroup(name)`
115-
* `crypto.createECDH(curveName)`
112+
* `crypto.createDiffieHellman(prime[, primeEncoding][, generator][, generatorEncoding])`
113+
* `crypto.createDiffieHellman(primeLength[, generator])`
114+
* `crypto.getDiffieHellman(groupName)`
115+
* `crypto.createECDH(curveName)`
116116
*`crypto.createHash(algorithm[, options])`
117117
*`crypto.createHmac(algorithm, key[, options])`
118118
*`crypto.createPrivateKey(key)`
@@ -133,7 +133,6 @@ These algorithms provide quantum-resistant cryptography.
133133
*`crypto.getCipherInfo(nameOrNid[, options])`
134134
*`crypto.getCiphers()`
135135
*`crypto.getCurves()`
136-
*`crypto.getDiffieHellman(groupName)`
137136
*`crypto.getFips()`
138137
*`crypto.getHashes()`
139138
*`crypto.getRandomValues(typedArray)`
@@ -165,8 +164,8 @@ These algorithms provide quantum-resistant cryptography.
165164
## `crypto.diffieHellman`
166165
| type | Status |
167166
| --------- | :----: |
168-
| `dh` | |
169-
| `ec` | |
167+
| `dh` | |
168+
| `ec` | |
170169
| `x448` ||
171170
| `x25519` ||
172171

bun.lock

Lines changed: 118 additions & 85 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

example/ios/Podfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2773,7 +2773,7 @@ SPEC CHECKSUMS:
27732773
glog: 5683914934d5b6e4240e497e0f4a3b42d1854183
27742774
hermes-engine: 4f8246b1f6d79f625e0d99472d1f3a71da4d28ca
27752775
NitroModules: 1715fe0e22defd9e2cdd48fb5e0dbfd01af54bec
2776-
QuickCrypto: a90c6474ef5d724d14cc452d8d23146621e0fb99
2776+
QuickCrypto: d2f42ab176e55442e803586b4621d5c21e0f0f19
27772777
RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669
27782778
RCTDeprecation: c4b9e2fd0ab200e3af72b013ed6113187c607077
27792779
RCTRequired: e97dd5dafc1db8094e63bc5031e0371f092ae92a

example/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"react-native-fast-encoder": "0.3.1",
4141
"react-native-nitro-modules": "0.29.1",
4242
"react-native-quick-base64": "2.2.2",
43-
"react-native-quick-crypto": "1.0.7",
43+
"react-native-quick-crypto": "workspace:*",
4444
"react-native-safe-area-context": "5.6.2",
4545
"react-native-screens": "4.18.0",
4646
"react-native-vector-icons": "10.3.0",

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: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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+
// Generate private key and derive public key for fair comparison
21+
const priv = p256.utils.randomPrivateKey();
22+
p256.getPublicKey(priv);
23+
});
24+
25+
bench.warmupTime = 100;
26+
return bench;
27+
};
28+
29+
const ecdh_p256_computeSecret: BenchFn = () => {
30+
const bench = new Bench({
31+
name: 'ECDH P-256 Compute',
32+
time: TIME_MS,
33+
});
34+
35+
const alice = rnqc.createECDH('prime256v1');
36+
alice.generateKeys();
37+
const bob = rnqc.createECDH('prime256v1');
38+
bob.generateKeys();
39+
const bobPub = bob.getPublicKey();
40+
41+
const nobleAlicePriv = p256.utils.randomPrivateKey();
42+
const nobleBobPriv = p256.utils.randomPrivateKey();
43+
const nobleBobPub = p256.getPublicKey(nobleBobPriv);
44+
45+
bench
46+
.add('rnqc', () => {
47+
alice.computeSecret(bobPub);
48+
})
49+
.add('@noble/curves', () => {
50+
p256.getSharedSecret(nobleAlicePriv, nobleBobPub);
51+
});
52+
53+
bench.warmupTime = 100;
54+
return bench;
55+
};
56+
57+
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
@@ -17,6 +17,8 @@ import '../tests/keys/public_cipher';
1717
import '../tests/keys/sign_verify_streaming';
1818
import '../tests/keys/sign_verify_oneshot';
1919
import '../tests/pbkdf2/pbkdf2_tests';
20+
import '../tests/ecdh/ecdh_tests';
21+
import '../tests/dh/dh_tests';
2022
import '../tests/random/random_tests';
2123
import '../tests/scrypt/scrypt_tests';
2224
import '../tests/subtle/deriveBits';

example/src/tests/dh/dh_tests.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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+
// RFC 3526 MODP Group 14 prime (2048-bit) for testing with explicit prime
9+
const MODP14_PRIME =
10+
'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1' +
11+
'29024E088A67CC74020BBEA63B139B22514A08798E3404DD' +
12+
'EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245' +
13+
'E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' +
14+
'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D' +
15+
'C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F' +
16+
'83655D23DCA3AD961C62F356208552BB9ED529077096966D' +
17+
'670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' +
18+
'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9' +
19+
'DE2BCBF6955817183995497CEA956AE515D2261898FA0510' +
20+
'15728E5A8AACAA68FFFFFFFFFFFFFFFF';
21+
22+
test(
23+
SUITE,
24+
'should create DiffieHellman with prime and numeric generator',
25+
() => {
26+
const prime = Buffer.from(MODP14_PRIME, 'hex');
27+
const dh = crypto.createDiffieHellman(prime, 2);
28+
29+
assert.strictEqual(dh.getPrime('hex'), prime.toString('hex').toLowerCase());
30+
assert.strictEqual(dh.getGenerator('hex'), '02');
31+
},
32+
);
33+
34+
test(
35+
SUITE,
36+
'should create DiffieHellman with prime and Buffer generator',
37+
() => {
38+
const prime = Buffer.from(MODP14_PRIME, 'hex');
39+
const generator = Buffer.from([2]);
40+
const dh = crypto.createDiffieHellman(prime, generator);
41+
42+
assert.strictEqual(dh.getPrime('hex'), prime.toString('hex').toLowerCase());
43+
assert.strictEqual(
44+
dh.getGenerator('hex'),
45+
generator.toString('hex').toLowerCase(),
46+
);
47+
},
48+
);
49+
50+
test(SUITE, 'should compute shared secret', () => {
51+
const alice = crypto.getDiffieHellman('modp14');
52+
alice.generateKeys();
53+
54+
const bob = crypto.getDiffieHellman('modp14');
55+
bob.generateKeys();
56+
57+
const aliceSecret = alice.computeSecret(bob.getPublicKey());
58+
const bobSecret = bob.computeSecret(alice.getPublicKey());
59+
60+
assert.strictEqual(aliceSecret.toString('hex'), bobSecret.toString('hex'));
61+
});
62+
63+
test(SUITE, 'should set keys', () => {
64+
const alice = crypto.getDiffieHellman('modp14');
65+
alice.generateKeys();
66+
67+
const bob = crypto.createDiffieHellman(
68+
alice.getPrime(),
69+
alice.getGenerator(),
70+
);
71+
bob.setPublicKey(alice.getPublicKey());
72+
bob.setPrivateKey(alice.getPrivateKey());
73+
74+
assert.strictEqual(bob.getPublicKey('hex'), alice.getPublicKey('hex'));
75+
assert.strictEqual(bob.getPrivateKey('hex'), alice.getPrivateKey('hex'));
76+
});
77+
78+
test(SUITE, 'should create DiffieHellman from standard group', () => {
79+
const dh = crypto.getDiffieHellman('modp14');
80+
assert.isOk(dh);
81+
const prime = dh.getPrime();
82+
assert.isTrue(Buffer.isBuffer(prime));
83+
assert.strictEqual(prime.length, 256);
84+
assert.strictEqual(dh.getGenerator('hex'), '02');
85+
});
86+
87+
test(SUITE, 'should reject prime length below 2048 bits', () => {
88+
assert.throws(() => {
89+
crypto.createDiffieHellman(512);
90+
}, /prime length must be at least 2048 bits/);
91+
});

0 commit comments

Comments
 (0)