@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.
- package/INSTALL.md +58 -40
- package/bin/nish +0 -0
- package/package.json +1 -1
- package/runtime/nish.d.ts +117 -8
- package/runtime/nish.h +67 -10
- package/runtime/nish.mjs +71 -7
- package/runtime/runtime-host.c +72 -26
- package/runtime/runtime-net.c +72 -19
- package/runtime/runtime-os.c +127 -45
- package/runtime/runtime-parallel.c +5 -1
- package/runtime/runtime-wasm.c +26 -2
- package/runtime/runtime.c +86 -14
- package/runtime/shim.mjs +114 -5
- package/scripts/build.sh +1 -3
- package/std/README.md +192 -36
- package/std/crypto/aes.ts +104 -21
- package/std/crypto/base64url.ts +43 -17
- package/std/crypto/chacha20poly1305.ts +52 -31
- package/std/crypto/ct.ts +14 -3
- package/std/crypto/hkdf.ts +385 -8
- package/std/crypto/hmac.ts +325 -9
- package/std/crypto/p256.ts +153 -27
- package/std/crypto/sha1.ts +147 -0
- package/std/crypto/sha256.ts +15 -3
- package/std/crypto/sha512.ts +16 -2
- package/std/crypto/x25519.ts +90 -37
- package/std/crypto/x509.ts +265 -65
- package/std/net/hpack.ts +1094 -0
- package/std/net/http-fields.ts +604 -0
- package/std/net/http1-server.ts +1389 -0
- package/std/net/http1.ts +1492 -0
- package/std/net/http2-frame.ts +745 -0
- package/std/net/http2-tls.ts +216 -0
- package/std/net/http2.ts +1433 -0
- package/std/net/http3-frame.ts +457 -0
- package/std/net/http3-server.ts +905 -0
- package/std/net/http3.ts +2145 -0
- package/std/net/qpack.ts +1163 -0
- package/std/net/quic-conn-ack.ts +206 -0
- package/std/net/quic-conn-cid.ts +425 -0
- package/std/net/quic-conn-params.ts +392 -0
- package/std/net/quic-datagram.ts +130 -0
- package/std/net/quic-frame.ts +1206 -0
- package/std/net/quic-listener.ts +653 -0
- package/std/net/quic-packet.ts +1474 -0
- package/std/net/quic-recovery.ts +849 -0
- package/std/net/quic-stream.ts +1603 -0
- package/std/net/quic.ts +3214 -0
- package/std/net/tls/codec.ts +1089 -0
- package/std/net/tls/record-server.ts +1173 -0
- package/std/net/tls/record.ts +621 -0
- package/std/net/tls/schedule.ts +429 -0
- package/std/net/tls-tcp.ts +410 -0
- package/std/net/tls.ts +1368 -0
- package/std/net/websocket.ts +823 -0
- package/std/net/webtransport.ts +1334 -0
- 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
|
+
}
|
package/std/crypto/sha256.ts
CHANGED
|
@@ -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
|
|
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
|
-
/**
|
|
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))
|
package/std/crypto/sha512.ts
CHANGED
|
@@ -495,15 +495,29 @@ export class Sha384 {
|
|
|
495
495
|
}
|
|
496
496
|
}
|
|
497
497
|
|
|
498
|
-
/**
|
|
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
|
-
/**
|
|
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()
|
package/std/crypto/x25519.ts
CHANGED
|
@@ -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
|
|
25
|
-
*
|
|
26
|
-
*
|
|
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)
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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)
|
|
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]
|
|
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]
|
|
393
|
+
q = f25519Add64(h[i], q) >> f25519Width(i)
|
|
374
394
|
}
|
|
375
|
-
h[0] = h[0]
|
|
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
|
-
|
|
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
|
|
474
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
|
561
|
+
return exposeWith(scalar, base, x25519Exposed)
|
|
509
562
|
}
|