@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.
- package/INSTALL.md +43 -25
- package/bin/nish +0 -0
- package/package.json +2 -2
- package/runtime/nish.d.ts +55 -5
- package/runtime/nish.h +215 -18
- package/runtime/nish.mjs +16 -0
- package/runtime/runtime-aes.c +490 -0
- package/runtime/runtime-cross.c +366 -0
- package/runtime/runtime-host.c +7 -6
- package/runtime/runtime-net.c +93 -34
- package/runtime/runtime-os.c +213 -15
- package/runtime/runtime-parallel.c +590 -107
- package/runtime/runtime-simd.c +4 -4
- package/runtime/runtime-wasm.c +5 -3
- package/runtime/runtime.c +69 -13
- package/runtime/shim.mjs +71 -1
- package/scripts/build.sh +223 -22
- package/std/README.md +81 -51
- package/std/bytes.ts +163 -0
- package/std/crypto/LICENSE-libsecp256k1 +19 -0
- package/std/crypto/aes.ts +238 -76
- package/std/crypto/chacha20poly1305.ts +43 -9
- package/std/crypto/ed25519.ts +840 -0
- package/std/crypto/hkdf.ts +304 -12
- package/std/crypto/hmac.ts +245 -53
- package/std/crypto/p256.ts +932 -61
- package/std/crypto/x25519.ts +55 -37
- package/std/crypto/x509-random.ts +71 -0
- package/std/crypto/x509.ts +9 -9
- package/std/json-tokenizer.ts +1296 -0
- package/std/json-typed.ts +1014 -0
- package/std/json-value.ts +1163 -0
- package/std/json.ts +1120 -34
- package/std/net/buffer-pool.ts +151 -0
- package/std/net/hpack.ts +416 -112
- package/std/net/http-fields.ts +93 -0
- package/std/net/http1-server.ts +417 -55
- package/std/net/http1.ts +31 -6
- package/std/net/http2-tls.ts +112 -24
- package/std/net/http2.ts +333 -75
- package/std/net/http3-server.ts +211 -31
- package/std/net/http3.ts +187 -28
- package/std/net/prefork.ts +412 -0
- package/std/net/quic-listener.ts +88 -7
- package/std/net/quic-packet.ts +243 -82
- package/std/net/quic-recovery.ts +361 -30
- package/std/net/quic-stream.ts +424 -24
- package/std/net/quic.ts +519 -167
- package/std/net/router.ts +676 -0
- package/std/net/tls/codec.ts +426 -27
- package/std/net/tls/record-server.ts +478 -132
- package/std/net/tls/record.ts +133 -42
- package/std/net/tls/schedule.ts +464 -55
- package/std/net/tls/ticket.ts +610 -0
- package/std/net/tls-tcp.ts +196 -53
- package/std/net/tls.ts +1018 -260
- package/std/net/url.ts +571 -0
- package/std/random.ts +94 -0
- 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
|