@amritk/nish 0.12.0 → 0.14.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 (42) hide show
  1. package/README.md +27 -23
  2. package/bin/launcher.js +45 -41
  3. package/bin/packaging.js +39 -33
  4. package/docs/AI.md +157 -35
  5. package/docs/INSTALL.md +13 -13
  6. package/llms.txt +1 -1
  7. package/package.json +9 -6
  8. package/runtime/nish.d.ts +85 -1
  9. package/runtime/nish.h +62 -11
  10. package/runtime/nish.mjs +38 -3
  11. package/runtime/runtime-host.c +178 -0
  12. package/runtime/{runtime_os.c → runtime-os.c} +16 -1
  13. package/runtime/{runtime_parallel.c → runtime-parallel.c} +112 -4
  14. package/runtime/{runtime_wasm.c → runtime-wasm.c} +6 -1
  15. package/runtime/runtime.c +5 -5
  16. package/runtime/shim.mjs +128 -6
  17. package/scripts/bootstrap.sh +29 -29
  18. package/scripts/build.sh +19 -13
  19. package/scripts/changelog-gen.mjs +260 -192
  20. package/scripts/ci-profile.mjs +80 -68
  21. package/scripts/codes-registry.js +25 -13
  22. package/scripts/gen-diagnostic-codes.mjs +124 -85
  23. package/scripts/nish-compiler.sh +2 -0
  24. package/scripts/platform-package.mjs +26 -26
  25. package/scripts/postinstall.mjs +46 -36
  26. package/scripts/size-report.sh +3 -3
  27. package/scripts/smoke.sh +1 -1
  28. package/std/README.md +42 -2
  29. package/std/collections.ts +194 -188
  30. package/std/crypto/base64url.ts +145 -0
  31. package/std/crypto/ct.ts +64 -0
  32. package/std/crypto/hkdf.ts +118 -0
  33. package/std/crypto/hmac.ts +155 -0
  34. package/std/crypto/sha256.ts +444 -0
  35. package/std/crypto/sha512.ts +510 -0
  36. package/std/crypto/x25519.ts +494 -0
  37. package/std/json.ts +136 -136
  38. package/std/map.ts +9 -7
  39. package/std/pair.ts +2 -2
  40. package/std/testing.ts +67 -67
  41. package/std/text.ts +54 -54
  42. package/std/threads.ts +126 -38
@@ -0,0 +1,444 @@
1
+ /**
2
+ * `nish/crypto/sha256` — SHA-256 as FIPS 180-4 §6.2 defines it, in pure Nish.
3
+ *
4
+ * Two shapes, because they answer two different callers. `sha256(data)` is the
5
+ * one-shot form for a message that is already in one buffer. `Sha256` is the
6
+ * streaming form, and it exists for TLS 1.3: a handshake hashes its transcript
7
+ * as the messages arrive and needs the hash of the transcript *so far* at
8
+ * several points, which is what `copy()` is for — take a copy, digest the copy,
9
+ * and keep feeding the original.
10
+ *
11
+ * import { sha256, Sha256 } from "nish/crypto/sha256";
12
+ *
13
+ * const whole: u8[] = sha256(message);
14
+ *
15
+ * const h = new Sha256();
16
+ * h.update(first, 0, toI32(first.length));
17
+ * const soFar: u8[] = h.copy().digest();
18
+ * h.update(second, 0, toI32(second.length));
19
+ * const all: u8[] = h.digest();
20
+ *
21
+ * Input is a window `(buf, off, len)` over a `u8[]`, so a caller hashing one
22
+ * record out of a larger receive buffer does not copy it out first. A digest is
23
+ * always a fresh 32-byte `u8[]`.
24
+ *
25
+ * Every word is a `u32`, whose arithmetic is defined to wrap, so the additions
26
+ * modulo 2^32 that the specification asks for are plain `+` with no masking.
27
+ * Nothing here branches or indexes on the message: the only branches are on
28
+ * lengths and loop counters, which a hash does not keep secret.
29
+ *
30
+ * Written from the specification, not ported from another implementation.
31
+ */
32
+
33
+ /** The length of a digest in bytes (FIPS 180-4 §1, "Message Digest Size"). */
34
+ export const SHA256_SIZE: i32 = 32
35
+
36
+ /** The length of a message block in bytes (FIPS 180-4 §1, "Block Size"). */
37
+ export const SHA256_BLOCK: i32 = 64
38
+
39
+ /**
40
+ * The round constant K_t of FIPS 180-4 §4.2.2: the first 32 bits of the
41
+ * fractional parts of the cube roots of the first sixty-four primes.
42
+ *
43
+ * A function over a `switch` rather than a table, because a module constant
44
+ * cannot be an array ([LANGUAGE.md](../../docs/LANGUAGE.md#module-constants))
45
+ * and building one per hasher would be sixty-four stores for every `new`. LLVM's
46
+ * code generator turns a `switch` whose every arm returns a constant into a
47
+ * read-only lookup table, so in the binary this is one load. `t` is the round
48
+ * number, never message data.
49
+ */
50
+ const sha256RoundConstant = (t: i32): u32 => {
51
+ switch (t) {
52
+ case 0:
53
+ return 0x428a2f98
54
+ case 1:
55
+ return 0x71374491
56
+ case 2:
57
+ return 0xb5c0fbcf
58
+ case 3:
59
+ return 0xe9b5dba5
60
+ case 4:
61
+ return 0x3956c25b
62
+ case 5:
63
+ return 0x59f111f1
64
+ case 6:
65
+ return 0x923f82a4
66
+ case 7:
67
+ return 0xab1c5ed5
68
+ case 8:
69
+ return 0xd807aa98
70
+ case 9:
71
+ return 0x12835b01
72
+ case 10:
73
+ return 0x243185be
74
+ case 11:
75
+ return 0x550c7dc3
76
+ case 12:
77
+ return 0x72be5d74
78
+ case 13:
79
+ return 0x80deb1fe
80
+ case 14:
81
+ return 0x9bdc06a7
82
+ case 15:
83
+ return 0xc19bf174
84
+ case 16:
85
+ return 0xe49b69c1
86
+ case 17:
87
+ return 0xefbe4786
88
+ case 18:
89
+ return 0x0fc19dc6
90
+ case 19:
91
+ return 0x240ca1cc
92
+ case 20:
93
+ return 0x2de92c6f
94
+ case 21:
95
+ return 0x4a7484aa
96
+ case 22:
97
+ return 0x5cb0a9dc
98
+ case 23:
99
+ return 0x76f988da
100
+ case 24:
101
+ return 0x983e5152
102
+ case 25:
103
+ return 0xa831c66d
104
+ case 26:
105
+ return 0xb00327c8
106
+ case 27:
107
+ return 0xbf597fc7
108
+ case 28:
109
+ return 0xc6e00bf3
110
+ case 29:
111
+ return 0xd5a79147
112
+ case 30:
113
+ return 0x06ca6351
114
+ case 31:
115
+ return 0x14292967
116
+ case 32:
117
+ return 0x27b70a85
118
+ case 33:
119
+ return 0x2e1b2138
120
+ case 34:
121
+ return 0x4d2c6dfc
122
+ case 35:
123
+ return 0x53380d13
124
+ case 36:
125
+ return 0x650a7354
126
+ case 37:
127
+ return 0x766a0abb
128
+ case 38:
129
+ return 0x81c2c92e
130
+ case 39:
131
+ return 0x92722c85
132
+ case 40:
133
+ return 0xa2bfe8a1
134
+ case 41:
135
+ return 0xa81a664b
136
+ case 42:
137
+ return 0xc24b8b70
138
+ case 43:
139
+ return 0xc76c51a3
140
+ case 44:
141
+ return 0xd192e819
142
+ case 45:
143
+ return 0xd6990624
144
+ case 46:
145
+ return 0xf40e3585
146
+ case 47:
147
+ return 0x106aa070
148
+ case 48:
149
+ return 0x19a4c116
150
+ case 49:
151
+ return 0x1e376c08
152
+ case 50:
153
+ return 0x2748774c
154
+ case 51:
155
+ return 0x34b0bcb5
156
+ case 52:
157
+ return 0x391c0cb3
158
+ case 53:
159
+ return 0x4ed8aa4a
160
+ case 54:
161
+ return 0x5b9cca4f
162
+ case 55:
163
+ return 0x682e6ff3
164
+ case 56:
165
+ return 0x748f82ee
166
+ case 57:
167
+ return 0x78a5636f
168
+ case 58:
169
+ return 0x84c87814
170
+ case 59:
171
+ return 0x8cc70208
172
+ case 60:
173
+ return 0x90befffa
174
+ case 61:
175
+ return 0xa4506ceb
176
+ case 62:
177
+ return 0xbef9a3f7
178
+ case 63:
179
+ return 0xc67178f2
180
+ // Unreachable: `t` is a round number, 0 to 63. A `default` of its own
181
+ // keeps K_63 inside the table, so the lookup needs no range test first.
182
+ default:
183
+ return 0
184
+ }
185
+ }
186
+
187
+ /** ROTR^n(x) of FIPS 180-4 §3.2: a right rotation of a 32-bit word, `0 < n < 32`. */
188
+ const sha256Rotr = (x: u32, n: u32): u32 => (x >>> n) | (x << (32 - n))
189
+
190
+ /** The big-endian word at `buf[at .. at + 4)`, as FIPS 180-4 §3.1 orders bytes in a word. */
191
+ const sha256LoadWord = (buf: u8[], at: i32): u32 =>
192
+ (toU32(buf[at]) << 24) | (toU32(buf[at + 1]) << 16) | (toU32(buf[at + 2]) << 8) | toU32(buf[at + 3])
193
+
194
+ /**
195
+ * Copies `from[fromAt .. fromAt + n)` to `to[toAt .. toAt + n)`. Every caller
196
+ * has already checked both windows, so the indices here are in range; they
197
+ * are computed from `k` so the loop has one counter.
198
+ */
199
+ const sha256CopyBytes = (from: u8[], fromAt: i32, to: u8[], toAt: i32, n: i32): void => {
200
+ for (let k: i32 = 0; k < n; k += 1) {
201
+ to[toAt + k] = from[fromAt + k]
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Folds the 64-byte block at `src[at .. at + 64)` into the hash value `h`: the
207
+ * computation of FIPS 180-4 §6.2.2, steps 1 to 4, for one block. `w` is the
208
+ * 64-word message schedule, passed in so that it is allocated once per hasher.
209
+ *
210
+ * A function over the arrays rather than a method, so that each array is a
211
+ * plain local whose length the guard below proves once for every loop.
212
+ */
213
+ const sha256Compress = (h: u32[], w: u32[], src: u8[], at: i32): void => {
214
+ // The test is written as the condition the body runs under, not as an
215
+ // early exit, because that is the shape that proves the plain `w[t]` and
216
+ // `h[i]` indices in range and drops their bounds checks. The computed ones —
217
+ // the byte reads in `sha256LoadWord` and `w[t - 2]` and its kin — keep a
218
+ // check each; measured, removing every check with `--unchecked-indexing`
219
+ // moved the 1 MiB time by less than the run-to-run noise.
220
+ if (toI32(h.length) >= 8 && toI32(w.length) >= 64 && at >= 0 && at <= toI32(src.length) - SHA256_BLOCK) {
221
+ // Step 1: the message schedule.
222
+ for (let t: i32 = 0; t < 16; t += 1) {
223
+ w[t] = sha256LoadWord(src, at + 4 * t)
224
+ }
225
+ for (let t: i32 = 16; t < 64; t += 1) {
226
+ const w2: u32 = w[t - 2]
227
+ const w15: u32 = w[t - 15]
228
+ // σ1 and σ0 of §4.1.2, (4.7) and (4.6).
229
+ const sigma1: u32 = sha256Rotr(w2, 17) ^ sha256Rotr(w2, 19) ^ (w2 >>> 10)
230
+ const sigma0: u32 = sha256Rotr(w15, 7) ^ sha256Rotr(w15, 18) ^ (w15 >>> 3)
231
+ w[t] = sigma1 + w[t - 7] + sigma0 + w[t - 16]
232
+ }
233
+ // Step 2: the working variables start from the previous hash value.
234
+ let a: u32 = h[0]
235
+ let b: u32 = h[1]
236
+ let c: u32 = h[2]
237
+ let d: u32 = h[3]
238
+ let e: u32 = h[4]
239
+ let f: u32 = h[5]
240
+ let g: u32 = h[6]
241
+ let hh: u32 = h[7]
242
+ // Step 3: sixty-four rounds.
243
+ for (let t: i32 = 0; t < 64; t += 1) {
244
+ // Σ1 (4.5), Ch (4.2), Σ0 (4.4) and Maj (4.3) of §4.1.2.
245
+ const bigSigma1: u32 = sha256Rotr(e, 6) ^ sha256Rotr(e, 11) ^ sha256Rotr(e, 25)
246
+ const choose: u32 = (e & f) ^ (~e & g)
247
+ const t1: u32 = hh + bigSigma1 + choose + sha256RoundConstant(t) + w[t]
248
+ const bigSigma0: u32 = sha256Rotr(a, 2) ^ sha256Rotr(a, 13) ^ sha256Rotr(a, 22)
249
+ const majority: u32 = (a & b) ^ (a & c) ^ (b & c)
250
+ const t2: u32 = bigSigma0 + majority
251
+ hh = g
252
+ g = f
253
+ f = e
254
+ e = d + t1
255
+ d = c
256
+ c = b
257
+ b = a
258
+ a = t1 + t2
259
+ }
260
+ // Step 4: the next intermediate hash value.
261
+ h[0] += a
262
+ h[1] += b
263
+ h[2] += c
264
+ h[3] += d
265
+ h[4] += e
266
+ h[5] += f
267
+ h[6] += g
268
+ h[7] += hh
269
+ } else {
270
+ panic("Sha256: a block outside its buffer")
271
+ }
272
+ }
273
+
274
+ /**
275
+ * A SHA-256 computation in progress: the hash value H of FIPS 180-4 §6.2 after
276
+ * every whole block absorbed so far, and the bytes of the block not yet whole.
277
+ *
278
+ * `update` may be called any number of times with windows of any length, and
279
+ * the digest does not depend on where the message was split. `digest` pads the
280
+ * message (§5.1.1), runs the last block or two, and ends the computation: the
281
+ * padding is written into this hasher's own state, so a second `digest`, a
282
+ * later `update` or a `copy` would carry the padding as message, and all three
283
+ * panic instead of answering a wrong value. `copy` first when the computation
284
+ * has to go on.
285
+ */
286
+ export class Sha256 {
287
+ /**
288
+ * The message length absorbed so far, in bytes. An `i64` because §5.1.1
289
+ * appends the length in bits as a 64-bit number, and a transcript can pass
290
+ * 2^31 bytes where an `i32` would wrap.
291
+ */
292
+ total: i64 = 0
293
+ /** H_0 .. H_7, the eight words of the intermediate hash value (§6.2.2 step 4). */
294
+ state: u32[]
295
+ /** The pending bytes of the current block; the first `fill` of them are live. */
296
+ block: u8[]
297
+ /**
298
+ * W_0 .. W_63, the message schedule of §6.2.2 step 1. It carries nothing from
299
+ * one block to the next and is a field only so that a long message allocates
300
+ * it once rather than once per block.
301
+ */
302
+ schedule: u32[]
303
+ /** How many bytes of `block` are live, always below `SHA256_BLOCK` between calls. */
304
+ fill: i32 = 0
305
+ finished: boolean = false
306
+
307
+ constructor() {
308
+ // H^(0) of FIPS 180-4 §5.3.3: the first 32 bits of the fractional parts
309
+ // of the square roots of the first eight primes.
310
+ const h: u32[] = new Array<u32>(8)
311
+ h[0] = 0x6a09e667
312
+ h[1] = 0xbb67ae85
313
+ h[2] = 0x3c6ef372
314
+ h[3] = 0xa54ff53a
315
+ h[4] = 0x510e527f
316
+ h[5] = 0x9b05688c
317
+ h[6] = 0x1f83d9ab
318
+ h[7] = 0x5be0cd19
319
+ this.state = h
320
+ this.block = new Array<u8>(SHA256_BLOCK)
321
+ this.schedule = new Array<u32>(64)
322
+ }
323
+
324
+ /**
325
+ * Absorbs `data[off .. off + len)`. A window outside `data` panics rather
326
+ * than hashing a short read, since a digest over the wrong bytes looks
327
+ * exactly like a digest over the right ones.
328
+ */
329
+ update(data: u8[], off: i32, len: i32): void {
330
+ if (this.finished) {
331
+ panic("Sha256: update after digest")
332
+ }
333
+ const size: i32 = toI32(data.length)
334
+ if (off < 0 || len < 0 || off > size || len > size - off) {
335
+ panic("Sha256: the window is outside the buffer")
336
+ }
337
+ this.total += toI64(len)
338
+ let at: i32 = off
339
+ const end: i32 = off + len
340
+ const block: u8[] = this.block
341
+ // Top up a partly filled block first, so that the loop below only ever
342
+ // starts at a block boundary.
343
+ if (this.fill > 0) {
344
+ const room: i32 = SHA256_BLOCK - this.fill
345
+ const take: i32 = len < room ? len : room
346
+ sha256CopyBytes(data, at, block, this.fill, take)
347
+ at += take
348
+ this.fill += take
349
+ if (this.fill < SHA256_BLOCK) {
350
+ return
351
+ }
352
+ sha256Compress(this.state, this.schedule, block, 0)
353
+ this.fill = 0
354
+ }
355
+ // Whole blocks are compressed straight out of the caller's buffer, with no
356
+ // copy through `block`.
357
+ while (end - at >= SHA256_BLOCK) {
358
+ sha256Compress(this.state, this.schedule, data, at)
359
+ at += SHA256_BLOCK
360
+ }
361
+ sha256CopyBytes(data, at, block, 0, end - at)
362
+ this.fill = end - at
363
+ }
364
+
365
+ /**
366
+ * An independent hasher at the same point in the same message: updating or
367
+ * digesting either one leaves the other as it was. This is how a caller
368
+ * takes the hash of a prefix and keeps going.
369
+ */
370
+ copy(): Sha256 {
371
+ if (this.finished) {
372
+ panic("Sha256: copy after digest")
373
+ }
374
+ const twin: Sha256 = new Sha256()
375
+ const fromState: u32[] = this.state
376
+ const toState: u32[] = twin.state
377
+ // Both are always eight words; the test is what proves the indices below
378
+ // in range, as it does in `sha256Compress`.
379
+ if (toI32(fromState.length) >= 8 && toI32(toState.length) >= 8) {
380
+ for (let i: i32 = 0; i < 8; i += 1) {
381
+ toState[i] = fromState[i]
382
+ }
383
+ } else {
384
+ panic("Sha256: a hash value that is not eight words")
385
+ }
386
+ sha256CopyBytes(this.block, 0, twin.block, 0, this.fill)
387
+ twin.fill = this.fill
388
+ twin.total = this.total
389
+ return twin
390
+ }
391
+
392
+ /**
393
+ * The 32-byte message digest (FIPS 180-4 §6.2.2, the final H^(N) in
394
+ * big-endian order), in a fresh array. Ends the computation.
395
+ */
396
+ digest(): u8[] {
397
+ if (this.finished) {
398
+ panic("Sha256: digest called twice")
399
+ }
400
+ this.finished = true
401
+ // §5.1.1: a single 1 bit, then zeros up to 56 bytes into a block, then the
402
+ // message length in bits as a big-endian 64-bit number. When fewer than
403
+ // eight bytes are left after the 1 bit, the zeros run into a second block.
404
+ // `block` is zero past `fill` only in a fresh hasher, so every zero is
405
+ // written rather than assumed.
406
+ const block: u8[] = this.block
407
+ const blockEnd: i32 = toI32(block.length)
408
+ const fill: i32 = this.fill
409
+ block[fill] = 0x80
410
+ for (let i: i32 = fill + 1; i < blockEnd; i += 1) {
411
+ block[i] = 0
412
+ }
413
+ if (fill >= SHA256_BLOCK - 8) {
414
+ sha256Compress(this.state, this.schedule, block, 0)
415
+ for (let i: i32 = 0; i < blockEnd; i += 1) {
416
+ block[i] = 0
417
+ }
418
+ }
419
+ const bits: u64 = toU64(this.total) << 3
420
+ for (let i: i32 = 0; i < 8; i += 1) {
421
+ block[SHA256_BLOCK - 1 - i] = toU8(bits >>> toU64(8 * i))
422
+ }
423
+ sha256Compress(this.state, this.schedule, block, 0)
424
+ const out: u8[] = new Array<u8>(SHA256_SIZE)
425
+ const state: u32[] = this.state
426
+ const words: i32 = toI32(state.length)
427
+ for (let i: i32 = 0; i < words; i += 1) {
428
+ const word: u32 = state[i]
429
+ out[4 * i] = toU8(word >>> 24)
430
+ out[4 * i + 1] = toU8(word >>> 16)
431
+ out[4 * i + 2] = toU8(word >>> 8)
432
+ out[4 * i + 3] = toU8(word)
433
+ }
434
+ return out
435
+ }
436
+ }
437
+
438
+ /** The SHA-256 digest of all of `data`, as a fresh 32-byte array. */
439
+ export const sha256 = (data: u8[]): u8[] => {
440
+ const hasher: Sha256 = new Sha256()
441
+ const from: i32 = 0
442
+ hasher.update(data, from, toI32(data.length))
443
+ return hasher.digest()
444
+ }