Skip to content

Commit 820739a

Browse files
authored
feat: hash (#593)
1 parent e782176 commit 820739a

22 files changed

Lines changed: 789 additions & 28 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
# VSCode
99
jsconfig.json
10+
.vscode/
1011

1112
# Xcode
1213
#

.vscode/launch.json

Lines changed: 0 additions & 20 deletions
This file was deleted.

bun.lockb

0 Bytes
Binary file not shown.

docs/implementation-coverage.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,10 @@ This document attempts to describe the implementation status of Crypto APIs/Inte
4343
*`ecdh.getPublicKey([encoding][, format])`
4444
*`ecdh.setPrivateKey(privateKey[, encoding])`
4545
*`ecdh.setPublicKey(publicKey[, encoding])`
46-
* Class: `Hash`
47-
* `hash.copy([options])`
48-
* `hash.digest([encoding])`
49-
* `hash.update(data[, inputEncoding])`
46+
* Class: `Hash`
47+
* `hash.copy([options])`
48+
* `hash.digest([encoding])`
49+
* `hash.update(data[, inputEncoding])`
5050
* ❌ Class: `Hmac`
5151
*`hmac.digest([encoding])`
5252
*`hmac.update(data[, inputEncoding])`
@@ -101,7 +101,7 @@ This document attempts to describe the implementation status of Crypto APIs/Inte
101101
*`crypto.createDiffieHellman(primeLength[, generator])`
102102
*`crypto.createDiffieHellmanGroup(name)`
103103
*`crypto.createECDH(curveName)`
104-
* `crypto.createHash(algorithm[, options])`
104+
* `crypto.createHash(algorithm[, options])`
105105
*`crypto.createHmac(algorithm, key[, options])`
106106
*`crypto.createPrivateKey(key)`
107107
*`crypto.createPublicKey(key)`
@@ -121,7 +121,7 @@ This document attempts to describe the implementation status of Crypto APIs/Inte
121121
*`crypto.getCurves()`
122122
*`crypto.getDiffieHellman(groupName)`
123123
*`crypto.getFips()`
124-
* `crypto.getHashes()`
124+
* `crypto.getHashes()`
125125
*`crypto.getRandomValues(typedArray)`
126126
*`crypto.hkdf(digest, ikm, salt, info, keylen, callback)`
127127
*`crypto.hkdfSync(digest, ikm, salt, info, keylen)`

docs/test_suite_results.png

8 KB
Loading

example/ios/Podfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1940,7 +1940,7 @@ SPEC CHECKSUMS:
19401940
fmt: 10c6e61f4be25dc963c36bd73fc7b1705fe975be
19411941
glog: 08b301085f15bcbb6ff8632a8ebaf239aae04e6a
19421942
hermes-engine: 46f1ffbf0297f4298862068dd4c274d4ac17a1fd
1943-
NitroModules: 3a58d9bc70815a0d5de4476ed6a36eff05a6a0ae
1943+
NitroModules: c36d6f656038a56beb1b1bcab2d0252d71744013
19441944
OpenSSL-Universal: b60a3702c9fea8b3145549d421fdb018e53ab7b4
19451945
QuickCrypto: 11878b44cfc77fad2ea8f387a16e315841651305
19461946
RCT-Folly: 84578c8756030547307e4572ab1947de1685c599

example/src/hooks/useTestsList.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useState, useCallback } from 'react';
22
import type { TestSuites } from '../types/tests';
33
import { TestsContext } from '../tests/util';
44

5+
import '../tests/hash/hash_tests';
56
import '../tests/ed25519/ed25519_tests';
67
import '../tests/pbkdf2/pbkdf2_tests';
78
import '../tests/random/random_tests';
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
/**
2+
* Tests are based on Node.js tests
3+
* https://github.com/nodejs/node/blob/master/test/parallel/test-crypto-hash.js
4+
*/
5+
6+
import { Buffer } from '@craftzdog/react-native-buffer';
7+
import {
8+
createHash,
9+
getHashes,
10+
type Encoding,
11+
} from 'react-native-quick-crypto';
12+
import { expect } from 'chai';
13+
import { test } from '../util';
14+
15+
const SUITE = 'hash';
16+
17+
test(SUITE, 'createHash with valid algorithm', () => {
18+
expect(() => {
19+
createHash('sha256');
20+
}).to.not.throw();
21+
});
22+
23+
test(SUITE, 'createHash with invalid algorithm', () => {
24+
expect(() => {
25+
createHash('sha123');
26+
}).to.throw(/Unknown hash algorithm: sha123/);
27+
});
28+
29+
// test hashing
30+
const a0 = createHash('md5').update('Test123').digest('latin1');
31+
const a1 = createHash('sha1').update('Test123').digest('hex');
32+
const a2 = createHash('sha256').update('Test123').digest('base64');
33+
const a3 = createHash('sha512').update('Test123').digest(); // buffer
34+
const a4 = createHash('sha1').update('Test123').digest('buffer');
35+
36+
test(SUITE, 'non stream - digest with latin1 argument', () => {
37+
expect(a0).to.deep.equal(
38+
'h\u00ea\u00cb\u0097\u00d8o\fF!\u00fa+\u000e\u0017\u00ca\u00bd\u008c',
39+
);
40+
});
41+
test(SUITE, 'non stream - digest with hex argument', () => {
42+
expect(a1).to.deep.equal('8308651804facb7b9af8ffc53a33a22d6a1c8ac2');
43+
});
44+
test(SUITE, 'non stream - digest with base64 argument', () => {
45+
expect(a2).to.deep.equal('2bX1jws4GYKTlxhloUB09Z66PoJZW+y+hq5R8dnx9l4=');
46+
});
47+
test(SUITE, 'non stream - digest with buffer argument', () => {
48+
expect(a4).to.deep.equal(
49+
Buffer.from('8308651804facb7b9af8ffc53a33a22d6a1c8ac2', 'hex'),
50+
);
51+
});
52+
test(SUITE, 'non stream - digest without argument defaults to buffer', () => {
53+
expect(a3).to.deep.equal(
54+
Buffer.from(
55+
"\u00c1(4\u00f1\u0003\u001fd\u0097!O'\u00d4C/&Qz\u00d4" +
56+
'\u0094\u0015l\u00b8\u008dQ+\u00db\u001d\u00c4\u00b5}\u00b2' +
57+
'\u00d6\u0092\u00a3\u00df\u00a2i\u00a1\u009b\n\n*\u000f' +
58+
'\u00d7\u00d6\u00a2\u00a8\u0085\u00e3<\u0083\u009c\u0093' +
59+
"\u00c2\u0006\u00da0\u00a1\u00879(G\u00ed'",
60+
'latin1',
61+
),
62+
);
63+
});
64+
65+
test(SUITE, 'non stream - multiple updates to same hash', () => {
66+
const h1 = createHash('sha1').update('Test').update('123').digest('hex');
67+
expect(h1).to.deep.equal(a1);
68+
});
69+
70+
// stream interface
71+
let a5 = createHash('sha512');
72+
a5.end('Test123');
73+
a5 = a5.read();
74+
let a6 = createHash('sha512');
75+
a6.write('Te');
76+
a6.write('st');
77+
a6.write('123');
78+
a6.end();
79+
a6 = a6.read();
80+
let a7 = createHash('sha512');
81+
a7.end();
82+
a7 = a7.read();
83+
let a8 = createHash('sha512');
84+
a8.write('');
85+
a8.end();
86+
a8 = a8.read();
87+
88+
test(SUITE, 'stream - should produce the same output as non-stream', () => {
89+
expect(a5).to.deep.equal(a3);
90+
expect(a6).to.deep.equal(a3);
91+
});
92+
test(SUITE, 'stream - empty', () => {
93+
expect(a7).to.deep.equal(a8);
94+
expect(a7).not.to.deep.equal(undefined);
95+
expect(a8).not.to.deep.equal(undefined);
96+
});
97+
98+
test(SUITE, 'copy - should create identical hash state', () => {
99+
const hash1 = createHash('sha256').update('Test123');
100+
const hash2 = hash1.copy();
101+
expect(hash1.digest('hex')).to.deep.equal(hash2.digest('hex'));
102+
});
103+
104+
test(SUITE, 'copy - calculate a rolling hash', () => {
105+
const hash = createHash('sha256');
106+
hash.update('one');
107+
expect(hash.copy().digest('hex')).to.deep.equal(
108+
'7692c3ad3540bb803c020b3aee66cd8887123234ea0c6e7143c0add73ff431ed',
109+
);
110+
hash.update('two');
111+
expect(hash.copy().digest('hex')).to.deep.equal(
112+
'25b6746d5172ed6352966a013d93ac846e1110d5a25e8f183b5931f4688842a1',
113+
);
114+
hash.update('three');
115+
expect(hash.copy().digest('hex')).to.deep.equal(
116+
'4592092e1061c7ea85af2aed194621cc17a2762bae33a79bf8ce33fd0168b801',
117+
);
118+
});
119+
120+
test(SUITE, 'getHashes - should return array of supported algorithms', () => {
121+
const algorithms = getHashes();
122+
const expectedAlgorithms = [
123+
'BLAKE2B-512',
124+
'BLAKE2S-256',
125+
'KECCAK-224',
126+
'KECCAK-256',
127+
'KECCAK-384',
128+
'KECCAK-512',
129+
'KECCAK-KMAC-128',
130+
'KECCAK-KMAC-256',
131+
'MD5',
132+
'MD5-SHA1',
133+
'NULL',
134+
'RIPEMD-160',
135+
'SHA1',
136+
'SHA2-224',
137+
'SHA2-256',
138+
'SHA2-256/192',
139+
'SHA2-384',
140+
'SHA2-512',
141+
'SHA2-512/224',
142+
'SHA2-512/256',
143+
'SHA3-224',
144+
'SHA3-256',
145+
'SHA3-384',
146+
'SHA3-512',
147+
'SHAKE-128',
148+
'SHAKE-256',
149+
'SM3',
150+
];
151+
expect(algorithms).to.be.an('array');
152+
expect(algorithms.sort()).to.deep.equal(expectedAlgorithms.sort());
153+
});
154+
155+
// errors
156+
test(SUITE, 'digest - segfault', () => {
157+
const hash = createHash('sha256');
158+
expect(() => {
159+
hash.digest({
160+
toString: () => {
161+
throw new Error('segfault');
162+
},
163+
} as unknown as Encoding);
164+
}).to.throw(/Value is an object/);
165+
});
166+
test(SUITE, 'update - calling update without argument', () => {
167+
const hash = createHash('sha256');
168+
expect(() => {
169+
// @ts-expect-error calling update without argument
170+
hash.update();
171+
}).to.throw(/input could not be converted/);
172+
});
173+
test(SUITE, 'digest - calling update after digest', () => {
174+
const hash = createHash('sha256');
175+
hash.digest();
176+
expect(() => hash.update('test')).to.throw(/Failed to update/);
177+
});
178+
179+
// outputLength option
180+
test(SUITE, 'output length = 0', () => {
181+
const hash = createHash('SHAKE-256', { outputLength: 0 });
182+
expect(hash.digest('hex')).to.deep.equal('');
183+
});
184+
test(SUITE, 'output length = 5', () => {
185+
expect(
186+
createHash('shake128', { outputLength: 5 }).digest('hex'),
187+
).to.deep.equal('7f9c2ba4e8');
188+
});
189+
test(SUITE, 'output length with copy', () => {
190+
const hash = createHash('shake128', { outputLength: 5 });
191+
const copy = hash.copy({ outputLength: 0 });
192+
expect(copy.digest('hex')).to.deep.equal('');
193+
expect(hash.digest('hex')).to.deep.equal('7f9c2ba4e8');
194+
});
195+
test(SUITE, 'large output length', () => {
196+
const largeHash = createHash('shake128', { outputLength: 128 }).digest('hex');
197+
expect(largeHash.length).to.equal(2 * 128);
198+
expect(largeHash.slice(0, 32)).to.deep.equal(
199+
'7f9c2ba4e88f827d616045507605853e',
200+
);
201+
expect(largeHash.slice(2 * 128 - 32, 2 * 128)).to.deep.equal(
202+
'df9a04302e10c8bc1cbf1a0b3a5120ea',
203+
);
204+
});
205+
test(SUITE, 'super long hash', () => {
206+
const superLongHash = createHash('shake256', {
207+
outputLength: 1024 * 1024,
208+
})
209+
.update('The message is shorter than the hash!')
210+
.digest('hex');
211+
expect(superLongHash.length).to.equal(2 * 1024 * 1024);
212+
expect(superLongHash.slice(0, 32)).to.deep.equal(
213+
'a2a28dbc49cfd6e5d6ceea3d03e77748',
214+
);
215+
expect(
216+
superLongHash.slice(2 * 1024 * 1024 - 32, 2 * 1024 * 1024),
217+
).to.deep.equal('193414035ddba77bf7bba97981e656ec');
218+
});
219+
test(SUITE, 'unreasonable output length', () => {
220+
expect(() => {
221+
createHash('shake128', { outputLength: 1024 * 1024 * 1024 }).digest('hex');
222+
}).to.throw(
223+
/Output length 1073741824 exceeds maximum allowed size of 16777216/,
224+
);
225+
});

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"@release-it/conventional-changelog": "^9.0.3",
2222
"release-it": "^17.10.0"
2323
},
24-
"packageManager": "bun@1.1.26",
24+
"packageManager": "bun@1.2.0",
2525
"release-it": {
2626
"npm": {
2727
"publish": false

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ set(CMAKE_CXX_STANDARD 20)
99
add_library(
1010
${PACKAGE_NAME} SHARED
1111
src/main/cpp/cpp-adapter.cpp
12+
../cpp/hash/HybridHash.cpp
1213
../cpp/ed25519/HybridEdKeyPair.cpp
1314
../cpp/pbkdf2/HybridPbkdf2.cpp
1415
../cpp/random/HybridRandom.cpp
@@ -21,6 +22,7 @@ include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/QuickCrypto+autolinkin
2122
# local includes
2223
include_directories(
2324
"src/main/cpp"
25+
"../cpp/hash"
2426
"../cpp/ed25519"
2527
"../cpp/pbkdf2"
2628
"../cpp/random"

0 commit comments

Comments
 (0)