@amritk/nish-x86_64-linux 0.15.0 → 0.17.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 (57) hide show
  1. package/INSTALL.md +58 -40
  2. package/bin/nish +0 -0
  3. package/package.json +1 -1
  4. package/runtime/nish.d.ts +117 -8
  5. package/runtime/nish.h +67 -10
  6. package/runtime/nish.mjs +71 -7
  7. package/runtime/runtime-host.c +72 -26
  8. package/runtime/runtime-net.c +72 -19
  9. package/runtime/runtime-os.c +127 -45
  10. package/runtime/runtime-parallel.c +5 -1
  11. package/runtime/runtime-wasm.c +26 -2
  12. package/runtime/runtime.c +86 -14
  13. package/runtime/shim.mjs +114 -5
  14. package/scripts/build.sh +1 -3
  15. package/std/README.md +192 -36
  16. package/std/crypto/aes.ts +104 -21
  17. package/std/crypto/base64url.ts +43 -17
  18. package/std/crypto/chacha20poly1305.ts +52 -31
  19. package/std/crypto/ct.ts +14 -3
  20. package/std/crypto/hkdf.ts +385 -8
  21. package/std/crypto/hmac.ts +325 -9
  22. package/std/crypto/p256.ts +153 -27
  23. package/std/crypto/sha1.ts +147 -0
  24. package/std/crypto/sha256.ts +15 -3
  25. package/std/crypto/sha512.ts +16 -2
  26. package/std/crypto/x25519.ts +90 -37
  27. package/std/crypto/x509.ts +265 -65
  28. package/std/net/hpack.ts +1094 -0
  29. package/std/net/http-fields.ts +604 -0
  30. package/std/net/http1-server.ts +1389 -0
  31. package/std/net/http1.ts +1492 -0
  32. package/std/net/http2-frame.ts +745 -0
  33. package/std/net/http2-tls.ts +216 -0
  34. package/std/net/http2.ts +1433 -0
  35. package/std/net/http3-frame.ts +457 -0
  36. package/std/net/http3-server.ts +905 -0
  37. package/std/net/http3.ts +2145 -0
  38. package/std/net/qpack.ts +1163 -0
  39. package/std/net/quic-conn-ack.ts +206 -0
  40. package/std/net/quic-conn-cid.ts +425 -0
  41. package/std/net/quic-conn-params.ts +392 -0
  42. package/std/net/quic-datagram.ts +130 -0
  43. package/std/net/quic-frame.ts +1206 -0
  44. package/std/net/quic-listener.ts +653 -0
  45. package/std/net/quic-packet.ts +1474 -0
  46. package/std/net/quic-recovery.ts +849 -0
  47. package/std/net/quic-stream.ts +1603 -0
  48. package/std/net/quic.ts +3214 -0
  49. package/std/net/tls/codec.ts +1089 -0
  50. package/std/net/tls/record-server.ts +1173 -0
  51. package/std/net/tls/record.ts +621 -0
  52. package/std/net/tls/schedule.ts +429 -0
  53. package/std/net/tls-tcp.ts +410 -0
  54. package/std/net/tls.ts +1368 -0
  55. package/std/net/websocket.ts +823 -0
  56. package/std/net/webtransport.ts +1334 -0
  57. package/std/secret.ts +62 -0
@@ -0,0 +1,147 @@
1
+ /**
2
+ * `nish/crypto/sha1` — SHA-1 as FIPS 180-4 §6.1 defines it, in pure Nish.
3
+ *
4
+ * **This is not for security.** SHA-1 is broken for collisions (SHAttered,
5
+ * 2017) and no new protocol should sign or authenticate with it. It is here
6
+ * for one caller: RFC 6455 §4.2.2's `Sec-WebSocket-Accept`, which is the
7
+ * base64 of the SHA-1 of the client's key and a fixed GUID. That value proves
8
+ * only that the server read the handshake as a WebSocket handshake, not who
9
+ * either side is, so a collision buys an attacker nothing there.
10
+ * `nish/net/websocket` is the importer; anything that needs a hash for its own
11
+ * sake takes `nish/crypto/sha256`.
12
+ *
13
+ * import { sha1 } from "nish/crypto/sha1";
14
+ *
15
+ * const digest: u8[] = sha1(message); // 20 bytes
16
+ *
17
+ * One shot only, because a handshake key is a few dozen bytes: there is no
18
+ * streaming hasher to keep in step with `Sha256`'s.
19
+ *
20
+ * Every word is a `u32`, whose arithmetic wraps, so the additions modulo 2^32
21
+ * are plain `+`. The only branches are on lengths, loop counters and the round
22
+ * number.
23
+ *
24
+ * Written from the specification, not ported from another implementation.
25
+ */
26
+
27
+ /** The length of a digest in bytes (FIPS 180-4 §1). */
28
+ export const SHA1_SIZE: i32 = 20
29
+
30
+ /** The length of a message block in bytes (FIPS 180-4 §1). */
31
+ export const SHA1_BLOCK: i32 = 64
32
+
33
+ /** ROTL^n(x) of FIPS 180-4 §3.2: a left rotation of a 32-bit word, `0 < n < 32`. */
34
+ const sha1Rotl = (x: u32, n: u32): u32 => (x << n) | (x >>> (32 - n))
35
+
36
+ /** The big-endian word at `buf[at .. at + 4)`, as FIPS 180-4 §3.1 orders bytes in a word. */
37
+ const sha1LoadWord = (buf: u8[], at: i32): u32 =>
38
+ (toU32(buf[at]) << 24) | (toU32(buf[at + 1]) << 16) | (toU32(buf[at + 2]) << 8) | toU32(buf[at + 3])
39
+
40
+ /**
41
+ * Folds the 64-byte block at `src[at .. at + 64)` into `h`: FIPS 180-4
42
+ * §6.1.2, steps 1 to 4. `w` is the 80-word schedule, allocated once per call
43
+ * to `sha1` rather than once per block.
44
+ */
45
+ const sha1Compress = (h: u32[], w: u32[], src: u8[], at: i32): void => {
46
+ // Written as the condition the body runs under, the shape that proves the
47
+ // plain `w[t]` and `h[i]` indices in range (as `sha256Compress` does).
48
+ if (toI32(h.length) >= 5 && toI32(w.length) >= 80 && at >= 0 && at <= toI32(src.length) - SHA1_BLOCK) {
49
+ // Step 1: the message schedule, big-endian words (§3.1), then (6.1.2).
50
+ for (let t: i32 = 0; t < 16; t += 1) {
51
+ w[t] = sha1LoadWord(src, at + 4 * t)
52
+ }
53
+ for (let t: i32 = 16; t < 80; t += 1) {
54
+ w[t] = sha1Rotl(w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16], 1)
55
+ }
56
+ // Step 2.
57
+ let a: u32 = h[0]
58
+ let b: u32 = h[1]
59
+ let c: u32 = h[2]
60
+ let d: u32 = h[3]
61
+ let e: u32 = h[4]
62
+ // Step 3: eighty rounds, f_t and K_t of §4.1.1 and §4.2.1 by quarter.
63
+ for (let t: i32 = 0; t < 80; t += 1) {
64
+ let f: u32 = 0
65
+ let k: u32 = 0
66
+ if (t < 20) {
67
+ f = (b & c) ^ (~b & d)
68
+ k = 0x5a827999
69
+ } else if (t < 40) {
70
+ f = b ^ c ^ d
71
+ k = 0x6ed9eba1
72
+ } else if (t < 60) {
73
+ f = (b & c) ^ (b & d) ^ (c & d)
74
+ k = 0x8f1bbcdc
75
+ } else {
76
+ f = b ^ c ^ d
77
+ k = 0xca62c1d6
78
+ }
79
+ const temp: u32 = sha1Rotl(a, 5) + f + e + k + w[t]
80
+ e = d
81
+ d = c
82
+ c = sha1Rotl(b, 30)
83
+ b = a
84
+ a = temp
85
+ }
86
+ // Step 4.
87
+ h[0] += a
88
+ h[1] += b
89
+ h[2] += c
90
+ h[3] += d
91
+ h[4] += e
92
+ } else {
93
+ panic("sha1: a block outside its buffer")
94
+ }
95
+ }
96
+
97
+ /**
98
+ * The SHA-1 digest of all of `data`, as a fresh 20-byte array.
99
+ *
100
+ * An array longer than 2^31 - 1 bytes panics, for the reason `sha256` gives:
101
+ * under `--number-mode f64`, `toI32` of a longer length saturates, and the
102
+ * answer would be the digest of a prefix.
103
+ */
104
+ export const sha1 = (data: u8[]): u8[] => {
105
+ if (data.length > 2147483647) {
106
+ panic("sha1: a message longer than 2^31 - 1 bytes")
107
+ }
108
+ const n: i32 = toI32(data.length)
109
+ // H^(0) of FIPS 180-4 §5.3.1.
110
+ const h: u32[] = new Array<u32>(5)
111
+ h[0] = 0x67452301
112
+ h[1] = 0xefcdab89
113
+ h[2] = 0x98badcfe
114
+ h[3] = 0x10325476
115
+ h[4] = 0xc3d2e1f0
116
+ const w: u32[] = new Array<u32>(80)
117
+ // Whole blocks straight from `data`, then the tail and its padding (§5.1.1)
118
+ // in one or two blocks of their own.
119
+ const whole: i32 = n - (n % SHA1_BLOCK)
120
+ for (let at: i32 = 0; at < whole; at += SHA1_BLOCK) {
121
+ sha1Compress(h, w, data, at)
122
+ }
123
+ const tail: i32 = n - whole
124
+ const tailBlocks: i32 = tail < SHA1_BLOCK - 8 ? 1 : 2
125
+ const last: u8[] = new Array<u8>(tailBlocks * SHA1_BLOCK)
126
+ const lastLength: i32 = toI32(last.length)
127
+ for (let k: i32 = 0; k < tail && k < lastLength; k += 1) {
128
+ last[k] = data[whole + k]
129
+ }
130
+ last[tail] = 0x80
131
+ const bits: u64 = toU64(toI64(n)) << 3
132
+ for (let i: i32 = 0; i < 8; i += 1) {
133
+ last[lastLength - 1 - i] = toU8(bits >>> toU64(8 * i))
134
+ }
135
+ for (let at: i32 = 0; at < lastLength; at += SHA1_BLOCK) {
136
+ sha1Compress(h, w, last, at)
137
+ }
138
+ const out: u8[] = new Array<u8>(SHA1_SIZE)
139
+ for (let i: i32 = 0; i < 5; i += 1) {
140
+ const word: u32 = h[i]
141
+ out[4 * i] = toU8(word >>> 24)
142
+ out[4 * i + 1] = toU8(word >>> 16)
143
+ out[4 * i + 2] = toU8(word >>> 8)
144
+ out[4 * i + 3] = toU8(word)
145
+ }
146
+ return out
147
+ }
@@ -215,8 +215,9 @@ const sha256Compress = (h: u32[], w: u32[], src: u8[], at: i32): void => {
215
215
  // early exit, because that is the shape that proves the plain `w[t]` and
216
216
  // `h[i]` indices in range and drops their bounds checks. The computed ones —
217
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.
218
+ // check each; measured, removing every check (with `--unchecked-indexing`,
219
+ // which no longer reaches a `nish/` module at all) moved the 1 MiB time by
220
+ // less than the run-to-run noise.
220
221
  if (toI32(h.length) >= 8 && toI32(w.length) >= 64 && at >= 0 && at <= toI32(src.length) - SHA256_BLOCK) {
221
222
  // Step 1: the message schedule.
222
223
  for (let t: i32 = 0; t < 16; t += 1) {
@@ -435,8 +436,19 @@ export class Sha256 {
435
436
  }
436
437
  }
437
438
 
438
- /** The SHA-256 digest of all of `data`, as a fresh 32-byte array. */
439
+ /**
440
+ * The SHA-256 digest of all of `data`, as a fresh 32-byte array.
441
+ *
442
+ * An array longer than 2^31 - 1 bytes panics. A window's length is an `i32`,
443
+ * and under `--number-mode f64` `toI32` of a longer length saturates, so
444
+ * hashing `toI32(data.length)` bytes would answer the digest of a prefix: one
445
+ * digest for every message that shares its first 2^31 - 1 bytes. The length
446
+ * is compared as the `number` it is, before any `toI32`.
447
+ */
439
448
  export const sha256 = (data: u8[]): u8[] => {
449
+ if (data.length > 2147483647) {
450
+ panic("sha256: a message longer than 2^31 - 1 bytes")
451
+ }
440
452
  const hasher: Sha256 = new Sha256()
441
453
  const from: i32 = 0
442
454
  hasher.update(data, from, toI32(data.length))
@@ -495,15 +495,29 @@ export class Sha384 {
495
495
  }
496
496
  }
497
497
 
498
- /** The SHA-512 digest of all of `data`, as a fresh 64-byte array. */
498
+ /**
499
+ * The SHA-512 digest of all of `data`, as a fresh 64-byte array. An array
500
+ * longer than 2^31 - 1 bytes panics, for the reason `sha256` gives: a longer
501
+ * length does not survive `toI32` under `--number-mode f64`, and the digest
502
+ * would be of a prefix.
503
+ */
499
504
  export const sha512 = (data: u8[]): u8[] => {
505
+ if (data.length > 2147483647) {
506
+ panic("sha512: a message longer than 2^31 - 1 bytes")
507
+ }
500
508
  const hasher = new Sha512()
501
509
  hasher.update(data, OFFSET_ZERO, toI32(data.length))
502
510
  return hasher.digest()
503
511
  }
504
512
 
505
- /** The SHA-384 digest of all of `data`, as a fresh 48-byte array. */
513
+ /**
514
+ * The SHA-384 digest of all of `data`, as a fresh 48-byte array. An array
515
+ * longer than 2^31 - 1 bytes panics, as in `sha512`.
516
+ */
506
517
  export const sha384 = (data: u8[]): u8[] => {
518
+ if (data.length > 2147483647) {
519
+ panic("sha384: a message longer than 2^31 - 1 bytes")
520
+ }
507
521
  const hasher = new Sha384()
508
522
  hasher.update(data, OFFSET_ZERO, toI32(data.length))
509
523
  return hasher.digest()
@@ -21,9 +21,10 @@
21
21
  * subtraction needs no bias and a carry is a floor division that works the
22
22
  * same on a negative limb.
23
23
  *
24
- * Signed `i64` overflow is undefined behaviour in this language unless a
25
- * program is compiled with `--wrapping`, so the limb bounds below are not a
26
- * nicety: each function states what it accepts and what it answers, and
24
+ * Signed `i64` overflow is undefined behaviour in this language, and
25
+ * `--wrapping` does not reach a `nish/` module (only `wrappingAdd`,
26
+ * `wrappingSub` and `wrappingMul` from `nish:unsafe` define a wrap, at the
27
+ * site that calls one), so the limb bounds below are not a nicety: each function states what it accepts and what it answers, and
27
28
  * `f25519Mul` says why its sums fit.
28
29
  *
29
30
  * **Constant time, by construction, and in part by disassembly.** Nothing
@@ -57,17 +58,36 @@
57
58
  * the literal `10` or `32`, in range and drop their bounds checks. (A named
58
59
  * constant as the bound, or a `panic` as the guard, does not prove them.)
59
60
  *
61
+ * **Keys are `Secret`s** (`nish:secret`, docs/LANGUAGE.md "Secrets"). `x25519`
62
+ * takes the scalar as a `Secret<u8[]>` and answers the shared secret as one;
63
+ * `x25519Base` answers the public key as plain bytes. The ladder runs inside
64
+ * `exposeWith`, and the clamped copy and every ladder element are wiped before
65
+ * it returns (ECC-2, docs/security/crypto-ecc.md).
66
+ *
60
67
  * Private names carry the `f25519` / `x25519` prefix because a `std/` module's
61
68
  * private functions share the importing program's flat symbol namespace
62
69
  * (`docs/wp26-stdlib.md` §3e).
63
70
  */
64
71
 
72
+ import { Secret, exposeWith, secret, wipe } from "nish:secret"
73
+
65
74
  /** The length in bytes of a scalar, a u-coordinate and an X25519 output. */
66
75
  export const X25519_SIZE: i32 = 32
67
76
 
68
77
  /** (A - 2) / 4 for curve25519's A = 486662 (RFC 7748 §5). */
69
78
  const X25519_A24: i64 = 121665
70
79
 
80
+ /**
81
+ * `a + b`, `a - b` and `a * b` on limbs: the `u64` arithmetic, read back as
82
+ * `i64`. Every bound this module states keeps the true result inside `i64`,
83
+ * so the answer is the signed one bit for bit; the arithmetic is unsigned
84
+ * because signed arithmetic is checked for an overflow, and a check is a
85
+ * branch on a secret limb. Each is one instruction once LLVM inlines it.
86
+ */
87
+ const f25519Add64 = (a: i64, b: i64): i64 => toI64(toU64(a) + toU64(b))
88
+ const f25519Sub64 = (a: i64, b: i64): i64 => toI64(toU64(a) - toU64(b))
89
+ const f25519Mul64 = (a: i64, b: i64): i64 => toI64(toU64(a) * toU64(b))
90
+
71
91
  /** The width of limb `i`: 26 bits for an even limb, 25 for an odd one. */
72
92
  const f25519Width = (i: i32): i64 => {
73
93
  const odd: i64 = toI64(i & 1)
@@ -75,7 +95,7 @@ const f25519Width = (i: i32): i64 => {
75
95
  }
76
96
 
77
97
  /** The mask of limb `i`'s bits, `2^width - 1`. */
78
- const f25519LimbMask = (i: i32): i64 => (toI64(1) << f25519Width(i)) - toI64(1)
98
+ const f25519LimbMask = (i: i32): i64 => f25519Sub64(toI64(1) << f25519Width(i), toI64(1))
79
99
 
80
100
  /** A fresh field element, zero. */
81
101
  const f25519Zero = (): i64[] => {
@@ -112,7 +132,7 @@ const f25519CarryChain = (h: i64[]): void => {
112
132
  for (let i: i32 = 0; i < 9; i++) {
113
133
  const c: i64 = h[i] >> f25519Width(i)
114
134
  h[i] = h[i] & f25519LimbMask(i)
115
- h[i + 1] = h[i + 1] + c
135
+ h[i + 1] = f25519Add64(h[i + 1], c)
116
136
  }
117
137
  }
118
138
 
@@ -139,10 +159,10 @@ const f25519Carry = (h: i64[]): void => {
139
159
  f25519CarryChain(h)
140
160
  const top: i64 = h[9] >> toI64(25)
141
161
  h[9] = h[9] & f25519LimbMask(9)
142
- h[0] = h[0] + top * toI64(19)
162
+ h[0] = f25519Add64(h[0], f25519Mul64(top, toI64(19)))
143
163
  const c0: i64 = h[0] >> toI64(26)
144
164
  h[0] = h[0] & f25519LimbMask(0)
145
- h[1] = h[1] + c0
165
+ h[1] = f25519Add64(h[1], c0)
146
166
  }
147
167
 
148
168
  /**
@@ -154,7 +174,7 @@ const f25519Add = (out: i64[], f: i64[], g: i64[]): void => {
154
174
  return
155
175
  }
156
176
  for (let i: i32 = 0; i < 10; i++) {
157
- out[i] = f[i] + g[i]
177
+ out[i] = f25519Add64(f[i], g[i])
158
178
  }
159
179
  }
160
180
 
@@ -168,7 +188,7 @@ const f25519Sub = (out: i64[], f: i64[], g: i64[]): void => {
168
188
  return
169
189
  }
170
190
  for (let i: i32 = 0; i < 10; i++) {
171
- out[i] = f[i] - g[i]
191
+ out[i] = f25519Sub64(f[i], g[i])
172
192
  }
173
193
  }
174
194
 
@@ -196,7 +216,7 @@ const f25519Mul = (out: i64[], f: i64[], g: i64[]): void => {
196
216
  // product takes `f`'s limb doubled.
197
217
  const doubled: i64[] = new Array<i64>(10)
198
218
  for (let i: i32 = 0; i < 10; i++) {
199
- doubled[i] = f[i] * toI64((i & 1) + 1)
219
+ doubled[i] = f25519Mul64(f[i], toI64((i & 1) + 1))
200
220
  }
201
221
  // The plain product, limb `i + j` of nineteen.
202
222
  const wide: i64[] = new Array<i64>(19)
@@ -205,14 +225,14 @@ const f25519Mul = (out: i64[], f: i64[], g: i64[]): void => {
205
225
  const k: i32 = i + j
206
226
  const fi: i64 = (i & j & 1) === 1 ? doubled[i] : f[i]
207
227
  if (k >= 0 && k < 19) {
208
- wide[k] = wide[k] + fi * g[j]
228
+ wide[k] = f25519Add64(wide[k], f25519Mul64(fi, g[j]))
209
229
  }
210
230
  }
211
231
  }
212
232
  // Limbs 10 to 18 are past bit 255, and 2^255 = 19 (mod p): fold them down.
213
233
  const h: i64[] = new Array<i64>(10)
214
234
  for (let k: i32 = 0; k < 9; k++) {
215
- h[k] = wide[k] + wide[k + 10] * toI64(19)
235
+ h[k] = f25519Add64(wide[k], f25519Mul64(wide[k + 10], toI64(19)))
216
236
  }
217
237
  h[9] = wide[9]
218
238
  f25519Carry(h)
@@ -242,7 +262,7 @@ const f25519MulA24 = (out: i64[], f: i64[]): void => {
242
262
  return
243
263
  }
244
264
  for (let i: i32 = 0; i < 10; i++) {
245
- out[i] = f[i] * X25519_A24
265
+ out[i] = f25519Mul64(f[i], X25519_A24)
246
266
  }
247
267
  f25519Carry(out)
248
268
  }
@@ -304,7 +324,7 @@ const f25519Swap = (f: i64[], g: i64[], bit: i64): void => {
304
324
  if (toI32(f.length) < 10 || toI32(g.length) < 10) {
305
325
  return
306
326
  }
307
- const mask: i64 = toI64(0) - bit
327
+ const mask: i64 = f25519Sub64(toI64(0), bit)
308
328
  for (let i: i32 = 0; i < 10; i++) {
309
329
  const t: i64 = mask & (f[i] ^ g[i])
310
330
  f[i] = f[i] ^ t
@@ -368,11 +388,11 @@ const f25519Encode = (f: i64[]): u8[] => {
368
388
  f25519Carry(h)
369
389
  f25519Carry(h)
370
390
 
371
- let q: i64 = (h[0] + toI64(19)) >> toI64(26)
391
+ let q: i64 = f25519Add64(h[0], toI64(19)) >> toI64(26)
372
392
  for (let i: i32 = 1; i < 10; i++) {
373
- q = (h[i] + q) >> f25519Width(i)
393
+ q = f25519Add64(h[i], q) >> f25519Width(i)
374
394
  }
375
- h[0] = h[0] + q * toI64(19)
395
+ h[0] = f25519Add64(h[0], f25519Mul64(q, toI64(19)))
376
396
  f25519CarryChain(h)
377
397
  h[9] = h[9] & f25519LimbMask(9)
378
398
 
@@ -466,25 +486,32 @@ const x25519Ladder = (k: u8[], u: i64[]): u8[] => {
466
486
 
467
487
  f25519Invert(t, z2)
468
488
  f25519Mul(x2, x2, t)
469
- return f25519Encode(x2)
489
+ const out: u8[] = f25519Encode(x2)
490
+ // ECC-2: every element the scalar's bits reached is zeroed before the
491
+ // ladder returns, with the store `nish:secret` keeps from the optimiser.
492
+ wipe(x2)
493
+ wipe(z2)
494
+ wipe(x3)
495
+ wipe(z3)
496
+ wipe(a)
497
+ wipe(aa)
498
+ wipe(b)
499
+ wipe(bb)
500
+ wipe(e)
501
+ wipe(c)
502
+ wipe(d)
503
+ wipe(da)
504
+ wipe(cb)
505
+ wipe(t)
506
+ return out
470
507
  }
471
508
 
472
509
  /**
473
- * X25519 (RFC 7748 §5): the u-coordinate of `scalar * u` on curve25519, as 32
474
- * little-endian bytes.
475
- *
476
- * `scalar` is clamped as §5 says — bits 0, 1, 2 and 255 cleared, bit 254 set —
477
- * on a copy, so the caller's array is not changed. The top bit of `u` is
478
- * ignored, and a `u` of p or more is taken modulo p. The answer is always
479
- * canonical (below p).
480
- *
481
- * Answers `null` unless both arguments are exactly `X25519_SIZE` bytes. An
482
- * all-zero answer is **returned, not refused**: it is what a low-order `u`
483
- * gives, and RFC 7748 §6.1 leaves checking for it to the protocol, which is
484
- * where TLS 1.3 (WP34 T1) makes that check. A caller doing a key exchange
485
- * outside TLS should make it too.
510
+ * X25519 on the plain scalar `scalar`, for `exposeWith` to run: the clamped
511
+ * copy is wiped once the ladder has used it, and the answer is the shared
512
+ * secret's plain bytes, which `x25519` wraps again at once.
486
513
  */
487
- export const x25519 = (scalar: u8[], u: u8[]): u8[] | null => {
514
+ const x25519Exposed = (scalar: u8[], u: u8[]): u8[] | null => {
488
515
  if (toI32(scalar.length) !== 32 || toI32(u.length) !== 32) {
489
516
  return null
490
517
  }
@@ -494,16 +521,42 @@ export const x25519 = (scalar: u8[], u: u8[]): u8[] | null => {
494
521
  }
495
522
  k[0] = k[0] & toU8(248)
496
523
  k[31] = (k[31] & toU8(127)) | toU8(64)
497
- return x25519Ladder(k, f25519Decode(u))
524
+ const out: u8[] = x25519Ladder(k, f25519Decode(u))
525
+ wipe(k)
526
+ return out
527
+ }
528
+
529
+ /**
530
+ * X25519 (RFC 7748 §5): the u-coordinate of `scalar * u` on curve25519, as 32
531
+ * little-endian bytes, held as the `Secret` it is.
532
+ *
533
+ * `scalar` is clamped as §5 says — bits 0, 1, 2 and 255 cleared, bit 254 set —
534
+ * on a copy, which is wiped, so the caller's key is not changed. The top bit of
535
+ * `u` is ignored, and a `u` of p or more is taken modulo p. The answer is
536
+ * always canonical (below p).
537
+ *
538
+ * Answers `null` unless both arguments are exactly `X25519_SIZE` bytes. An
539
+ * all-zero answer is **returned, not refused**: it is what a low-order `u`
540
+ * gives, and RFC 7748 §6.1 leaves checking for it to the protocol. Nothing in
541
+ * this tree makes that check for you yet (TLS 1.3, WP34 T1, is planned to), so
542
+ * every caller doing a key exchange must refuse an all-zero answer itself —
543
+ * inside `expose`, with `timingSafeEqual` against 32 zero bytes.
544
+ */
545
+ export const x25519 = (scalar: Secret<u8[]>, u: u8[]): Secret<u8[]> | null => {
546
+ const shared: u8[] | null = exposeWith(scalar, u, x25519Exposed)
547
+ if (shared === null) {
548
+ return null
549
+ }
550
+ return secret(shared)
498
551
  }
499
552
 
500
553
  /**
501
554
  * `x25519(scalar, 9)`: the public key for the private key `scalar`, since 9 is
502
- * curve25519's base point (RFC 7748 §4.1, §6.1). Answers `null` unless
503
- * `scalar` is `X25519_SIZE` bytes.
555
+ * curve25519's base point (RFC 7748 §4.1, §6.1), and public, so plain bytes.
556
+ * Answers `null` unless `scalar` is `X25519_SIZE` bytes.
504
557
  */
505
- export const x25519Base = (scalar: u8[]): u8[] | null => {
558
+ export const x25519Base = (scalar: Secret<u8[]>): u8[] | null => {
506
559
  const base: u8[] = new Array<u8>(32)
507
560
  base[0] = toU8(9)
508
- return x25519(scalar, base)
561
+ return exposeWith(scalar, base, x25519Exposed)
509
562
  }