@amritk/nish-x86_64-linux 0.17.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 (61) 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 +231 -20
  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 +203 -0
  14. package/runtime/runtime-wasm.c +16 -3
  15. package/runtime/runtime.c +89 -23
  16. package/runtime/shim.mjs +71 -1
  17. package/scripts/build.sh +225 -20
  18. package/std/README.md +86 -55
  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 +1376 -42
  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 +1412 -70
  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 +231 -45
  42. package/std/net/http3.ts +187 -28
  43. package/std/net/prefork.ts +412 -0
  44. package/std/net/quic-conn-params.ts +86 -16
  45. package/std/net/quic-listener.ts +576 -219
  46. package/std/net/quic-packet.ts +443 -58
  47. package/std/net/quic-recovery.ts +361 -30
  48. package/std/net/quic-stream.ts +434 -28
  49. package/std/net/quic.ts +922 -206
  50. package/std/net/router.ts +676 -0
  51. package/std/net/tls/codec.ts +426 -27
  52. package/std/net/tls/record-server.ts +478 -132
  53. package/std/net/tls/record.ts +133 -42
  54. package/std/net/tls/schedule.ts +464 -55
  55. package/std/net/tls/ticket.ts +610 -0
  56. package/std/net/tls-tcp.ts +204 -57
  57. package/std/net/tls.ts +1132 -305
  58. package/std/net/url.ts +571 -0
  59. package/std/random.ts +94 -0
  60. package/std/text.ts +77 -0
  61. package/std/threads.ts +438 -14
package/std/crypto/aes.ts CHANGED
@@ -57,6 +57,31 @@
57
57
  * the loops over blocks — is the discipline above and not a verified
58
58
  * property.
59
59
  *
60
+ * **The seams.** Four private functions do nearly all of the cipher's work,
61
+ * each called from exactly one place: `aesScheduleInto` (the key schedule),
62
+ * `aesBlockInto` (one block), `aesCtrInto` (GCTR over a run of bytes) and
63
+ * `ghashBlocksInto` (GHASH over a run of bytes). Each takes the arrays its
64
+ * caller passes, writes into them and allocates nothing the caller needs
65
+ * back, and its one caller checks every precondition it names first. That is
66
+ * what lets the compiler, natively, put each of those four calls behind a
67
+ * branch on the runtime's `nish_aes_hw` (`src/kernels.ts`, lowered in
68
+ * `src/emit.ts`): on a CPU with AES-NI and PCLMULQDQ, or the ARMv8 AES and
69
+ * PMULL instructions, the call goes to a kernel in `runtime/runtime-aes.c`
70
+ * that does no bounds checks of its own; on any other CPU, under
71
+ * `NISH_AES=soft`, on wasm32 and under Node, the body here runs (WP38 S5,
72
+ * `docs/wp38-simd.md`). The bodies are the bitsliced code, and keep their
73
+ * own defensive checks. The kernel keeps its own layout in a key's
74
+ * `roundKeys`, which a process only ever reads under the one answer
75
+ * `nish_aes_hw` gives it.
76
+ *
77
+ * What runs outside the seams, and so on the bitsliced code whichever way
78
+ * they go: `ghashLengths`, the last GHASH block of every tag, and so the end
79
+ * of J0's derivation for an IV that is not 12 bytes, multiplies with
80
+ * `ghashMultiply` on the state a seam left; and `aesGcmTag` folds E(K, J0)
81
+ * into it. A kernel therefore leaves the GHASH state in this module's own
82
+ * form, two big-endian words, and `tests/link/crypto_aes_agree` holds the two
83
+ * paths to the same bytes on the IVs that take that road.
84
+ *
60
85
  * **What is refused.** A key of any length but 16 or 32 bytes (AES-192 is out
61
86
  * of scope), a block or a sample that is not 16 bytes, an empty IV (SP 800-38D
62
87
  * §5.2.1.1 asks for at least one bit), a sealed input shorter than its tag, a
@@ -70,6 +95,8 @@
70
95
  * (`docs/wp26-stdlib.md` §3e).
71
96
  */
72
97
 
98
+ import { wipe } from "nish:secret"
99
+
73
100
  /** The AES block, in bytes. */
74
101
  export const AES_BLOCK: i32 = 16
75
102
  /** GCM's tag, in bytes: this module makes and accepts only the full 128 bits. */
@@ -86,27 +113,53 @@ const AES_BATCH: i32 = 64
86
113
  * because the language has no private ones, and a key built or edited by hand
87
114
  * is not trusted: one whose shape is not what `aesKey` makes answers `null`
88
115
  * everywhere (`aesKeyUsable`), and one whose H is zero opens nothing
89
- * (`aesGcmOpen`).
116
+ * (`aesGcmOpen`). On the runtime kernels' path GHASH takes H and its powers
117
+ * from `roundKeys`, while `ghashLengths` and a non-12-byte IV's J0 take it
118
+ * from `h`; `aesKey` writes both from one schedule, so they agree. A key
119
+ * edited so that they disagree is outside the contract, and what it seals
120
+ * then depends on which path the process runs.
121
+ *
122
+ * Both are secret. A holder done with a key hands it to `aesKeyWipe`, whose
123
+ * volatile stores zero all of `roundKeys` (the kernels' tail of H's powers
124
+ * included) and `h`, and stand however dead the key is about to be.
90
125
  */
91
126
  export class AesKey {
92
127
  /** Nr: 10 for AES-128, 14 for AES-256. */
93
128
  rounds: i32 = 0
94
129
  /**
95
130
  * Round key `i` in the bitsliced layout, replicated into all four block
96
- * lanes, as eight words from `8 * i`: `8 * (rounds + 1)` words in all.
131
+ * lanes, as eight words from `8 * i`: `8 * (rounds + 1)` words in all. In a
132
+ * native process that runs the runtime's AES kernels (the seams, above),
133
+ * the same words hold the kernels' layout instead, `runtime/runtime-aes.c`'s.
97
134
  */
98
135
  roundKeys: u64[]
99
- /** H as a big-endian 128-bit number, high half. */
100
- hHi: u64 = 0
101
- /** H, low half. */
102
- hLo: u64 = 0
136
+ /**
137
+ * H as a big-endian 128-bit number, two words: `h[0]` the high half and
138
+ * `h[1]` the low. An array rather than two scalar fields, so that
139
+ * `aesKeyWipe`'s volatile wipe reaches it.
140
+ */
141
+ h: u64[]
103
142
 
104
143
  constructor(rounds: i32, roundKeys: u64[]) {
105
144
  this.rounds = rounds
106
145
  this.roundKeys = roundKeys
146
+ this.h = new Array<u64>(2)
107
147
  }
108
148
  }
109
149
 
150
+ /**
151
+ * Zeroes everything secret `key` holds — every word of `roundKeys`, the
152
+ * runtime kernels' powers of H after the schedule included, and `h` — with
153
+ * `wipe`'s volatile stores, which no optimiser removes however dead the key
154
+ * is about to be. `rounds` is not secret and stays. A wiped key seals under
155
+ * an all-zero schedule and, its H being zero, opens nothing (`aesGcmOpen`);
156
+ * `aesKeyInto` installs a new key into it.
157
+ */
158
+ export const aesKeyWipe = (key: AesKey): void => {
159
+ wipe(key.roundKeys)
160
+ wipe(key.h)
161
+ }
162
+
110
163
  /** A 16-bit pattern repeated into each of a word's four 16-bit lanes. */
111
164
  const aesLanes = (pattern: u64): u64 => {
112
165
  const two: u64 = pattern | (pattern << toU64(16))
@@ -439,17 +492,19 @@ export const aesBitslicedRound = (q: u64[], rk: u64[], at: i32): void => {
439
492
  aesAddRoundKey(q, rk, at)
440
493
  }
441
494
 
442
- /** The cipher (FIPS 197 §5.1) on the four blocks of `buf[0 .. 64)`, in place. `q` is scratch. */
443
- const aesEncryptBatch = (key: AesKey, buf: u8[], q: u64[]): void => {
444
- const rk: u64[] = key.roundKeys
495
+ /**
496
+ * The cipher (FIPS 197 §5.1) on the four blocks of `buf[0 .. 64)`, in place,
497
+ * under the bitsliced round keys `rk`. `q` is scratch.
498
+ */
499
+ const aesEncryptBatch = (rk: u64[], rounds: i32, buf: u8[], q: u64[]): void => {
445
500
  aesPack(q, buf)
446
501
  aesAddRoundKey(q, rk, 0)
447
- for (let round: i32 = 1; round < key.rounds; round++) {
502
+ for (let round: i32 = 1; round < rounds; round++) {
448
503
  aesBitslicedRound(q, rk, 8 * round)
449
504
  }
450
505
  aesSbox(q)
451
506
  aesShiftRows(q)
452
- aesAddRoundKey(q, rk, 8 * key.rounds)
507
+ aesAddRoundKey(q, rk, 8 * rounds)
453
508
  aesUnpack(buf, q)
454
509
  }
455
510
 
@@ -496,12 +551,17 @@ const aesStore32 = (out: u8[], at: i32, w: u32): void => {
496
551
  }
497
552
 
498
553
  /**
499
- * KeyExpansion (FIPS 197 §5.2) for Nk = 4 or 8, then every round key
500
- * bitsliced and replicated into the four block lanes. `w` holds the words
501
- * big-endian, byte 0 of the key in the high byte of `w[0]`, as the standard
502
- * writes them. Branches only on the word number.
554
+ * The key-schedule seam: KeyExpansion (FIPS 197 §5.2) for Nk = 4 or 8 into
555
+ * `rk`, every round key bitsliced and replicated into the four block lanes.
556
+ * `w` holds the words big-endian, byte 0 of the key in the high byte of
557
+ * `w[0]`, as the standard writes them. Branches only on the word number.
558
+ *
559
+ * Its one caller, `aesKeyInto`, has checked that `key` is 16 or 32 bytes,
560
+ * that `rounds` is 10 or 14 to match, and that `rk` holds the
561
+ * `8 * (rounds + 1)` words this writes; the compiler may replace that call
562
+ * with a runtime kernel, which writes its own layout into the same words.
503
563
  */
504
- const aesExpand = (key: u8[], rounds: i32): u64[] => {
564
+ const aesScheduleInto = (key: u8[], rounds: i32, rk: u64[]): void => {
505
565
  const nk: i32 = rounds - 6
506
566
  const total: i32 = (rounds + 1) * 4
507
567
  const w: u32[] = new Array<u32>(total)
@@ -528,8 +588,8 @@ const aesExpand = (key: u8[], rounds: i32): u64[] => {
528
588
  }
529
589
  }
530
590
  // Each round key, written into all four blocks of a batch and packed.
531
- const rk: u64[] = new Array<u64>((rounds + 1) * 8)
532
591
  const buf: u8[] = new Array<u8>(AES_BATCH)
592
+ const rkLength: i32 = toI32(rk.length)
533
593
  for (let round: i32 = 0; round <= rounds; round++) {
534
594
  for (let k: i32 = 0; k < 4; k++) {
535
595
  for (let j: i32 = 0; j < 4; j++) {
@@ -538,10 +598,16 @@ const aesExpand = (key: u8[], rounds: i32): u64[] => {
538
598
  }
539
599
  aesPack(q, buf)
540
600
  for (let b: i32 = 0; b < 8; b++) {
541
- rk[8 * round + b] = q[b]
601
+ const at: i32 = 8 * round + b
602
+ if (at < rkLength) {
603
+ rk[at] = q[b]
604
+ }
542
605
  }
543
606
  }
544
- return rk
607
+ // The key's words, the last round key's bits and its bytes, all secret.
608
+ wipe(w)
609
+ wipe(q)
610
+ wipe(buf)
545
611
  }
546
612
 
547
613
  /**
@@ -583,27 +649,51 @@ const aesCopyPrefix = (dst: u8[], src: u8[], count: i32): void => {
583
649
  }
584
650
 
585
651
  /**
586
- * Whether `key` has the shape `aesKey` makes: 10 or 14 rounds and the
587
- * `(rounds + 1) * 8` round-key words the cipher reads. A key of another shape
588
- * could only have been built or edited by hand, and would index past its round
589
- * keys and panic, so every entry point answers `null` for it instead. The
590
- * branch is on the shape, which is not secret.
652
+ * Whether `key` has the shape `aesKey` makes: 10 or 14 rounds, the
653
+ * `(rounds + 1) * 8` round-key words the cipher reads and a two-word H. A key
654
+ * of another shape could only have been built or edited by hand, and would
655
+ * index past its arrays and panic, so every entry point answers `null` for it
656
+ * instead. The branch is on the shape, which is not secret.
591
657
  */
592
658
  const aesKeyUsable = (key: AesKey): boolean => {
593
659
  const rounds: i32 = key.rounds
594
660
  const words: i32 = (rounds + 1) * 8
595
- return (rounds === 10 || rounds === 14) && toI32(key.roundKeys.length) === words
661
+ return (
662
+ (rounds === 10 || rounds === 14) && toI32(key.roundKeys.length) === words && toI32(key.h.length) === 2
663
+ )
596
664
  }
597
665
 
598
666
  /**
599
- * The first 16 bytes of `block` encrypted, in the first 16 bytes of a fresh
600
- * batch; the rest of the batch is the cipher of zeros and is not read.
667
+ * The single-block seam: `dst[0 .. 16) = E(K, src[0 .. 16))` under the round
668
+ * keys `rk`. The block goes through the first lane of a batch whose other
669
+ * three are zeros and not read, and the batch and the bitsliced state are
670
+ * wiped before they are released: the block may be H or E(K, J0).
671
+ *
672
+ * Its one caller, `aesEncryptOne`, has checked that `rk` is a schedule of
673
+ * `rounds` rounds and that `src` and `dst` each hold 16 bytes; the compiler
674
+ * may replace that call with a runtime kernel.
601
675
  */
602
- const aesEncryptOne = (key: AesKey, block: u8[]): u8[] => {
676
+ const aesBlockInto = (rk: u64[], rounds: i32, src: u8[], dst: u8[]): void => {
603
677
  const buf: u8[] = new Array<u8>(AES_BATCH)
604
- aesCopyPrefix(buf, block, AES_BLOCK)
605
- aesEncryptBatch(key, buf, new Array<u64>(8))
606
- return buf
678
+ const q: u64[] = new Array<u64>(8)
679
+ aesCopyPrefix(buf, src, AES_BLOCK)
680
+ aesEncryptBatch(rk, rounds, buf, q)
681
+ aesCopyPrefix(dst, buf, AES_BLOCK)
682
+ wipe(buf)
683
+ wipe(q)
684
+ }
685
+
686
+ /**
687
+ * The first 16 bytes of `src` encrypted into the first 16 of `dst`: the one
688
+ * call of `aesBlockInto`, so the checks its contract needs are made here. A
689
+ * key of the wrong shape, or a block shorter than 16 bytes, leaves `dst` as it
690
+ * was; no caller passes one.
691
+ */
692
+ const aesEncryptOne = (key: AesKey, src: u8[], dst: u8[]): void => {
693
+ if (!aesKeyUsable(key) || toI32(src.length) < AES_BLOCK || toI32(dst.length) < AES_BLOCK) {
694
+ return
695
+ }
696
+ aesBlockInto(key.roundKeys, key.rounds, src, dst)
607
697
  }
608
698
 
609
699
  /**
@@ -617,10 +707,10 @@ export const aesKey = (key: u8[]): AesKey | null => {
617
707
  return null
618
708
  }
619
709
  const rounds: i32 = length === 16 ? 10 : 14
620
- const expanded: AesKey = new AesKey(rounds, aesExpand(key, rounds))
621
- const buf: u8[] = aesEncryptOne(expanded, [])
622
- expanded.hHi = aesLoad64(buf, 0, 8)
623
- expanded.hLo = aesLoad64(buf, 8, 16)
710
+ const expanded: AesKey = new AesKey(rounds, new Array<u64>((rounds + 1) * 8))
711
+ if (!aesKeyInto(key, expanded)) {
712
+ return null
713
+ }
624
714
  return expanded
625
715
  }
626
716
 
@@ -630,15 +720,23 @@ export const aesKey = (key: u8[]): AesKey | null => {
630
720
  * 0^128) for GCM, written into `out`, whose `roundKeys` must already be the
631
721
  * `8 * (rounds + 1)` words that key length needs (88 or 120). It answers
632
722
  * whether it did; a key of another length, or a schedule array of the wrong
633
- * size for it, answers `false` and leaves `out` as it was.
723
+ * size for it (or an `h` that is not two words), answers `false` and leaves
724
+ * `out` as it was.
634
725
  *
635
726
  * `aesKey` makes a fresh `AesKey` and stores its schedule into it, which no
636
727
  * arena scope may be put around; this stores numbers only, so a caller that
637
728
  * keeps one `AesKey` per connection installs every key inside a `using a =
638
729
  * arena()` block and leaves the arena where it was. The schedule and H are
639
- * secret: the caller wipes `out` when it is done with the key. The working
640
- * copy of the schedule is zeroed before it is released, by stores that stand
641
- * no better than `aesKey`'s own temporaries do.
730
+ * secret: the caller hands `out` to `aesKeyWipe` when it is done with the
731
+ * key. The schedule is written straight into `out.roundKeys`, and the
732
+ * working copies the bitsliced schedule and the block H passes through are
733
+ * wiped with `wipe`'s volatile stores before they are released. On a CPU
734
+ * that runs the runtime's kernels (the seams, in the module docs) those
735
+ * copies are the kernels' own, in `runtime/runtime-aes.c`, and what they
736
+ * leave in vector registers no wipe here reaches.
737
+ *
738
+ * `aesKey` makes its key through this, so this is the one call of
739
+ * `aesScheduleInto`, and the checks its contract needs are made here.
642
740
  */
643
741
  export const aesKeyInto = (key: u8[], out: AesKey): boolean => {
644
742
  const length: i32 = toI32(key.length)
@@ -647,22 +745,17 @@ export const aesKeyInto = (key: u8[], out: AesKey): boolean => {
647
745
  }
648
746
  const rounds: i32 = length === 16 ? 10 : 14
649
747
  const roundKeys: u64[] = out.roundKeys
650
- const words: i32 = (rounds + 1) * 8
651
- if (toI32(roundKeys.length) !== words) {
748
+ const hash: u64[] = out.h
749
+ if (toI32(roundKeys.length) !== (rounds + 1) * 8 || toI32(hash.length) !== 2) {
652
750
  return false
653
751
  }
654
- const expanded: u64[] = aesExpand(key, rounds)
655
- for (let i: i32 = 0; i < toI32(roundKeys.length) && i < toI32(expanded.length); i++) {
656
- roundKeys[i] = expanded[i]
657
- expanded[i] = 0
658
- }
752
+ aesScheduleInto(key, rounds, roundKeys)
659
753
  out.rounds = rounds
660
- const buf: u8[] = aesEncryptOne(out, [])
661
- out.hHi = aesLoad64(buf, 0, 8)
662
- out.hLo = aesLoad64(buf, 8, 16)
663
- for (let i: i32 = 0; i < toI32(buf.length); i++) {
664
- buf[i] = 0
665
- }
754
+ const h: u8[] = new Array<u8>(AES_BLOCK)
755
+ aesEncryptOne(out, new Array<u8>(AES_BLOCK), h)
756
+ hash[0] = aesLoad64(h, 0, 8)
757
+ hash[1] = aesLoad64(h, 8, 16)
758
+ wipe(h)
666
759
  return true
667
760
  }
668
761
 
@@ -672,7 +765,7 @@ export const aesEncryptBlock = (key: AesKey, block: u8[]): u8[] | null => {
672
765
  return null
673
766
  }
674
767
  const out: u8[] = new Array<u8>(AES_BLOCK)
675
- aesCopyPrefix(out, aesEncryptOne(key, block), AES_BLOCK)
768
+ aesEncryptOne(key, block, out)
676
769
  return out
677
770
  }
678
771
 
@@ -685,8 +778,10 @@ export const aesHeaderMask = (key: AesKey, sample: u8[]): u8[] | null => {
685
778
  if (!aesKeyUsable(key) || toI32(sample.length) !== AES_BLOCK) {
686
779
  return null
687
780
  }
781
+ const block: u8[] = new Array<u8>(AES_BLOCK)
782
+ aesEncryptOne(key, sample, block)
688
783
  const mask: u8[] = new Array<u8>(5)
689
- aesCopyPrefix(mask, aesEncryptOne(key, sample), 5)
784
+ aesCopyPrefix(mask, block, 5)
690
785
  return mask
691
786
  }
692
787
 
@@ -798,10 +893,17 @@ export const ghashMultiply = (y: u64[], hHi: u64, hLo: u64): void => {
798
893
  }
799
894
 
800
895
  /**
801
- * Absorbs `data[0 .. length)` into the GHASH state `y`, 16 bytes at a time,
802
- * the last block padded with zeros (SP 800-38D §6.4, §7.1 step 5).
896
+ * The GHASH seam: absorbs `data[0 .. length)` into the GHASH state `y` under
897
+ * H = `hHi:hLo`, 16 bytes at a time, the last block padded with zeros
898
+ * (SP 800-38D §6.4, §7.1 step 5). `_rk` is the key's round keys, which this
899
+ * body does not read: a runtime kernel may keep powers of H after its own
900
+ * schedule there.
901
+ *
902
+ * Its one caller, `ghashUpdate`, has checked that `y` holds two words, that
903
+ * `0 <= length <= data.length` and that `_rk` is a whole schedule; the
904
+ * compiler may replace that call with a runtime kernel.
803
905
  */
804
- const ghashUpdate = (y: u64[], key: AesKey, data: u8[], length: i32): void => {
906
+ const ghashBlocksInto = (y: u64[], hHi: u64, hLo: u64, _rk: u64[], data: u8[], length: i32): void => {
805
907
  if (toI32(y.length) < 2) {
806
908
  return
807
909
  }
@@ -812,21 +914,35 @@ const ghashUpdate = (y: u64[], key: AesKey, data: u8[], length: i32): void => {
812
914
  const at: i32 = block * 16
813
915
  y[0] = y[0] ^ aesLoad64(data, at, length)
814
916
  y[1] = y[1] ^ aesLoad64(data, at + 8, length)
815
- ghashMultiply(y, key.hHi, key.hLo)
917
+ ghashMultiply(y, hHi, hLo)
918
+ }
919
+ }
920
+
921
+ /**
922
+ * Absorbs `data[0 .. length)` into the GHASH state `y` under `key`'s H: the
923
+ * one call of `ghashBlocksInto`, so the checks its contract needs are made
924
+ * here. A state, length or key that fails them leaves `y` as it was; no
925
+ * caller passes one.
926
+ */
927
+ const ghashUpdate = (y: u64[], key: AesKey, data: u8[], length: i32): void => {
928
+ if (toI32(y.length) < 2 || length < 0 || length > toI32(data.length) || !aesKeyUsable(key)) {
929
+ return
816
930
  }
931
+ ghashBlocksInto(y, key.h[0], key.h[1], key.roundKeys, data, length)
817
932
  }
818
933
 
819
934
  /** The last GHASH block: the AAD's and the ciphertext's lengths in bits, 64 bits each. */
820
935
  const ghashLengths = (y: u64[], key: AesKey, aadLength: i32, textLength: i32): void => {
821
936
  y[0] = y[0] ^ (toU64(aadLength) << toU64(3))
822
937
  y[1] = y[1] ^ (toU64(textLength) << toU64(3))
823
- ghashMultiply(y, key.hHi, key.hLo)
938
+ ghashMultiply(y, key.h[0], key.h[1])
824
939
  }
825
940
 
826
941
  /**
827
942
  * The pre-counter block J0 (SP 800-38D §7.1 step 2): a 12-byte IV followed by
828
943
  * the 32-bit counter 1, or for any other length GHASH of the IV padded to a
829
- * block and followed by its length in bits.
944
+ * block and followed by its length in bits. That GHASH, with the IV, says
945
+ * something of H, so the GHASH state is wiped here and the caller wipes J0.
830
946
  */
831
947
  const aesGcmJ0 = (key: AesKey, iv: u8[]): u8[] => {
832
948
  const length: i32 = toI32(iv.length)
@@ -841,6 +957,8 @@ const aesGcmJ0 = (key: AesKey, iv: u8[]): u8[] => {
841
957
  ghashLengths(y, key, 0, length)
842
958
  aesStore64(j0, 0, y[0])
843
959
  aesStore64(j0, 8, y[1])
960
+ // GHASH of the IV under H: with the IV, it says something of H.
961
+ wipe(y)
844
962
  return j0
845
963
  }
846
964
 
@@ -856,12 +974,20 @@ const aesGcmLengthFits = (length: i32): boolean =>
856
974
  (toI64(length) + toI64(15)) >> toI64(4) <= (toI64(1) << toI64(32)) - toI64(2)
857
975
 
858
976
  /**
859
- * GCTR (SP 800-38D §6.5) from inc32(J0): `dst[i] = src[i] ⊕ keystream[i]` for
860
- * `i` below `length`, four blocks per pass of the cipher. The counter is the
861
- * last four bytes of J0 as a big-endian `u32` and wraps modulo 2^32, as
862
- * inc32 does, while the first twelve bytes stay as they are.
977
+ * The GCTR seam (SP 800-38D §6.5) from inc32(J0): `dst[i] = src[i] ⊕
978
+ * keystream[i]` for `i` below `length`, four blocks per pass of the cipher,
979
+ * under the round keys `rk`. The counter is the last four bytes of J0 as a
980
+ * big-endian `u32` and wraps modulo 2^32, as inc32 does, while the first
981
+ * twelve bytes stay as they are. `dst` may be `src`: each byte is read before
982
+ * it is written. The batch of keystream and the bitsliced state are wiped
983
+ * before it returns.
984
+ *
985
+ * Its one caller, `aesGcmCtr`, has checked that `rk` is a schedule of
986
+ * `rounds` rounds, that `j0` is 16 bytes and that `0 <= length` and `length`
987
+ * is within both `src` and `dst`; the compiler may replace that call with a
988
+ * runtime kernel.
863
989
  */
864
- const aesGcmCtr = (key: AesKey, j0: u8[], src: u8[], dst: u8[], length: i32): void => {
990
+ const aesCtrInto = (rk: u64[], rounds: i32, j0: u8[], src: u8[], dst: u8[], length: i32): void => {
865
991
  if (toI32(j0.length) < 16) {
866
992
  return
867
993
  }
@@ -870,6 +996,7 @@ const aesGcmCtr = (key: AesKey, j0: u8[], src: u8[], dst: u8[], length: i32): vo
870
996
  const q: u64[] = new Array<u64>(8)
871
997
  const srcLength: i32 = toI32(src.length)
872
998
  const dstLength: i32 = toI32(dst.length)
999
+ const bufLength: i32 = toI32(buf.length)
873
1000
  // Counted in batches, like `ghashUpdate`'s blocks, so `base` never passes `length`.
874
1001
  const batches: i32 = (length >> 6) + ((length & 63) !== 0 ? 1 : 0)
875
1002
  for (let batch: i32 = 0; batch < batches; batch++) {
@@ -880,29 +1007,59 @@ const aesGcmCtr = (key: AesKey, j0: u8[], src: u8[], dst: u8[], length: i32): vo
880
1007
  }
881
1008
  aesStore32(buf, 16 * k + 12, counter + toU32((base >> 4) + k + 1))
882
1009
  }
883
- aesEncryptBatch(key, buf, q)
884
- for (let j: i32 = 0; j < AES_BATCH && j < toI32(buf.length); j++) {
885
- const at: i32 = base + j
886
- if (at >= 0 && at < length && at < srcLength && at < dstLength) {
1010
+ aesEncryptBatch(rk, rounds, buf, q)
1011
+ // Every bound is in the loop's condition, where LLVM folds them into one
1012
+ // trip count and vectorises the XOR; the same tests inside the body, once
1013
+ // `aesGcmCtr`'s checks were inlined above them, became three compares a
1014
+ // byte and cost seal and open 6%. The body's test is only for the bounds
1015
+ // prover, and is always true.
1016
+ for (let at: i32 = base; at < length && at < srcLength && at < dstLength && at - base < bufLength; at++) {
1017
+ const j: i32 = at - base
1018
+ if (at >= 0 && j >= 0 && j < bufLength) {
887
1019
  dst[at] = src[at] ^ buf[j]
888
1020
  }
889
1021
  }
890
1022
  }
1023
+ // The last batch's keystream and its bitsliced state, both secret.
1024
+ wipe(buf)
1025
+ wipe(q)
1026
+ }
1027
+
1028
+ /**
1029
+ * GCTR from inc32(J0) over `src[0 .. length)` into `dst` under `key`: the one
1030
+ * call of `aesCtrInto`, so the checks its contract needs are made here. A
1031
+ * key, J0 or length that fails them leaves `dst` as it was; no caller passes
1032
+ * one.
1033
+ */
1034
+ const aesGcmCtr = (key: AesKey, j0: u8[], src: u8[], dst: u8[], length: i32): void => {
1035
+ if (
1036
+ !aesKeyUsable(key) ||
1037
+ toI32(j0.length) !== AES_BLOCK ||
1038
+ length < 0 ||
1039
+ length > toI32(src.length) ||
1040
+ length > toI32(dst.length)
1041
+ ) {
1042
+ return
1043
+ }
1044
+ aesCtrInto(key.roundKeys, key.rounds, j0, src, dst, length)
891
1045
  }
892
1046
 
893
1047
  /**
894
1048
  * E(K, J0) ⊕ S, GCM's tag (SP 800-38D §7.1 step 6), into `y` (zero, two
895
1049
  * words) as two big-endian words: S is the GHASH of the AAD and the
896
- * ciphertext `text[0 .. textLength)`.
1050
+ * ciphertext `text[0 .. textLength)`. E(K, J0)'s block is wiped once it is
1051
+ * folded in.
897
1052
  */
898
1053
  const aesGcmTag = (y: u64[], key: AesKey, j0: u8[], aad: u8[], text: u8[], textLength: i32): void => {
899
1054
  const aadLength: i32 = toI32(aad.length)
900
1055
  ghashUpdate(y, key, aad, aadLength)
901
1056
  ghashUpdate(y, key, text, textLength)
902
1057
  ghashLengths(y, key, aadLength, textLength)
903
- const mask: u8[] = aesEncryptOne(key, j0)
1058
+ const mask: u8[] = new Array<u8>(AES_BLOCK)
1059
+ aesEncryptOne(key, j0, mask)
904
1060
  y[0] = y[0] ^ aesLoad64(mask, 0, 16)
905
1061
  y[1] = y[1] ^ aesLoad64(mask, 8, 16)
1062
+ wipe(mask)
906
1063
  }
907
1064
 
908
1065
  /**
@@ -937,6 +1094,7 @@ export const aesGcmSeal = (key: AesKey, iv: u8[], aad: u8[], plaintext: u8[]): u
937
1094
  aesGcmTag(tag, key, j0, aad, out, length)
938
1095
  aesStore64(out, length, tag[0])
939
1096
  aesStore64(out, length + 8, tag[1])
1097
+ wipe(j0)
940
1098
  return out
941
1099
  }
942
1100
 
@@ -972,11 +1130,15 @@ export const aesGcmOpen = (key: AesKey, iv: u8[], aad: u8[], sealed: u8[]): u8[]
972
1130
  // authenticate any ciphertext and AAD. Only a key built or edited by hand
973
1131
  // has it (for one from `aesKey` the chance is 2^-128), and such a key opens
974
1132
  // nothing. H is secret, so this is folded into the mask, not branched on.
975
- const hashKeySet: u64 = ~ctEq(key.hHi | key.hLo, toU64(0))
976
- if ((same & hashKeySet) === toU64(0)) {
977
- return null
1133
+ const hashKeySet: u64 = ~ctEq(key.h[0] | key.h[1], toU64(0))
1134
+ wipe(tag)
1135
+ let out: u8[] | null = null
1136
+ if ((same & hashKeySet) !== toU64(0)) {
1137
+ const opened: u8[] = new Array<u8>(length)
1138
+ aesGcmCtr(key, j0, sealed, opened, length)
1139
+ out = opened
978
1140
  }
979
- const out: u8[] = new Array<u8>(length)
980
- aesGcmCtr(key, j0, sealed, out, length)
1141
+ // One wipe both paths reach, so the -O2 count sees it go missing.
1142
+ wipe(j0)
981
1143
  return out
982
1144
  }
@@ -22,6 +22,14 @@
22
22
  * array so that the words stay in registers, and, as RFC 8439 §3 suggests,
23
23
  * the state is built once per message and only its counter moves per block.
24
24
  *
25
+ * **What is wiped.** Every array a call fills from the key — the ChaCha20
26
+ * state, the keystream block, the Poly1305 one-time key, its `r` and the
27
+ * accumulator, and on open the tag computed for comparison — is zeroed with
28
+ * `wipe`'s volatile stores before the call returns, on the refusal path too,
29
+ * so that no optimiser can drop the zeroing of an array about to die. The
30
+ * caller's own key, and the words the rounds keep in registers, are not this
31
+ * module's to reach.
32
+ *
25
33
  * **Poly1305** keeps its accumulator and its key `r` as five limbs of 26 bits
26
34
  * each in `u64`, so limb `i` holds bits `26i` to `26i + 25` of a 130-bit
27
35
  * number. RFC 8439 §3 asks for constant-time arithmetic without saying how;
@@ -54,6 +62,8 @@
54
62
  * namespace (`docs/wp26-stdlib.md` §3e).
55
63
  */
56
64
 
65
+ import { wipe } from "nish:secret"
66
+
57
67
  /** The length of a ChaCha20 key in bytes (RFC 8439 §2.3). */
58
68
  export const CHACHA20_KEY_SIZE: i32 = 32
59
69
 
@@ -240,7 +250,8 @@ const chacha20Core = (state: u32[], out: u32[]): void => {
240
250
  * `dst[dstAt + i] = src[srcAt + i] ^ keystream` for `len` bytes, the keystream
241
251
  * running from `state`'s counter on, and the counter left one past the last
242
252
  * block used (RFC 8439 §2.4). The caller has checked every window and that
243
- * the counter does not wrap.
253
+ * the counter does not wrap. The last keystream block is wiped before it
254
+ * returns; `state` is the caller's to wipe.
244
255
  */
245
256
  const chacha20XorInto = (state: u32[], src: u8[], srcAt: i32, len: i32, dst: u8[], dstAt: i32): void => {
246
257
  const keystream: u32[] = new Array<u32>(16)
@@ -266,6 +277,7 @@ const chacha20XorInto = (state: u32[], src: u8[], srcAt: i32, len: i32, dst: u8[
266
277
  }
267
278
  done = done + take
268
279
  }
280
+ wipe(keystream)
269
281
  }
270
282
 
271
283
  /**
@@ -329,7 +341,9 @@ export const chacha20Block = (key: u8[], counter: u32, nonce: u8[]): u8[] | null
329
341
  return null
330
342
  }
331
343
  const out: u8[] = new Array<u8>(CHACHA20_BLOCK_SIZE)
332
- chacha20XorInto(chacha20State(key, counter, nonce, 0), out, 0, CHACHA20_BLOCK_SIZE, out, 0)
344
+ const state: u32[] = chacha20State(key, counter, nonce, 0)
345
+ chacha20XorInto(state, out, 0, CHACHA20_BLOCK_SIZE, out, 0)
346
+ wipe(state)
333
347
  return out
334
348
  }
335
349
 
@@ -350,7 +364,9 @@ export const chacha20 = (key: u8[], counter: u32, nonce: u8[], data: u8[]): u8[]
350
364
  return null
351
365
  }
352
366
  const out: u8[] = new Array<u8>(len)
353
- chacha20XorInto(chacha20State(key, counter, nonce, 0), data, 0, len, out, 0)
367
+ const state: u32[] = chacha20State(key, counter, nonce, 0)
368
+ chacha20XorInto(state, data, 0, len, out, 0)
369
+ wipe(state)
354
370
  return out
355
371
  }
356
372
 
@@ -566,6 +582,8 @@ export const poly1305 = (key: u8[], msg: u8[]): u8[] | null => {
566
582
  poly1305Absorb(h, r, msg, 0, toI32(msg.length), false)
567
583
  const tag: u8[] = new Array<u8>(POLY1305_TAG_SIZE)
568
584
  poly1305Finish(h, key, tag)
585
+ wipe(h)
586
+ wipe(r)
569
587
  return tag
570
588
  }
571
589
 
@@ -579,7 +597,10 @@ export const poly1305KeyGen = (key: u8[], nonce: u8[]): u8[] | null => {
579
597
  if (toI32(key.length) !== CHACHA20_KEY_SIZE || toI32(nonce.length) !== CHACHA20POLY1305_NONCE_SIZE) {
580
598
  return null
581
599
  }
582
- return chacha20Poly1305Setup(chacha20State(key, 0, nonce, 0))
600
+ const state: u32[] = chacha20State(key, 0, nonce, 0)
601
+ const polyKey: u8[] = chacha20Poly1305Setup(state)
602
+ wipe(state)
603
+ return polyKey
583
604
  }
584
605
 
585
606
  /**
@@ -602,6 +623,8 @@ const chacha20Poly1305Tag = (polyKey: u8[], aad: u8[], ct: u8[], at: i32, len: i
602
623
  poly1305Block(h, r, lengths, 0, toU64(POLY1305_HIGH_BIT))
603
624
  const tag: u8[] = new Array<u8>(POLY1305_TAG_SIZE)
604
625
  poly1305Finish(h, polyKey, tag)
626
+ wipe(h)
627
+ wipe(r)
605
628
  return tag
606
629
  }
607
630
 
@@ -664,6 +687,8 @@ export const chacha20Poly1305Seal = (key: u8[], nonce: u8[], aad: u8[], plaintex
664
687
  for (let i: i32 = 0; i < toI32(tag.length); i++) {
665
688
  sealed[len + i] = tag[i]
666
689
  }
690
+ wipe(state)
691
+ wipe(polyKey)
667
692
  return sealed
668
693
  }
669
694
 
@@ -694,11 +719,18 @@ export const chacha20Poly1305Open = (key: u8[], nonce: u8[], aad: u8[], sealed:
694
719
  const tag: u8[] = chacha20Poly1305Tag(polyKey, aad, sealed, 0, len)
695
720
  // The one branch on the comparison, and it is on the answer the caller is
696
721
  // about to learn anyway.
697
- if (chacha20Poly1305TagMatch(tag, sealed, len) === 0) {
698
- return null
722
+ const match: u32 = chacha20Poly1305TagMatch(tag, sealed, len)
723
+ wipe(polyKey)
724
+ wipe(tag)
725
+ let plaintext: u8[] | null = null
726
+ if (match !== 0) {
727
+ const opened: u8[] = new Array<u8>(len)
728
+ chacha20XorInto(state, sealed, 0, len, opened, 0)
729
+ plaintext = opened
699
730
  }
700
- const plaintext: u8[] = new Array<u8>(len)
701
- chacha20XorInto(state, sealed, 0, len, plaintext, 0)
731
+ // One wipe both paths reach, rather than one on each, so that each is a
732
+ // store of its own the -O2 count can see go missing.
733
+ wipe(state)
702
734
  return plaintext
703
735
  }
704
736
 
@@ -714,6 +746,8 @@ export const chacha20HeaderMask = (hpKey: u8[], sample: u8[]): u8[] | null => {
714
746
  return null
715
747
  }
716
748
  const mask: u8[] = new Array<u8>(CHACHA20_HEADER_MASK_SIZE)
717
- chacha20XorInto(chacha20State(hpKey, chacha20Word(sample, 0), sample, 4), mask, 0, 5, mask, 0)
749
+ const state: u32[] = chacha20State(hpKey, chacha20Word(sample, 0), sample, 4)
750
+ chacha20XorInto(state, mask, 0, 5, mask, 0)
751
+ wipe(state)
718
752
  return mask
719
753
  }