@amritk/nish-x86_64-linux 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/INSTALL.md +43 -25
  2. package/bin/nish +0 -0
  3. package/package.json +2 -2
  4. package/runtime/nish.d.ts +55 -5
  5. package/runtime/nish.h +215 -18
  6. package/runtime/nish.mjs +16 -0
  7. package/runtime/runtime-aes.c +490 -0
  8. package/runtime/runtime-cross.c +366 -0
  9. package/runtime/runtime-host.c +7 -6
  10. package/runtime/runtime-net.c +93 -34
  11. package/runtime/runtime-os.c +213 -15
  12. package/runtime/runtime-parallel.c +590 -107
  13. package/runtime/runtime-simd.c +4 -4
  14. package/runtime/runtime-wasm.c +5 -3
  15. package/runtime/runtime.c +69 -13
  16. package/runtime/shim.mjs +71 -1
  17. package/scripts/build.sh +223 -22
  18. package/std/README.md +81 -51
  19. package/std/bytes.ts +163 -0
  20. package/std/crypto/LICENSE-libsecp256k1 +19 -0
  21. package/std/crypto/aes.ts +238 -76
  22. package/std/crypto/chacha20poly1305.ts +43 -9
  23. package/std/crypto/ed25519.ts +840 -0
  24. package/std/crypto/hkdf.ts +304 -12
  25. package/std/crypto/hmac.ts +245 -53
  26. package/std/crypto/p256.ts +932 -61
  27. package/std/crypto/x25519.ts +55 -37
  28. package/std/crypto/x509-random.ts +71 -0
  29. package/std/crypto/x509.ts +9 -9
  30. package/std/json-tokenizer.ts +1296 -0
  31. package/std/json-typed.ts +1014 -0
  32. package/std/json-value.ts +1163 -0
  33. package/std/json.ts +1120 -34
  34. package/std/net/buffer-pool.ts +151 -0
  35. package/std/net/hpack.ts +416 -112
  36. package/std/net/http-fields.ts +93 -0
  37. package/std/net/http1-server.ts +417 -55
  38. package/std/net/http1.ts +31 -6
  39. package/std/net/http2-tls.ts +112 -24
  40. package/std/net/http2.ts +333 -75
  41. package/std/net/http3-server.ts +211 -31
  42. package/std/net/http3.ts +187 -28
  43. package/std/net/prefork.ts +412 -0
  44. package/std/net/quic-listener.ts +88 -7
  45. package/std/net/quic-packet.ts +243 -82
  46. package/std/net/quic-recovery.ts +361 -30
  47. package/std/net/quic-stream.ts +424 -24
  48. package/std/net/quic.ts +519 -167
  49. package/std/net/router.ts +676 -0
  50. package/std/net/tls/codec.ts +426 -27
  51. package/std/net/tls/record-server.ts +478 -132
  52. package/std/net/tls/record.ts +133 -42
  53. package/std/net/tls/schedule.ts +464 -55
  54. package/std/net/tls/ticket.ts +610 -0
  55. package/std/net/tls-tcp.ts +196 -53
  56. package/std/net/tls.ts +1018 -260
  57. package/std/net/url.ts +571 -0
  58. package/std/random.ts +94 -0
  59. package/std/threads.ts +438 -14
@@ -0,0 +1,490 @@
1
+ /* Nish runtime, the AES half: AES-128 and AES-256 in counter mode and GHASH on
2
+ * the CPU's own AES and carry-less multiply instructions (WP38 S5,
3
+ * docs/wp38-simd.md §3.1). std/crypto/aes.ts is the cipher; this is a faster
4
+ * body for four of its private functions, which the compiler calls in their
5
+ * place when `nish_aes_hw` says this machine can run it.
6
+ *
7
+ * A translation unit of its own for the reason runtime-simd.c is one: each file
8
+ * carries its own measured `.text*` ceiling in tests/runtime-budgets.js, and this
9
+ * one would not fit in what runtime-simd.c has left (RUNTIME_AES_TEXT_BUDGET says
10
+ * how much it needs). It also keeps byte search and AES apart, so a program that
11
+ * searches text links no AES and one that speaks TLS no byte search. Section GC
12
+ * means a program that never calls a kernel links none of this file.
13
+ *
14
+ * There are two paths and no portable one. x86-64 runs AES-NI and PCLMULQDQ
15
+ * (plus SSSE3's byte shuffle), compiled for those extensions alone with
16
+ * `target(...)` so the binary stays a baseline binary; AArch64 runs the ARMv8
17
+ * Cryptography Extension's AES and PMULL, likewise with `target("+aes")`. A
18
+ * machine without them, or a process with `NISH_AES=soft`, runs the bitsliced
19
+ * Nish code in std/crypto/aes.ts instead: the compiler keeps that code and
20
+ * branches on `nish_aes_hw` at each call, so a table-free C AES here would only
21
+ * be a second copy of the code under review. Every other target, wasm32
22
+ * included, compiles `nish_aes_hw` to `return 0` and the kernels to a trap
23
+ * nothing reaches, so a link that names them still resolves.
24
+ *
25
+ * The round-key array, `rk`, is the `8 * (rounds + 1)` words (88 or 120 u64s)
26
+ * the bitsliced schedule uses, laid out differently on this path. One process
27
+ * only ever uses one layout, because `nish_aes_hw`'s answer is fixed on its
28
+ * first call. In bytes:
29
+ *
30
+ * [0, 16 * (rounds + 1)) the FIPS 197 key schedule, round key r at 16 * r,
31
+ * each in the byte order the cipher XORs it in
32
+ * [240, 304) H, H^2, H^3 and H^4 for GHASH, H = E(K, 0^128),
33
+ * each as two u64 lanes, low then high, of the block
34
+ * read as a big-endian 128-bit number
35
+ * [304, 368) for each power, its two halves XORed together in
36
+ * both lanes: Karatsuba's middle operand, kept so a
37
+ * multiply by a power costs three carry-less
38
+ * products, not four
39
+ * everything else zero
40
+ *
41
+ * H sits at a fixed offset rather than after the schedule because
42
+ * `nish_aes_ghash` is not told `rounds`. `nish_aes_ghash` reads H and its
43
+ * powers from `rk` and not from its `hHi`/`hLo` arguments, which the bitsliced
44
+ * walker it stands in for needs and which hold the same H (std/crypto/aes.ts
45
+ * computes them as E(K, 0^128) from this same schedule).
46
+ *
47
+ * Constant time: every kernel's branches are loop bounds on `length`, `rounds`
48
+ * or an array header, and no load or store address depends on a key, data or
49
+ * H. tests/ct-asm-runtime.js reads the `clang -O2` assembly of this file for
50
+ * both targets and holds every kernel function to that. The one branch on
51
+ * anything else is `nish_aes_hw`'s, on the CPU's feature bits and `NISH_AES`
52
+ * (docs/security/ct-verification.md).
53
+ *
54
+ * Wiping: the kernels keep no copy of a round key, a counter block or the GHASH
55
+ * state on the stack on purpose. The two stack buffers they do have, the
56
+ * keystream of a partial last block and a zero-padded last block for GHASH, are
57
+ * zeroed before return by volatile stores, the mechanism `nish:secret`'s wipe
58
+ * uses, which tests/run.js holds against `clang -O2`. What sits in vector
59
+ * registers, and what the register allocator spills, no store from C can
60
+ * reach (docs/security/crypto-aead.md).
61
+ *
62
+ * The algorithms are FIPS 197 (the key expansion, with SubWord taken from the
63
+ * AES instructions) and SP 800-38D (GCTR and GHASH); the GHASH multiply is the
64
+ * bit-reflected carry-less method of S. Gueron and M. E. Kounavis, "Intel
65
+ * Carry-Less Multiplication Instruction and its Usage for Computing the GCM
66
+ * Mode" (Intel, rev. 2.02, 2014), written here from the paper's description,
67
+ * with the aggregated reduction of its §5 over four blocks.
68
+ */
69
+ #include <stdint.h>
70
+
71
+ #include "nish.h"
72
+
73
+ #if defined(__x86_64__)
74
+ #include <cpuid.h>
75
+ #include <tmmintrin.h>
76
+ #include <wmmintrin.h>
77
+ #define NISH_AES_X86 1
78
+ #elif defined(__aarch64__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
79
+ #include <arm_neon.h>
80
+ #define NISH_AES_ARM 1
81
+ #endif
82
+
83
+ #if defined(NISH_AES_X86) || defined(NISH_AES_ARM)
84
+
85
+ /* Named here rather than through <stdlib.h> and <sys/auxv.h>, because the
86
+ constant-time check compiles this file for aarch64 on hosts with no aarch64
87
+ libc headers, and these two prototypes are all it needs from them. */
88
+ char *getenv(const char *name);
89
+
90
+ /* GCC's vector extensions, which clang shares: XOR and per-lane shifts work on
91
+ both targets, so GHASH's reduction is written once. The `u` types are the
92
+ same vectors at byte alignment, for loads and stores through an array's
93
+ data, and may alias the u64 slots they are read out of. */
94
+ typedef uint64_t nish_aes_u64x2 __attribute__((vector_size(16)));
95
+ typedef uint8_t nish_aes_u8x16 __attribute__((vector_size(16)));
96
+ typedef uint32_t nish_aes_u32x4 __attribute__((vector_size(16)));
97
+ typedef uint8_t nish_aes_u8x16u __attribute__((vector_size(16), aligned(1), may_alias));
98
+ typedef uint32_t nish_aes_u32u __attribute__((aligned(1), may_alias));
99
+
100
+ /* Every kernel and helper: the extensions it needs, and (under clang, the
101
+ compiler build.sh uses) no calls to libc, so neither a byte loop nor a wipe
102
+ turns into a `memcpy` or `memset` the constant-time check would have to
103
+ take on trust. */
104
+ #if defined(__has_attribute) && __has_attribute(no_builtin)
105
+ #define NISH_AES_NO_LIBC no_builtin
106
+ #else
107
+ #define NISH_AES_NO_LIBC
108
+ #endif
109
+ #if defined(NISH_AES_X86)
110
+ #define NISH_AES_TARGET __attribute__((target("aes,pclmul,ssse3"), NISH_AES_NO_LIBC))
111
+ #else
112
+ #define NISH_AES_TARGET __attribute__((target("+aes"), NISH_AES_NO_LIBC))
113
+ #endif
114
+ #define NISH_AES_INLINE static inline __attribute__((always_inline)) NISH_AES_TARGET
115
+
116
+ /* Byte offsets in `rk` (the layout in the header). */
117
+ #define NISH_AES_HPOW 240
118
+ #define NISH_AES_HMID 304
119
+ #define NISH_AES_HEND 368
120
+
121
+ NISH_AES_INLINE nish_aes_u8x16 nish_aes_load(const char *p) {
122
+ return *(const nish_aes_u8x16u *)p;
123
+ }
124
+
125
+ NISH_AES_INLINE void nish_aes_store(char *p, nish_aes_u8x16 v) {
126
+ *(nish_aes_u8x16u *)p = v;
127
+ }
128
+
129
+ /* Zeroes sixteen bytes of the stack with stores `-O2` must keep. */
130
+ NISH_AES_INLINE void nish_aes_wipe16(void *p) {
131
+ volatile uint64_t *v = (volatile uint64_t *)p;
132
+ v[0] = 0;
133
+ v[1] = 0;
134
+ }
135
+
136
+ /* The block read as a big-endian 128-bit number, as two lanes low then high:
137
+ the order GHASH's multiply works in. Its own inverse. */
138
+ NISH_AES_INLINE nish_aes_u64x2 nish_aes_reflect(nish_aes_u8x16 b) {
139
+ return (nish_aes_u64x2)__builtin_shufflevector(b, b, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);
140
+ }
141
+
142
+ #if defined(NISH_AES_X86)
143
+ /* One block through `rounds` rounds of AES-NI: the whitening key, the middle
144
+ rounds, and the last round, which skips MixColumns. Round keys are read from
145
+ `k` at each use rather than held, so none is copied to the stack. */
146
+ NISH_AES_INLINE nish_aes_u8x16 nish_aes_encrypt(const char *k, int32_t rounds, nish_aes_u8x16 s) {
147
+ __m128i x = (__m128i)s ^ (__m128i)nish_aes_load(k);
148
+ for (int32_t r = 1; r < rounds; r++) x = _mm_aesenc_si128(x, (__m128i)nish_aes_load(k + 16 * r));
149
+ return (nish_aes_u8x16)_mm_aesenclast_si128(x, (__m128i)nish_aes_load(k + 16 * rounds));
150
+ }
151
+
152
+ /* Four blocks at once: AESENC's latency is several cycles and one issues a
153
+ cycle, so four independent blocks keep the unit busy where one would wait. */
154
+ NISH_AES_INLINE void nish_aes_encrypt4(const char *k, int32_t rounds, nish_aes_u8x16 *s) {
155
+ __m128i k0 = (__m128i)nish_aes_load(k);
156
+ __m128i a = (__m128i)s[0] ^ k0, b = (__m128i)s[1] ^ k0, c = (__m128i)s[2] ^ k0, d = (__m128i)s[3] ^ k0;
157
+ for (int32_t r = 1; r < rounds; r++) {
158
+ __m128i kr = (__m128i)nish_aes_load(k + 16 * r);
159
+ a = _mm_aesenc_si128(a, kr);
160
+ b = _mm_aesenc_si128(b, kr);
161
+ c = _mm_aesenc_si128(c, kr);
162
+ d = _mm_aesenc_si128(d, kr);
163
+ }
164
+ __m128i kl = (__m128i)nish_aes_load(k + 16 * rounds);
165
+ s[0] = (nish_aes_u8x16)_mm_aesenclast_si128(a, kl);
166
+ s[1] = (nish_aes_u8x16)_mm_aesenclast_si128(b, kl);
167
+ s[2] = (nish_aes_u8x16)_mm_aesenclast_si128(c, kl);
168
+ s[3] = (nish_aes_u8x16)_mm_aesenclast_si128(d, kl);
169
+ }
170
+
171
+ /* SubWord (FIPS 197 §5.2) of one key word, in the order its bytes sit in
172
+ memory: AESKEYGENASSIST's lowest dword is SubWord of the dword above it. */
173
+ NISH_AES_INLINE uint32_t nish_aes_sub_word(uint32_t w) {
174
+ return (uint32_t)_mm_cvtsi128_si32(_mm_aeskeygenassist_si128(_mm_set1_epi32((int)w), 0));
175
+ }
176
+
177
+ /* The low lanes' and the high lanes' 64 × 64 carry-less products. */
178
+ NISH_AES_INLINE nish_aes_u64x2 nish_aes_clmul_lo(nish_aes_u64x2 a, nish_aes_u64x2 b) {
179
+ return (nish_aes_u64x2)_mm_clmulepi64_si128((__m128i)a, (__m128i)b, 0x00);
180
+ }
181
+
182
+ NISH_AES_INLINE nish_aes_u64x2 nish_aes_clmul_hi(nish_aes_u64x2 a, nish_aes_u64x2 b) {
183
+ return (nish_aes_u64x2)_mm_clmulepi64_si128((__m128i)a, (__m128i)b, 0x11);
184
+ }
185
+ #else
186
+ /* The same on ARMv8. AESE XORs its key in before SubBytes and ShiftRows, where
187
+ AESENC does it after MixColumns, so each round key moves one round earlier
188
+ and the last one is XORed in on its own. */
189
+ NISH_AES_INLINE nish_aes_u8x16 nish_aes_encrypt(const char *k, int32_t rounds, nish_aes_u8x16 s) {
190
+ uint8x16_t x = (uint8x16_t)s;
191
+ for (int32_t r = 0; r < rounds - 1; r++) x = vaesmcq_u8(vaeseq_u8(x, (uint8x16_t)nish_aes_load(k + 16 * r)));
192
+ x = vaeseq_u8(x, (uint8x16_t)nish_aes_load(k + 16 * (rounds - 1)));
193
+ return (nish_aes_u8x16)x ^ nish_aes_load(k + 16 * rounds);
194
+ }
195
+
196
+ NISH_AES_INLINE void nish_aes_encrypt4(const char *k, int32_t rounds, nish_aes_u8x16 *s) {
197
+ uint8x16_t a = (uint8x16_t)s[0], b = (uint8x16_t)s[1], c = (uint8x16_t)s[2], d = (uint8x16_t)s[3];
198
+ for (int32_t r = 0; r < rounds - 1; r++) {
199
+ uint8x16_t kr = (uint8x16_t)nish_aes_load(k + 16 * r);
200
+ a = vaesmcq_u8(vaeseq_u8(a, kr));
201
+ b = vaesmcq_u8(vaeseq_u8(b, kr));
202
+ c = vaesmcq_u8(vaeseq_u8(c, kr));
203
+ d = vaesmcq_u8(vaeseq_u8(d, kr));
204
+ }
205
+ uint8x16_t kp = (uint8x16_t)nish_aes_load(k + 16 * (rounds - 1));
206
+ nish_aes_u8x16 kl = nish_aes_load(k + 16 * rounds);
207
+ s[0] = (nish_aes_u8x16)vaeseq_u8(a, kp) ^ kl;
208
+ s[1] = (nish_aes_u8x16)vaeseq_u8(b, kp) ^ kl;
209
+ s[2] = (nish_aes_u8x16)vaeseq_u8(c, kp) ^ kl;
210
+ s[3] = (nish_aes_u8x16)vaeseq_u8(d, kp) ^ kl;
211
+ }
212
+
213
+ /* SubWord from AESE with a zero key: with the word in all four columns,
214
+ ShiftRows moves each byte to a column holding the same byte, so lane 0 is
215
+ SubBytes of the word and nothing else. */
216
+ NISH_AES_INLINE uint32_t nish_aes_sub_word(uint32_t w) {
217
+ return vgetq_lane_u32(vreinterpretq_u32_u8(vaeseq_u8(vreinterpretq_u8_u32(vdupq_n_u32(w)), vdupq_n_u8(0))), 0);
218
+ }
219
+
220
+ NISH_AES_INLINE nish_aes_u64x2 nish_aes_clmul_lo(nish_aes_u64x2 a, nish_aes_u64x2 b) {
221
+ return (nish_aes_u64x2)vreinterpretq_u64_p128(vmull_p64((poly64_t)a[0], (poly64_t)b[0]));
222
+ }
223
+
224
+ NISH_AES_INLINE nish_aes_u64x2 nish_aes_clmul_hi(nish_aes_u64x2 a, nish_aes_u64x2 b) {
225
+ return (nish_aes_u64x2)vreinterpretq_u64_p128(
226
+ vmull_high_p64(vreinterpretq_p64_u64((uint64x2_t)a), vreinterpretq_p64_u64((uint64x2_t)b)));
227
+ }
228
+ #endif
229
+
230
+ /* GHASH's reduction (SP 800-38D §6.3): the 256-bit product `hi:lo` of two
231
+ reflected blocks is the true product shifted right by one, so it is shifted
232
+ left once, and then its low half -- the coefficients of x^128 and up -- is
233
+ folded into the high half by x^128 = x^7 + x^2 + x + 1, in two steps
234
+ because the fold of its lowest bits lands back in the low half (Gueron and
235
+ Kounavis, Algorithm 4 and §4's shift). */
236
+ NISH_AES_INLINE nish_aes_u64x2 nish_aes_reduce(nish_aes_u64x2 hi, nish_aes_u64x2 lo) {
237
+ const nish_aes_u64x2 zero = {0, 0};
238
+ nish_aes_u64x2 hiOut = (hi << 1) | __builtin_shufflevector(hi >> 63, zero, 2, 0) |
239
+ __builtin_shufflevector(lo >> 63, zero, 1, 2);
240
+ lo = (lo << 1) | __builtin_shufflevector(lo >> 63, zero, 2, 0);
241
+ /* The first fold: x0's bits that x, x^2 and x^7 carry past the 128th
242
+ position land in x1. */
243
+ lo ^= __builtin_shufflevector((lo << 63) ^ (lo << 62) ^ (lo << 57), zero, 2, 0);
244
+ /* The second: all of d:x0 times 1 + x + x^2 + x^7, which in the reflected
245
+ order is d:x0 shifted right by 0, 1, 2 and 7 across both lanes. */
246
+ nish_aes_u64x2 r1 = (lo >> 1) | __builtin_shufflevector(lo << 63, zero, 1, 2);
247
+ nish_aes_u64x2 r2 = (lo >> 2) | __builtin_shufflevector(lo << 62, zero, 1, 2);
248
+ nish_aes_u64x2 r7 = (lo >> 7) | __builtin_shufflevector(lo << 57, zero, 1, 2);
249
+ return hiOut ^ lo ^ r1 ^ r2 ^ r7;
250
+ }
251
+
252
+ /* Karatsuba's middle operand: the two halves XORed, in both lanes. */
253
+ NISH_AES_INLINE nish_aes_u64x2 nish_aes_mid(nish_aes_u64x2 a) {
254
+ return a ^ __builtin_shufflevector(a, a, 1, 0);
255
+ }
256
+
257
+ /* Accumulates one 128 × 128 carry-less product into `acc`: lane products
258
+ into [0] and [1], the middle one into [2], `hm` being `h`'s middle operand. */
259
+ NISH_AES_INLINE void nish_aes_mul_acc(nish_aes_u64x2 *acc, nish_aes_u64x2 x, nish_aes_u64x2 h, nish_aes_u64x2 hm) {
260
+ acc[0] ^= nish_aes_clmul_lo(x, h);
261
+ acc[1] ^= nish_aes_clmul_hi(x, h);
262
+ acc[2] ^= nish_aes_clmul_lo(nish_aes_mid(x), hm);
263
+ }
264
+
265
+ /* Reduces what `nish_aes_mul_acc` accumulated: the middle product, less the
266
+ other two, straddles the halves at bit 64. */
267
+ NISH_AES_INLINE nish_aes_u64x2 nish_aes_mul_finish(const nish_aes_u64x2 *acc) {
268
+ const nish_aes_u64x2 zero = {0, 0};
269
+ nish_aes_u64x2 mid = acc[2] ^ acc[0] ^ acc[1];
270
+ return nish_aes_reduce(acc[1] ^ __builtin_shufflevector(mid, zero, 1, 2),
271
+ acc[0] ^ __builtin_shufflevector(mid, zero, 2, 0));
272
+ }
273
+
274
+ NISH_AES_INLINE nish_aes_u64x2 nish_aes_mul(nish_aes_u64x2 x, nish_aes_u64x2 h) {
275
+ nish_aes_u64x2 acc[3] = {{0, 0}, {0, 0}, {0, 0}};
276
+ nish_aes_mul_acc(acc, x, h, nish_aes_mid(h));
277
+ return nish_aes_mul_finish(acc);
278
+ }
279
+
280
+ /* `nish_aes_ctr`'s counter block `n` past J0: J0's first twelve bytes and its
281
+ last four, read big-endian, plus `n`, modulo 2^32 as inc32 says. */
282
+ NISH_AES_INLINE nish_aes_u8x16 nish_aes_counter(nish_aes_u8x16 j0, uint32_t counter, uint32_t n) {
283
+ nish_aes_u32x4 v = (nish_aes_u32x4)j0;
284
+ v[3] = __builtin_bswap32(counter + n);
285
+ return (nish_aes_u8x16)v;
286
+ }
287
+
288
+ NISH_AES_TARGET void nish_aes_schedule(const nish_array *key, int32_t rounds, nish_array *rk) {
289
+ const int32_t nk = rounds - 6;
290
+ const int32_t words = 4 * (rounds + 1);
291
+ const nish_aes_u32u *in = (const nish_aes_u32u *)key->data;
292
+ nish_aes_u32u *w = (nish_aes_u32u *)rk->data;
293
+ for (int32_t i = 0; i < nk; i++) w[i] = in[i];
294
+ /* FIPS 197 §5.2 on words read little-endian, so RotWord is a rotate right
295
+ by eight and Rcon goes in the low byte. nk is 4 or 8, so `i % nk` is a
296
+ mask. */
297
+ uint32_t rcon = 1;
298
+ for (int32_t i = nk; i < words; i++) {
299
+ uint32_t t = w[i - 1];
300
+ if ((i & (nk - 1)) == 0) {
301
+ t = nish_aes_sub_word((t >> 8) | (t << 24)) ^ rcon;
302
+ rcon = (rcon << 1) ^ ((rcon >> 7) * 0x11b);
303
+ } else if (nk == 8 && (i & 7) == 4) {
304
+ t = nish_aes_sub_word(t);
305
+ }
306
+ w[i] = w[i - nk] ^ t;
307
+ }
308
+ /* Every word after the schedule is zero but H's, so nothing the bitsliced
309
+ layout left in a reused array survives into this one. */
310
+ uint64_t *all = (uint64_t *)rk->data;
311
+ for (uint64_t i = (uint64_t)words / 2; i < rk->len; i++) all[i] = 0;
312
+ /* H = E(K, 0^128) and its powers, each with its middle operand. */
313
+ const nish_aes_u8x16 zero = {0};
314
+ nish_aes_u64x2 h = nish_aes_reflect(nish_aes_encrypt(rk->data, rounds, zero));
315
+ nish_aes_u64x2 p = h;
316
+ for (int32_t i = 0; i < 4; i++) {
317
+ nish_aes_store(rk->data + NISH_AES_HPOW + 16 * i, (nish_aes_u8x16)p);
318
+ nish_aes_store(rk->data + NISH_AES_HMID + 16 * i, (nish_aes_u8x16)nish_aes_mid(p));
319
+ p = nish_aes_mul(p, h);
320
+ }
321
+ }
322
+
323
+ NISH_AES_TARGET void nish_aes_block(const nish_array *rk, int32_t rounds, const nish_array *src, nish_array *dst) {
324
+ nish_aes_store(dst->data, nish_aes_encrypt(rk->data, rounds, nish_aes_load(src->data)));
325
+ }
326
+
327
+ NISH_AES_TARGET void nish_aes_ctr(const nish_array *rk, int32_t rounds, const nish_array *j0, const nish_array *src,
328
+ nish_array *dst, int32_t length) {
329
+ const char *k = rk->data;
330
+ const char *in = src->data;
331
+ char *out = dst->data;
332
+ const nish_aes_u8x16 iv = nish_aes_load(j0->data);
333
+ const uint32_t counter = __builtin_bswap32(*(const nish_aes_u32u *)(j0->data + 12));
334
+ const uint32_t blocks = (uint32_t)length >> 4;
335
+ uint32_t n = 0;
336
+ /* Four blocks a pass, each read before its own block is written, so `dst`
337
+ may be `src`. */
338
+ for (; n + 4 <= blocks; n += 4) {
339
+ nish_aes_u8x16 s[4];
340
+ for (uint32_t j = 0; j < 4; j++) s[j] = nish_aes_counter(iv, counter, n + j + 1);
341
+ nish_aes_encrypt4(k, rounds, s);
342
+ for (uint32_t j = 0; j < 4; j++) {
343
+ const uint64_t at = 16 * (uint64_t)(n + j);
344
+ nish_aes_store(out + at, nish_aes_load(in + at) ^ s[j]);
345
+ }
346
+ }
347
+ for (; n < blocks; n++) {
348
+ const uint64_t at = 16 * (uint64_t)n;
349
+ nish_aes_store(out + at, nish_aes_load(in + at) ^ nish_aes_encrypt(k, rounds, nish_aes_counter(iv, counter, n + 1)));
350
+ }
351
+ const uint32_t rest = (uint32_t)length & 15;
352
+ if (rest != 0) {
353
+ _Alignas(16) uint8_t ks[16];
354
+ nish_aes_store((char *)ks, nish_aes_encrypt(k, rounds, nish_aes_counter(iv, counter, n + 1)));
355
+ const uint64_t at = 16 * (uint64_t)n;
356
+ for (uint32_t i = 0; i < rest; i++) out[at + i] = (char)(in[at + i] ^ ks[i]);
357
+ nish_aes_wipe16(ks);
358
+ }
359
+ }
360
+
361
+ NISH_AES_TARGET void nish_aes_ghash(nish_array *y, uint64_t hHi, uint64_t hLo, const nish_array *rk,
362
+ const nish_array *data, int32_t length) {
363
+ (void)hHi;
364
+ (void)hLo;
365
+ const char *k = rk->data;
366
+ const char *in = data->data;
367
+ /* y is two words, high then low; the multiply wants low then high. */
368
+ nish_aes_u64x2 acc = (nish_aes_u64x2)nish_aes_load(y->data);
369
+ acc = __builtin_shufflevector(acc, acc, 1, 0);
370
+ const uint32_t blocks = (uint32_t)length >> 4;
371
+ uint32_t n = 0;
372
+ /* Four blocks a reduction: (Y ⊕ X1)·H^4 ⊕ X2·H^3 ⊕ X3·H^2 ⊕ X4·H is four
373
+ blocks of the serial recurrence, and its products add before the one
374
+ reduction they share. */
375
+ if (blocks >= 4) {
376
+ nish_aes_u64x2 hp[4], hm[4];
377
+ for (int32_t i = 0; i < 4; i++) {
378
+ hp[i] = (nish_aes_u64x2)nish_aes_load(k + NISH_AES_HPOW + 16 * i);
379
+ hm[i] = (nish_aes_u64x2)nish_aes_load(k + NISH_AES_HMID + 16 * i);
380
+ }
381
+ for (; n + 4 <= blocks; n += 4) {
382
+ nish_aes_u64x2 sum[3] = {{0, 0}, {0, 0}, {0, 0}};
383
+ for (uint32_t j = 0; j < 4; j++) {
384
+ nish_aes_u64x2 x = nish_aes_reflect(nish_aes_load(in + 16 * (uint64_t)(n + j)));
385
+ if (j == 0) x ^= acc;
386
+ nish_aes_mul_acc(sum, x, hp[3 - j], hm[3 - j]);
387
+ }
388
+ acc = nish_aes_mul_finish(sum);
389
+ }
390
+ }
391
+ const nish_aes_u64x2 h = (nish_aes_u64x2)nish_aes_load(k + NISH_AES_HPOW);
392
+ for (; n < blocks; n++) acc = nish_aes_mul(acc ^ nish_aes_reflect(nish_aes_load(in + 16 * (uint64_t)n)), h);
393
+ const uint32_t rest = (uint32_t)length & 15;
394
+ if (rest != 0) {
395
+ _Alignas(16) uint8_t pad[16] = {0};
396
+ const uint64_t at = 16 * (uint64_t)n;
397
+ for (uint32_t i = 0; i < rest; i++) pad[i] = (uint8_t)in[at + i];
398
+ acc = nish_aes_mul(acc ^ nish_aes_reflect(nish_aes_load((const char *)pad)), h);
399
+ nish_aes_wipe16(pad);
400
+ }
401
+ nish_aes_store(y->data, (nish_aes_u8x16)__builtin_shufflevector(acc, acc, 1, 0));
402
+ }
403
+
404
+ #if defined(NISH_AES_X86)
405
+ /* Whether this CPU runs the kernels: CPUID leaf 1's ECX has AES (bit 25),
406
+ PCLMULQDQ (bit 1) and SSSE3 (bit 9), whose byte shuffle reverses a block for
407
+ GHASH. Asked through `cpuid.h`, which is inline assembly, and not through
408
+ `__builtin_cpu_supports`, whose libgcc object runs a constructor at every
409
+ start-up once a link names it (runtime-simd.c, `nish_cpu_has_avx2`). The
410
+ 128-bit registers need no question of the operating system: x86-64 saves
411
+ them on every context switch. */
412
+ static int32_t nish_aes_cpu(void) {
413
+ unsigned a, b, c, d;
414
+ if (!__get_cpuid(1, &a, &b, &c, &d)) return 0;
415
+ return (c & bit_AES) != 0 && (c & bit_PCLMUL) != 0 && (c & bit_SSSE3) != 0;
416
+ }
417
+ #elif defined(__linux__)
418
+ /* Linux reports the ARMv8 Cryptography Extension in the auxiliary vector:
419
+ AT_HWCAP is 16, and its HWCAP_AES and HWCAP_PMULL bits are 3 and 4
420
+ (arch/arm64/include/uapi/asm/hwcap.h). */
421
+ unsigned long getauxval(unsigned long type);
422
+
423
+ static int32_t nish_aes_cpu(void) {
424
+ unsigned long caps = getauxval(16);
425
+ return (caps & (1ul << 3)) != 0 && (caps & (1ul << 4)) != 0;
426
+ }
427
+ #elif defined(__APPLE__)
428
+ /* Every Apple AArch64 core has both: they are part of the A7's ARMv8.0 and of
429
+ every core since. */
430
+ static int32_t nish_aes_cpu(void) {
431
+ return 1;
432
+ }
433
+ #else
434
+ /* Another AArch64 system: no question asked, so the bitsliced path. */
435
+ static int32_t nish_aes_cpu(void) {
436
+ return 0;
437
+ }
438
+ #endif
439
+
440
+ /* The answer: 0 until the first call, then 1 for the kernels or 2 for the
441
+ bitsliced path. The load and the store are relaxed atomics, plain moves on
442
+ both targets, because under `--threads` two workers may make the first call
443
+ at once; both reach the same answer, so whichever store lands is it. */
444
+ static int32_t nish_aes_answer;
445
+
446
+ int32_t nish_aes_hw(void) {
447
+ int32_t answer = __atomic_load_n(&nish_aes_answer, __ATOMIC_RELAXED);
448
+ if (answer == 0) {
449
+ /* `soft` pins the bitsliced path and never asks the CPU; anything else,
450
+ or nothing, means the best this machine has. */
451
+ const char *pin = getenv("NISH_AES");
452
+ int soft = pin != 0 && pin[0] == 's' && pin[1] == 'o' && pin[2] == 'f' && pin[3] == 't' && pin[4] == 0;
453
+ answer = !soft && nish_aes_cpu() ? 1 : 2;
454
+ __atomic_store_n(&nish_aes_answer, answer, __ATOMIC_RELAXED);
455
+ }
456
+ return answer == 1;
457
+ }
458
+
459
+ #else
460
+
461
+ /* A target with no AES instructions this file uses, wasm32 among them: the
462
+ bitsliced path always, and kernels that a program whose every call is behind
463
+ `nish_aes_hw` never reaches. */
464
+ int32_t nish_aes_hw(void) {
465
+ return 0;
466
+ }
467
+
468
+ void nish_aes_schedule(const nish_array *key, int32_t rounds, nish_array *rk) {
469
+ (void)key, (void)rounds, (void)rk;
470
+ __builtin_trap();
471
+ }
472
+
473
+ void nish_aes_block(const nish_array *rk, int32_t rounds, const nish_array *src, nish_array *dst) {
474
+ (void)rk, (void)rounds, (void)src, (void)dst;
475
+ __builtin_trap();
476
+ }
477
+
478
+ void nish_aes_ctr(const nish_array *rk, int32_t rounds, const nish_array *j0, const nish_array *src, nish_array *dst,
479
+ int32_t length) {
480
+ (void)rk, (void)rounds, (void)j0, (void)src, (void)dst, (void)length;
481
+ __builtin_trap();
482
+ }
483
+
484
+ void nish_aes_ghash(nish_array *y, uint64_t hHi, uint64_t hLo, const nish_array *rk, const nish_array *data,
485
+ int32_t length) {
486
+ (void)y, (void)hHi, (void)hLo, (void)rk, (void)data, (void)length;
487
+ __builtin_trap();
488
+ }
489
+
490
+ #endif