@amritk/nish-x86_64-linux 0.16.0 → 0.18.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 (58) hide show
  1. package/INSTALL.md +43 -34
  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 +50 -13
  6. package/runtime/nish.mjs +71 -7
  7. package/runtime/runtime-host.c +43 -24
  8. package/runtime/runtime-net.c +57 -10
  9. package/runtime/runtime-os.c +62 -20
  10. package/runtime/runtime-simd.c +203 -0
  11. package/runtime/runtime-wasm.c +12 -0
  12. package/runtime/runtime.c +66 -18
  13. package/runtime/shim.mjs +114 -5
  14. package/scripts/build.sh +14 -12
  15. package/std/README.md +168 -36
  16. package/std/crypto/aes.ts +51 -8
  17. package/std/crypto/base64url.ts +25 -12
  18. package/std/crypto/chacha20poly1305.ts +43 -29
  19. package/std/crypto/ct.ts +6 -1
  20. package/std/crypto/hkdf.ts +364 -3
  21. package/std/crypto/hmac.ts +307 -7
  22. package/std/crypto/p256.ts +618 -34
  23. package/std/crypto/sha1.ts +147 -0
  24. package/std/crypto/sha256.ts +3 -2
  25. package/std/crypto/x25519.ts +88 -36
  26. package/std/crypto/x509.ts +130 -47
  27. package/std/json.ts +297 -41
  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 +911 -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 +462 -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 +929 -0
  45. package/std/net/quic-packet.ts +1698 -0
  46. package/std/net/quic-recovery.ts +849 -0
  47. package/std/net/quic-stream.ts +1609 -0
  48. package/std/net/quic.ts +3578 -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 +414 -0
  54. package/std/net/tls.ts +1437 -0
  55. package/std/net/websocket.ts +823 -0
  56. package/std/net/webtransport.ts +1334 -0
  57. package/std/secret.ts +62 -0
  58. package/std/text.ts +77 -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) {
@@ -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,45 +486,77 @@ 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
507
+ }
508
+
509
+ /**
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.
513
+ */
514
+ const x25519Exposed = (scalar: u8[], u: u8[]): u8[] | null => {
515
+ if (toI32(scalar.length) !== 32 || toI32(u.length) !== 32) {
516
+ return null
517
+ }
518
+ const k: u8[] = new Array<u8>(32)
519
+ for (let i: i32 = 0; i < 32; i++) {
520
+ k[i] = scalar[i]
521
+ }
522
+ k[0] = k[0] & toU8(248)
523
+ k[31] = (k[31] & toU8(127)) | toU8(64)
524
+ const out: u8[] = x25519Ladder(k, f25519Decode(u))
525
+ wipe(k)
526
+ return out
470
527
  }
471
528
 
472
529
  /**
473
530
  * X25519 (RFC 7748 §5): the u-coordinate of `scalar * u` on curve25519, as 32
474
- * little-endian bytes.
531
+ * little-endian bytes, held as the `Secret` it is.
475
532
  *
476
533
  * `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).
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).
480
537
  *
481
538
  * Answers `null` unless both arguments are exactly `X25519_SIZE` bytes. An
482
539
  * all-zero answer is **returned, not refused**: it is what a low-order `u`
483
540
  * gives, and RFC 7748 §6.1 leaves checking for it to the protocol. Nothing in
484
541
  * this tree makes that check for you yet (TLS 1.3, WP34 T1, is planned to), so
485
542
  * every caller doing a key exchange must refuse an all-zero answer itself —
486
- * with `timingSafeEqual` against 32 zero bytes, since the answer is a secret.
543
+ * inside `expose`, with `timingSafeEqual` against 32 zero bytes.
487
544
  */
488
- export const x25519 = (scalar: u8[], u: u8[]): u8[] | null => {
489
- if (toI32(scalar.length) !== 32 || toI32(u.length) !== 32) {
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) {
490
548
  return null
491
549
  }
492
- const k: u8[] = new Array<u8>(32)
493
- for (let i: i32 = 0; i < 32; i++) {
494
- k[i] = scalar[i]
495
- }
496
- k[0] = k[0] & toU8(248)
497
- k[31] = (k[31] & toU8(127)) | toU8(64)
498
- return x25519Ladder(k, f25519Decode(u))
550
+ return secret(shared)
499
551
  }
500
552
 
501
553
  /**
502
554
  * `x25519(scalar, 9)`: the public key for the private key `scalar`, since 9 is
503
- * curve25519's base point (RFC 7748 §4.1, §6.1). Answers `null` unless
504
- * `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.
505
557
  */
506
- export const x25519Base = (scalar: u8[]): u8[] | null => {
558
+ export const x25519Base = (scalar: Secret<u8[]>): u8[] | null => {
507
559
  const base: u8[] = new Array<u8>(32)
508
560
  base[0] = toU8(9)
509
- return x25519(scalar, base)
561
+ return exposeWith(scalar, base, x25519Exposed)
510
562
  }