Skip to content

Commit 8254643

Browse files
authored
feat: add TurboSHAKE and KangarooTwelve digests (#1032)
1 parent f17ec04 commit 8254643

19 files changed

Lines changed: 1175 additions & 3 deletions

example/ios/Podfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2815,7 +2815,7 @@ SPEC CHECKSUMS:
28152815
NitroMmkv: afbc5b2fbf963be567c6c545aa1efcf6a9cec68e
28162816
NitroModules: 11bba9d065af151eae51e38a6425e04c3b223ff3
28172817
OpenSSL-Universal: 9110d21982bb7e8b22a962b6db56a8aa805afde7
2818-
QuickCrypto: 30f0d4eceadf1dbb9df5c17605e9f4279a0041ea
2818+
QuickCrypto: 71ccdcd47ae5f72d4817a4914966b8c34b3eb48d
28192819
RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669
28202820
RCTDeprecation: c4b9e2fd0ab200e3af72b013ed6113187c607077
28212821
RCTRequired: e97dd5dafc1db8094e63bc5031e0371f092ae92a

example/src/hooks/useTestsList.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import '../tests/subtle/argon2_deriveBits';
3636
import '../tests/subtle/deriveBits';
3737
import '../tests/subtle/derive_key';
3838
import '../tests/subtle/digest';
39+
import '../tests/subtle/digest_turboshake';
3940
import '../tests/subtle/encap_decap';
4041
import '../tests/subtle/encrypt_decrypt';
4142
import '../tests/subtle/generateKey';
Lines changed: 363 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
1+
import { expect } from 'chai';
2+
import { Buffer, subtle } from 'react-native-quick-crypto';
3+
import { test } from '../util';
4+
5+
// RFC 9861 §5 test vectors for TurboSHAKE128/256 and KangarooTwelve KT128/256.
6+
// Vectors mirror Node's test/parallel/test-webcrypto-digest-turboshake-rfc.js.
7+
8+
const SUITE = 'subtle.digest.turboshake';
9+
10+
// ptn(n): RFC 9861 helper — n bytes following the pattern 00, 01, ..., F9, FA.
11+
function ptn(n: number): Uint8Array {
12+
const out = new Uint8Array(n);
13+
for (let i = 0; i < n; i++) out[i] = i % 251;
14+
return out;
15+
}
16+
17+
const fromHex = (hex: string): Uint8Array => {
18+
const bytes = new Uint8Array(hex.length / 2);
19+
for (let i = 0; i < bytes.length; i++) {
20+
bytes[i] = parseInt(hex.substring(i * 2, i * 2 + 2), 16);
21+
}
22+
return bytes;
23+
};
24+
25+
type ShakeVec = [Uint8Array, number, string, number?]; // [input, outBytes, hex, domainSeparation?]
26+
type KtVec = [Uint8Array, number, string, Uint8Array?]; // [input, outBytes, hex, customization?]
27+
28+
const turboSHAKE128Vectors: ShakeVec[] = [
29+
[
30+
new Uint8Array(0),
31+
32,
32+
'1e415f1c5983aff2169217277d17bb53' + '8cd945a397ddec541f1ce41af2c1b74c',
33+
],
34+
[
35+
new Uint8Array(0),
36+
64,
37+
'1e415f1c5983aff2169217277d17bb53' +
38+
'8cd945a397ddec541f1ce41af2c1b74c' +
39+
'3e8ccae2a4dae56c84a04c2385c03c15' +
40+
'e8193bdf58737363321691c05462c8df',
41+
],
42+
[
43+
ptn(1),
44+
32,
45+
'55cedd6f60af7bb29a4042ae832ef3f5' + '8db7299f893ebb9247247d856958daa9',
46+
],
47+
[
48+
ptn(17),
49+
32,
50+
'9c97d036a3bac819db70ede0ca554ec6' + 'e4c2a1a4ffbfd9ec269ca6a111161233',
51+
],
52+
[
53+
ptn(17 ** 2),
54+
32,
55+
'96c77c279e0126f7fc07c9b07f5cdae1' + 'e0be60bdbe10620040e75d7223a624d2',
56+
],
57+
[
58+
ptn(17 ** 3),
59+
32,
60+
'd4976eb56bcf118520582b709f73e1d6' + '853e001fdaf80e1b13e0d0599d5fb372',
61+
],
62+
[
63+
fromHex('ffffff'),
64+
32,
65+
'bf323f940494e88ee1c540fe660be8a0' + 'c93f43d15ec006998462fa994eed5dab',
66+
0x01,
67+
],
68+
[
69+
fromHex('ff'),
70+
32,
71+
'8ec9c66465ed0d4a6c35d13506718d68' + '7a25cb05c74cca1e42501abd83874a67',
72+
0x06,
73+
],
74+
[
75+
fromHex('ffffff'),
76+
32,
77+
'b658576001cad9b1e5f399a9f77723bb' + 'a05458042d68206f7252682dba3663ed',
78+
0x07,
79+
],
80+
[
81+
fromHex('ffffffffffffff'),
82+
32,
83+
'8deeaa1aec47ccee569f659c21dfa8e1' + '12db3cee37b18178b2acd805b799cc37',
84+
0x0b,
85+
],
86+
[
87+
fromHex('ff'),
88+
32,
89+
'553122e2135e363c3292bed2c6421fa2' + '32bab03daa07c7d6636603286506325b',
90+
0x30,
91+
],
92+
[
93+
fromHex('ffffff'),
94+
32,
95+
'16274cc656d44cefd422395d0f9053bd' + 'a6d28e122aba15c765e5ad0e6eaf26f9',
96+
0x7f,
97+
],
98+
];
99+
100+
const turboSHAKE256Vectors: ShakeVec[] = [
101+
[
102+
new Uint8Array(0),
103+
64,
104+
'367a329dafea871c7802ec67f905ae13' +
105+
'c57695dc2c6663c61035f59a18f8e7db' +
106+
'11edc0e12e91ea60eb6b32df06dd7f00' +
107+
'2fbafabb6e13ec1cc20d995547600db0',
108+
],
109+
[
110+
ptn(1),
111+
64,
112+
'3e1712f928f8eaf1054632b2aa0a246e' +
113+
'd8b0c378728f60bc970410155c28820e' +
114+
'90cc90d8a3006aa2372c5c5ea176b068' +
115+
'2bf22bae7467ac94f74d43d39b0482e2',
116+
],
117+
[
118+
ptn(17 ** 2),
119+
64,
120+
'66b810db8e90780424c0847372fdc957' +
121+
'10882fde31c6df75beb9d4cd9305cfca' +
122+
'e35e7b83e8b7e6eb4b78605880116316' +
123+
'fe2c078a09b94ad7b8213c0a738b65c0',
124+
],
125+
[
126+
fromHex('ffffff'),
127+
64,
128+
'd21c6fbbf587fa2282f29aea620175fb' +
129+
'0257413af78a0b1b2a87419ce031d933' +
130+
'ae7a4d383327a8a17641a34f8a1d1003' +
131+
'ad7da6b72dba84bb62fef28f62f12424',
132+
0x01,
133+
],
134+
[
135+
fromHex('ffffffffffffff'),
136+
64,
137+
'bb36764951ec97e9d85f7ee9a67a7718' +
138+
'fc005cf42556be79ce12c0bde50e5736' +
139+
'd6632b0d0dfb202d1bbb8ffe3dd74cb0' +
140+
'0834fa756cb03471bab13a1e2c16b3c0',
141+
0x0b,
142+
],
143+
];
144+
145+
const kt128Vectors: KtVec[] = [
146+
[
147+
new Uint8Array(0),
148+
32,
149+
'1ac2d450fc3b4205d19da7bfca1b3751' + '3c0803577ac7167f06fe2ce1f0ef39e5',
150+
],
151+
[
152+
new Uint8Array(0),
153+
64,
154+
'1ac2d450fc3b4205d19da7bfca1b3751' +
155+
'3c0803577ac7167f06fe2ce1f0ef39e5' +
156+
'4269c056b8c82e48276038b6d292966c' +
157+
'c07a3d4645272e31ff38508139eb0a71',
158+
],
159+
[
160+
ptn(1),
161+
32,
162+
'2bda92450e8b147f8a7cb629e784a058' + 'efca7cf7d8218e02d345dfaa65244a1f',
163+
],
164+
[
165+
ptn(17),
166+
32,
167+
'6bf75fa2239198db4772e36478f8e19b' + '0f371205f6a9a93a273f51df37122888',
168+
],
169+
[
170+
ptn(17 ** 2),
171+
32,
172+
'0c315ebcdedbf61426de7dcf8fb725d1' + 'e74675d7f5327a5067f367b108ecb67c',
173+
],
174+
[
175+
new Uint8Array(0),
176+
32,
177+
'fab658db63e94a246188bf7af69a1330' + '45f46ee984c56e3c3328caaf1aa1a583',
178+
ptn(1),
179+
],
180+
[
181+
fromHex('ff'),
182+
32,
183+
'd848c5068ced736f4462159b9867fd4c' + '20b808acc3d5bc48e0b06ba0a3762ec4',
184+
ptn(41),
185+
],
186+
// tree-hashing path: |S| > 8192, exercises the multi-chunk branch.
187+
[
188+
ptn(8192),
189+
32,
190+
'48f256f6772f9edfb6a8b661ec92dc93' + 'b95ebd05a08a17b39ae3490870c926c3',
191+
],
192+
[
193+
ptn(8192),
194+
32,
195+
'6a7c1b6a5cd0d8c9ca943a4a216cc646' + '04559a2ea45f78570a15253d67ba00ae',
196+
ptn(8190),
197+
],
198+
];
199+
200+
const kt256Vectors: KtVec[] = [
201+
[
202+
new Uint8Array(0),
203+
64,
204+
'b23d2e9cea9f4904e02bec06817fc10c' +
205+
'e38ce8e93ef4c89e6537076af8646404' +
206+
'e3e8b68107b8833a5d30490aa3348235' +
207+
'3fd4adc7148ecb782855003aaebde4a9',
208+
],
209+
[
210+
ptn(1),
211+
64,
212+
'0d005a194085360217128cf17f91e1f7' +
213+
'1314efa5564539d444912e3437efa17f' +
214+
'82db6f6ffe76e781eaa068bce01f2bbf' +
215+
'81eacb983d7230f2fb02834a21b1ddd0',
216+
],
217+
[
218+
ptn(17 ** 2),
219+
64,
220+
'de8ccbc63e0f133ebb4416814d4c66f6' +
221+
'91bbf8b6a61ec0a7700f836b086cb029' +
222+
'd54f12ac7159472c72db118c35b4e6aa' +
223+
'213c6562caaa9dcc518959e69b10f3ba',
224+
],
225+
[
226+
new Uint8Array(0),
227+
64,
228+
'9280f5cc39b54a5a594ec63de0bb9937' +
229+
'1e4609d44bf845c2f5b8c316d72b1598' +
230+
'11f748f23e3fabbe5c3226ec96c62186' +
231+
'df2d33e9df74c5069ceecbb4dd10eff6',
232+
ptn(1),
233+
],
234+
[
235+
ptn(8192),
236+
64,
237+
'c6ee8e2ad3200c018ac87aaa031cdac2' +
238+
'2121b412d07dc6e0dccbb53423747e9a' +
239+
'1c18834d99df596cf0cf4b8dfafb7bf0' +
240+
'2d139d0c9035725adc1a01b7230a41fa',
241+
],
242+
];
243+
244+
const toHex = (buf: ArrayBuffer): string => Buffer.from(buf).toString('hex');
245+
246+
turboSHAKE128Vectors.forEach(([input, outBytes, expected, ds], i) => {
247+
test(SUITE, `TurboSHAKE128 RFC 9861 vector ${i}`, async () => {
248+
const algorithm: {
249+
name: 'TurboSHAKE128';
250+
outputLength: number;
251+
domainSeparation?: number;
252+
} = { name: 'TurboSHAKE128', outputLength: outBytes * 8 };
253+
if (ds !== undefined) algorithm.domainSeparation = ds;
254+
const result = await subtle.digest(algorithm, input);
255+
expect(toHex(result)).to.equal(expected);
256+
});
257+
});
258+
259+
turboSHAKE256Vectors.forEach(([input, outBytes, expected, ds], i) => {
260+
test(SUITE, `TurboSHAKE256 RFC 9861 vector ${i}`, async () => {
261+
const algorithm: {
262+
name: 'TurboSHAKE256';
263+
outputLength: number;
264+
domainSeparation?: number;
265+
} = { name: 'TurboSHAKE256', outputLength: outBytes * 8 };
266+
if (ds !== undefined) algorithm.domainSeparation = ds;
267+
const result = await subtle.digest(algorithm, input);
268+
expect(toHex(result)).to.equal(expected);
269+
});
270+
});
271+
272+
kt128Vectors.forEach(([input, outBytes, expected, customization], i) => {
273+
test(SUITE, `KT128 RFC 9861 vector ${i}`, async () => {
274+
const algorithm: {
275+
name: 'KT128';
276+
outputLength: number;
277+
customization?: Uint8Array;
278+
} = { name: 'KT128', outputLength: outBytes * 8 };
279+
if (customization !== undefined) algorithm.customization = customization;
280+
const result = await subtle.digest(algorithm, input);
281+
expect(toHex(result)).to.equal(expected);
282+
});
283+
});
284+
285+
kt256Vectors.forEach(([input, outBytes, expected, customization], i) => {
286+
test(SUITE, `KT256 RFC 9861 vector ${i}`, async () => {
287+
const algorithm: {
288+
name: 'KT256';
289+
outputLength: number;
290+
customization?: Uint8Array;
291+
} = { name: 'KT256', outputLength: outBytes * 8 };
292+
if (customization !== undefined) algorithm.customization = customization;
293+
const result = await subtle.digest(algorithm, input);
294+
expect(toHex(result)).to.equal(expected);
295+
});
296+
});
297+
298+
// Long-output vectors that exercise the squeeze loop across multiple rate-blocks.
299+
test(SUITE, 'TurboSHAKE128 long squeeze (10032 bytes, last 32)', async () => {
300+
const result = await subtle.digest(
301+
{ name: 'TurboSHAKE128', outputLength: 10032 * 8 },
302+
new Uint8Array(0),
303+
);
304+
expect(Buffer.from(result).subarray(-32).toString('hex')).to.equal(
305+
'a3b9b0385900ce761f22aed548e754da' + '10a5242d62e8c658e3f3a923a7555607',
306+
);
307+
});
308+
309+
test(SUITE, 'TurboSHAKE256 long squeeze (10032 bytes, last 32)', async () => {
310+
const result = await subtle.digest(
311+
{ name: 'TurboSHAKE256', outputLength: 10032 * 8 },
312+
new Uint8Array(0),
313+
);
314+
expect(Buffer.from(result).subarray(-32).toString('hex')).to.equal(
315+
'abefa11630c661269249742685ec082f' + '207265dccf2f43534e9c61ba0c9d1d75',
316+
);
317+
});
318+
319+
// Validation: WICG WebCrypto Modern Algos requires outputLength multiple of 8.
320+
test(SUITE, 'TurboSHAKE rejects non-byte-aligned outputLength', async () => {
321+
let threw = false;
322+
try {
323+
await subtle.digest(
324+
{ name: 'TurboSHAKE128', outputLength: 17 },
325+
new Uint8Array(0),
326+
);
327+
} catch (err) {
328+
threw = true;
329+
expect((err as Error).name).to.equal('OperationError');
330+
}
331+
expect(threw).to.equal(true);
332+
});
333+
334+
test(
335+
SUITE,
336+
'TurboSHAKE rejects domainSeparation outside 0x01..0x7F',
337+
async () => {
338+
let threw = false;
339+
try {
340+
await subtle.digest(
341+
{ name: 'TurboSHAKE128', outputLength: 256, domainSeparation: 0x80 },
342+
new Uint8Array(0),
343+
);
344+
} catch (err) {
345+
threw = true;
346+
expect((err as Error).name).to.equal('OperationError');
347+
}
348+
expect(threw).to.equal(true);
349+
},
350+
);
351+
352+
test(SUITE, 'KangarooTwelve rejects missing outputLength', async () => {
353+
let threw = false;
354+
try {
355+
// outputLength deliberately omitted — required by WICG WebCrypto Modern
356+
// Algos draft and Node webidl.js:880-897.
357+
await subtle.digest({ name: 'KT128' }, new Uint8Array(0));
358+
} catch (err) {
359+
threw = true;
360+
expect((err as Error).name).to.equal('OperationError');
361+
}
362+
expect(threw).to.equal(true);
363+
});

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ Pod::Spec.new do |s|
152152
"\"$(PODS_TARGET_SRCROOT)/cpp/hkdf\"",
153153
"\"$(PODS_TARGET_SRCROOT)/cpp/dh\"",
154154
"\"$(PODS_TARGET_SRCROOT)/cpp/ecdh\"",
155+
"\"$(PODS_TARGET_SRCROOT)/cpp/turboshake\"",
155156
"\"$(PODS_TARGET_SRCROOT)/nitrogen/generated/shared/c++\"",
156157
"\"$(PODS_TARGET_SRCROOT)/deps/ncrypto/include\"",
157158
"\"$(PODS_TARGET_SRCROOT)/deps/simdutf/include\"",

0 commit comments

Comments
 (0)