@amritk/nish-x86_64-linux 0.31.0 → 0.33.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/bin/nish +0 -0
- package/docs/AI.md +10 -4
- package/package.json +1 -1
- package/std/README.md +2 -0
- package/std/crypto/bcrypt-batch.ts +702 -0
- package/std/crypto/bcrypt.ts +152 -101
- package/std/sort.ts +178 -0
package/bin/nish
CHANGED
|
Binary file
|
package/docs/AI.md
CHANGED
|
@@ -49,7 +49,7 @@ stdout, nothing on stderr, every field 1-based with `endLine`/`endColumn`
|
|
|
49
49
|
exclusive:
|
|
50
50
|
|
|
51
51
|
```json
|
|
52
|
-
{"file":"p.ts","line":3,"column":17,"endLine":3,"endColumn":20,"severity":"error","code":"NL2249","message":"Unknown method `map` on i32[] (supported: push, pop, indexOf, join, set, fill)"}
|
|
52
|
+
{"file":"p.ts","line":3,"column":17,"endLine":3,"endColumn":20,"severity":"error","code":"NL2249","message":"Unknown method `map` on i32[] (supported: push, pop, indexOf, join, set, fill, sort)"}
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
- **Key on `code`, never on `message`.** A code is a promise: `NL2249` means
|
|
@@ -159,7 +159,7 @@ rejects. This table is the highest-value part of the page.
|
|
|
159
159
|
| `x == y` | `Loose equality is forbidden` | `x === y` |
|
|
160
160
|
| `throw new Error(m)` | `` `throw` is forbidden `` | `return Err(m)`, or `panic(m)` to end the process |
|
|
161
161
|
| `try { … } catch { … }` | `` `try`/`catch`/`finally` is forbidden `` | `Result<T, E>` and `isErr()` |
|
|
162
|
-
| `xs.map(f)`, `.filter`, `.reduce`, `.forEach`, `.slice`, `.
|
|
162
|
+
| `xs.map(f)`, `.filter`, `.reduce`, `.forEach`, `.slice`, `.shift` | `` Unknown method `map` on i32[] (supported: push, pop, indexOf, join, set, fill, sort) `` | a `for` loop, or a top-level `map(xs, f)` with a [function parameter](#function-parameters) |
|
|
163
163
|
| `s.toUpperCase()`, `s.split()`, `s.trim()`, `s.replace()` | `` Unknown method … on string `` | index bytes with `charCodeAt` / `substring` |
|
|
164
164
|
| `a?.b`, `a ?? b` | `?.` only in a chain that ends in `??`; `??` only on a `T \| null`, a chain or `m.get(k)` | `a?.b ?? d`, `a ?? d`, or `if (a !== null)` first |
|
|
165
165
|
| `x as T`, `<T>x`, `x satisfies T` | `Unsupported expression in Phase 1: AsExpression` (to `any` or `unknown`: `` Type assertion to `any` is forbidden ``); `x!` is a syntax error | there are no casts; `implements` is the only widening |
|
|
@@ -852,7 +852,8 @@ export const main = (): i32 => {
|
|
|
852
852
|
(`` The arrow reads `k`, a `let` of the function it is written in: ... bind the value to a `const` before the call ``),
|
|
853
853
|
read `this` (bind `const self = this`), or read a function parameter of its
|
|
854
854
|
function. An arrow inside another arrow, or given to a method, a
|
|
855
|
-
constructor, `expose` or `exposeWith`, captures nothing.
|
|
855
|
+
constructor, `expose` or `exposeWith`, captures nothing; `xs.sort(cmp)`'s
|
|
856
|
+
comparator is the exception, and captures as an arrow argument does.
|
|
856
857
|
- **The argument's type must match exactly**: `(x: i32) => i64` for a
|
|
857
858
|
`(x: i32) => i32` parameter is refused. A generic function cannot be passed
|
|
858
859
|
by name — write `(x) => identity(x)`.
|
|
@@ -2138,7 +2139,12 @@ there is no `undefined` to return), `a.indexOf(v)`, `a.join(sep)` — **`join` i
|
|
|
2138
2139
|
`string[]` only** — and, on an array of numbers only, `dst.set(src[, offset])`
|
|
2139
2140
|
(one `memmove`; a range past the end panics) and `a.fill(v[, start[, end]])`
|
|
2140
2141
|
(ends clamped and counted back from the end when negative, as in JavaScript;
|
|
2141
|
-
never panics). Both are statements.
|
|
2142
|
+
never panics). Both are statements. `a.sort(cmp)` sorts in place, stably, and
|
|
2143
|
+
answers `a` — the same order Node gives; the comparator is required (`a.sort()`
|
|
2144
|
+
is refused, since JavaScript would compare strings), a readonly array and
|
|
2145
|
+
`process.argv` are refused, `a - b` panics on `i32` values a full range apart
|
|
2146
|
+
(compare with `a > b ? 1 : a < b ? -1 : 0` there), and the comparator may read the caller's `const` locals and parameters but not
|
|
2147
|
+
a `let`, as any arrow argument may. That is every array method there is. A
|
|
2142
2148
|
window into a buffer is `(buf, offset, length)`; there is no view type.
|
|
2143
2149
|
|
|
2144
2150
|
**Strings.** `s.length` (**bytes**), `s.charCodeAt(i)` (the byte, bounds-checked),
|
package/package.json
CHANGED
package/std/README.md
CHANGED
|
@@ -12,6 +12,7 @@ whatever program imports it, and subject to the same rules as `examples/` or
|
|
|
12
12
|
| [`testing.ts`](./testing.ts) | a test runner: a `Suite` a program drives with straight-line assertions, printing the `PASS` / `FAIL` / `SKIP` lines the repository's own harness prints, and answering the exit code |
|
|
13
13
|
| [`text.ts`](./text.ts) | the string operations a program would otherwise write inline: `splitLines`, `splitWhitespace`, `trim` and its halves, `contains`, `replaceAll`, `firstDifference` over two arrays of lines, `codePointLength`, the number of Unicode code points (what JSON Schema's `minLength` counts, where `.length` counts UTF-8 bytes natively and UTF-16 units under Node; it answers the same on both by asking which it is reading), and `indexOfAny(text, bytes, from)`, the first index at or after `from` whose byte is one of 1 to 16 ASCII `bytes`. Its walker is the meaning and what runs under Node; natively the compiler replaces the one call to it with the runtime kernel `nish_str_index_of_any`, which reads 16 or 32 bytes at a time and is variable-time ([`docs/LANGUAGE.md`](../docs/LANGUAGE.md#byte-search-indexofany-in-nishtext)). `tests/link/std_text_index_of_any*` are its programs |
|
|
14
14
|
| [`math.ts`](./math.ts) | `Math.tan`, `Math.atan2` and `Math.hypot` on `f64`, ported from fdlibm (`LICENSE-fdlibm`) so that every target, wasm included, answers alike without libm. A program never imports it: a call to one of the three loads it, and the compiler copies the functions the call reaches into the calling module as `internal` ones. JavaScript's answer for every NaN, signed zero and infinity, and within one ulp elsewhere (0.83, 0.84 and 0.93 ulp measured against mpmath). `tests/cases/math_std_edges` pins the special values |
|
|
15
|
+
| [`sort.ts`](./sort.ts) | `xs.sort(cmp)` on any array: `sortWith`, a stable merge sort over runs of 32 sorted by insertion — one scratch array released before it returns, a pair of runs already in order copied without merging, no bounds check left in it — so its order is element for element Node's `Array.prototype.sort`. Like `math.ts` it is one the compiler knows about: a program never imports it, a `.sort(cmp)` call loads it, the checker rewrites the call to `sortWith`, and each comparator's instantiation — the comparator called directly, no function pointer — is copied into the module that sorts. Under Node the call stays the platform's own. `tests/cases/arr_sort_cmp*` are its programs, a 10,000-element sort with ties among them |
|
|
15
16
|
| [`regex.ts`](./regex.ts) | Regular expressions in JavaScript's syntax with the `u` flag's strictness, matched by a Pike VM in time linear in the input whatever the pattern; finding every match is one search per match, so `findAll` and its kin can be quadratic when a long branch is preferred to a short one that wins ([`docs/security/regex.md`](../docs/security/regex.md)). `regexCompile(pattern, flags)` answers a `Regex` or the error text; `test`, `exec` into a reused `RegexMatch`, `find`, `findAll`, `count`, `replace` and `replaceAll` with `$&`, `$1` and `$<name>`, and `split`. Flags `i`, `m`, `s` and `u`; `i` folds by Unicode's simple case folding, ASCII letters only to ASCII. `\p{…}` and `\P{…}` take Unicode 16.0's General_Category, Script, Script_Extensions and binary properties by JavaScript's names, from tables `scripts/gen-regex-unicode.mjs` generates into the module and checks against Node's `RegExp`; backreferences and lookaround are refused at compile time. Offsets are UTF-8 bytes. A search allocates nothing beyond its answer: the thread lists and capture tables are the `Regex`'s, so one `Regex` serves one thread at a time. A leading literal is found with `indexOf`, a small leading set with `indexOfAny`. `tests/link/std_regex` and `tests/link/std_regex_unicode` check it against JavaScript's own answers |
|
|
16
17
|
| [`json.ts`](./json.ts) | `jsonField(object, name)`: the value of one field of one flat JSON object, which is the shape the compiler's own `--json` diagnostics have. A reader and not a parser — it answers text, answers `null` for a field that is not there, and does not validate. `JsonWriter` is the other direction: any JSON value written into a caller-owned `u8[]`, commas and colons included, byte for byte what `JSON.stringify` writes, and allocating nothing per call except an `f64`'s digits, a temporary string the call gives back before it returns (`text()` and `jsonStringifyString` build strings by design); misuse and a full buffer set a sticky negative `JSON_WRITE_*` code in `failed` rather than panicking. `setIndent(n)` and `setIndentTab()` write what `JSON.stringify(v, null, n)` or `(v, null, "\t")` writes, with no allocation for the indent, and `setStrict(true)` makes NaN and the infinities the sticky `JSON_WRITE_NON_FINITE` instead of `null`; both modes are off by default and kept by `reset`. `jsonStringifyString(s)` is the string rule answered as a string. `stringBytes(bytes, off, len)` and `keyBytes(bytes, off, len)` write a window of a byte array as `string` and `key` write a string of those bytes, allocating nothing, and refuse a window outside the array with `JSON_WRITE_BAD_WINDOW` |
|
|
17
18
|
| [`json-tokenizer.ts`](./json-tokenizer.ts) | `JsonTokenizer`: a validating, streaming tokenizer for RFC 8259 JSON held in a window of a `u8[]`. `next()` answers one `JSON_TOK_*` token at a time — object and array openers and closers, keys, strings, numbers and the three literals — then `JSON_TOK_END`, or a negative `JSON_TOK_ERR_*` code that sticks in `failed`, with the byte in `errorAt`. Strings are checked as UTF-8 and a lone surrogate escape is refused; `stringInto` decodes the current string into the caller's array and `keyEquals` compares it with a name, neither allocating. `numberF64` is correctly rounded; `isInteger` and `numberI64` read an integer that fits an `i64`, and `isU64` and `numberU64` one that fits a `u64`. `isIntegral` asks whether a number's value is whole however it is written (`1.0`, `1e2`, `1e400`), JSON Schema's "integer", from the text and at any exponent. `skipValue` steps over a value. `JsonLimits` caps nesting depth, string, number and total bytes, each with its own code; `reset` reuses the tokenizer, so a reused body keeps the arena flat. Duplicate keys are reported as tokens. No input panics and no byte outside the window is read; [`docs/security/json.md`](../docs/security/json.md) is its record, and `tests/link/std_json_tokenizer` runs JSONTestSuite and a seeded fuzz |
|
|
@@ -95,6 +96,7 @@ is listed in [`SECURITY.md`](../SECURITY.md#threat-model).
|
|
|
95
96
|
| [`crypto/jwt.ts`](./crypto/jwt.ts) | JSON Web Tokens in the JWS compact form. `JwtVerifier(maxToken)` holds its buffers, sized once, and `verify` / `verifyString(key, token, now)` answer `JWT_OK` or a `JWT_ERR_*` code: the key decides the algorithm (`alg` must name it exactly, so `none` and an HS header in front of an RSA key are refused), the header holds `alg` once and no `crit` or `b64`, base64url is strict, the signature is checked before the payload is read, and then `exp`, `nbf` and `iat` with a leeway, and `iss`, `aud` and `sub`, are checked. Keys: `jwtHmacKey` (HS256/384/512, keyed once through `HmacKey`), `jwtRsaKey` / `jwtRsaKeyParts` (RS256/384/512, 2048 bits or more), `jwtEcKey` (ES256, ES384), `jwtEd25519Key` (EdDSA), and the signing `jwtEs256SigningKey` / `jwtEd25519SigningKey`, each from a `Secret<u8[]>` it copies; `JwtKey.wipe()` zeroes the copy. `jwtSign` / `jwtSignString` / `jwtSignKid` mint a token under an HS, ES256 or EdDSA key. `peekKid` reads the unauthenticated `kid`, to choose a key | RFC 7515 Appendix A, RFC 7519, RFC 7518, RFC 8037 A.4, RFC 8725 |
|
|
96
97
|
| [`crypto/ct.ts`](./crypto/ct.ts) | `timingSafeEqual(a, b)`, which reads every byte whatever it holds, and `timingSafeEqualAt(a, aOff, b, bOff, len)` over two windows, which answers `false` for a window outside its array. Two lengths that differ answer `false` at once, because a length is public, and so does an array longer than 2^31 − 1 bytes | — |
|
|
97
98
|
| [`crypto/bcrypt.ts`](./crypto/bcrypt.ts) | `bcryptHash(password, salt, cost)`, the 60-character `$2b$` hash of a `Secret<u8[]>` password under a 16-byte salt and a cost of 4 to 31, and `bcryptVerify(password, hash)`, which reads `$2a$`, `$2b$` and `$2y$` hashes, refuses `$2x$`, and compares in constant time. The first 72 bytes of a password count, up to its first NUL, as under libxcrypt's `crypt_r` (where other implementations differ: [the interoperability table](../docs/security/crypto-bcrypt.md#interoperability)); the Blowfish state is wiped before either returns. `bcryptHash4(p0, p1, p2, p3, salts, cost)` and `bcryptVerify4(p0, p1, p2, p3, hashes)` do up to four at once, interleaved (four in the time of about 1.2 single hashes on one core), each lane the single path's answer; verify groups lanes by cost, and a malformed lane makes the whole call an `Err` naming it. `BCRYPT_MAX_PASSWORD`, `BCRYPT_HASH_LENGTH`, `BCRYPT_SALT_SIZE`, `BCRYPT_MIN_COST`, `BCRYPT_MAX_COST`. A `std/threads` task may call all four ([BCRYPT-9](../docs/security/crypto-bcrypt.md#findings)) | OpenBSD `bcrypt(3)`; crypt_blowfish 1.3 for `$2a$` and `$2y$` |
|
|
99
|
+
| [`crypto/bcrypt-batch.ts`](./crypto/bcrypt-batch.ts) | `bcryptBatcher({ width, holdNanos, cores, cost })`, a login batcher: `verify(password, hash, now)` and `hash(password, salt, now)` queue a request and answer its ticket, `waitNanos(now)` says when a run is due (a full batch, or the oldest request held `holdNanos`), and `run(now)` sends up to `width × cores` requests as `bcryptVerifyN` / `bcryptHashN` batches on one `std/threads` task per core and answers a `BcryptAnswer` (`ticket`, `ok`, `hash`, `error`) for each. It keeps and wipes its own copy of each password; a damaged stored hash answers its `Err` on its own row ([the record](../docs/security/crypto-bcrypt.md#the-login-batcher)) | — |
|
|
98
100
|
| [`crypto/base64url.ts`](./crypto/base64url.ts) | `base64urlEncode(data)` and `base64urlDecode(text)`, unpadded; `base64StdPadEncode(data)` and `base64StdPadDecode(text)`, the padded standard alphabet that SCRAM and PEM use, constant-time in both directions. These are not `nish/base64`, which encodes only, is not constant-time and is for pixels; the names differ because std modules share the program's one namespace. Decoding is strict, so every byte string has one spelling. `base64urlDecode` answers `null` for any `=`, a character outside the alphabet, a length of 1 mod 4 or nonzero unused low bits; `base64StdPadDecode` requires the padding, answering `null` unless the text is whole four-character groups with `=` only as the last one or two characters, every other character in the alphabet and the unused bits zero. Both refuse a text longer than 2^31 − 1 bytes, and `base64urlEncode` panics on such an array | RFC 4648 §4, §5, §10 (`tests/link/std_base64_padded`) |
|
|
99
101
|
| [`crypto/sha1.ts`](./crypto/sha1.ts) | `sha1(data)`, a fresh 20-byte digest, and `SHA1_SIZE` and `SHA1_BLOCK`. **Not for security**: SHA-1 is broken for collisions, and it is here only because RFC 6455 §4.2.2's `Sec-WebSocket-Accept` is defined on it (`nish/net/websocket` is its importer). One-shot only; it panics on an array longer than 2^31 − 1 bytes. It came after the #363 audit and has no record in `docs/security/` | FIPS 180-4 §6.1 |
|
|
100
102
|
| [`crypto/x25519.ts`](./crypto/x25519.ts) | `x25519(scalar, u)`, the shared secret as a `Secret<u8[]>`, and `x25519Base(scalar)`, the public key, each from a `Secret<u8[]>` scalar, on ten 25.5-bit limbs in `i64`. Either answers `null` unless its arguments are `X25519_SIZE` (32) bytes; the scalar is clamped on a copy, which is wiped | RFC 7748 §5.2, §6.1, Wycheproof `x25519_test` |
|
|
@@ -0,0 +1,702 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/crypto/bcrypt-batch` — a login batcher: it queues bcrypt verifies and
|
|
3
|
+
* hashes as they arrive, sends them as `bcryptVerifyN` / `bcryptHashN`
|
|
4
|
+
* batches, and runs the batches on `std/threads` tasks, one per core.
|
|
5
|
+
*
|
|
6
|
+
* import { bcryptCalibrate } from "nish/crypto/bcrypt"
|
|
7
|
+
* import { BcryptAnswer, BcryptBatcher, bcryptBatcher } from "nish/crypto/bcrypt-batch"
|
|
8
|
+
* import { availableParallelism, monotonicNanos } from "nish:process"
|
|
9
|
+
*
|
|
10
|
+
* const noHold: i64 = 0
|
|
11
|
+
* const made = bcryptBatcher({ width: bcryptCalibrate(), holdNanos: noHold, cores: availableParallelism(), cost: 10 })
|
|
12
|
+
* const batcher: BcryptBatcher = made.value
|
|
13
|
+
* // In the server's loop, for each login that arrives:
|
|
14
|
+
* const ticket: i32 = batcher.verify(password, storedHash, monotonicNanos())
|
|
15
|
+
* // ... wait at most batcher.waitNanos(now) for the next readiness event, then:
|
|
16
|
+
* for (const a of batcher.run(monotonicNanos())) { reply(a.ticket, a.ok) }
|
|
17
|
+
*
|
|
18
|
+
* **Why.** How full a batch is moves the time per password by more than any
|
|
19
|
+
* instruction-set work does: on one core a full six-lane cost-10 batch takes
|
|
20
|
+
* 15.7 ms per hash and a lone hash 56.9 ms (docs/security/crypto-bcrypt.md,
|
|
21
|
+
* "Speed"). A server whose logins arrive one at a time never fills a batch by
|
|
22
|
+
* itself; this module holds them until there are enough, or until a short
|
|
23
|
+
* window has passed.
|
|
24
|
+
*
|
|
25
|
+
* **When a batch is sent.** `run(now)` sends work when the queue holds at
|
|
26
|
+
* least `width` requests, or when the oldest has waited `holdNanos`; a hold
|
|
27
|
+
* of 0 sends whatever is queued at every `run`. It takes at most
|
|
28
|
+
* `width × cores` requests a run, the oldest first, and spreads them evenly
|
|
29
|
+
* over `min(cores, taken)` tasks, each running its share as one batch, so a
|
|
30
|
+
* run takes about one batch's time and the event loop gets control back
|
|
31
|
+
* between runs. Spreading rather than filling is deliberate: while a run is
|
|
32
|
+
* going nothing else can use the free cores, and a batch of two on each of
|
|
33
|
+
* three cores ends sooner than one of six on one. Under load the requests
|
|
34
|
+
* that arrive during a run fill the next one, so the batches fill on their
|
|
35
|
+
* own; at light load each request runs alone on a core, as a task per login
|
|
36
|
+
* would. `waitNanos(now)` says how long the loop may sleep before a `run` is
|
|
37
|
+
* due.
|
|
38
|
+
*
|
|
39
|
+
* **`run` blocks.** It opens a scope, runs the shares, one on the calling
|
|
40
|
+
* thread, and returns once every share has finished: about one batch, 60–95
|
|
41
|
+
* ms of cost 10 on the core this was measured on. That is the pause the
|
|
42
|
+
* password-hashing guide describes for a scope on the event loop's thread;
|
|
43
|
+
* give the batcher a thread of its own to avoid it.
|
|
44
|
+
*
|
|
45
|
+
* **The batcher keeps its own copy of each password.** `verify` and `hash`
|
|
46
|
+
* borrow the caller's `Secret<u8[]>`, as any function taking one does: they
|
|
47
|
+
* copy it, inside `exposeWith`, into a holder of the batcher's own (a class
|
|
48
|
+
* with one `readonly` `Secret` field), and the caller wipes its password as
|
|
49
|
+
* it would after `bcryptVerify`. `run` calls each copy's `wipe()` on the
|
|
50
|
+
* calling thread once the scope that read it has joined, so no task can
|
|
51
|
+
* still be reading it. A request that never runs (a batcher dropped with
|
|
52
|
+
* requests queued) is not wiped: call `run` until `pending()` is 0 before
|
|
53
|
+
* dropping one. The tasks call only `bcryptVerifyN`, `bcryptHashN` and
|
|
54
|
+
* `bcryptVerify`, which wipe everything they make
|
|
55
|
+
* (docs/security/crypto-bcrypt.md, BCRYPT-9, BCRYPT-11).
|
|
56
|
+
*
|
|
57
|
+
* **Every request gets its own answer.** A verify against a hash string that
|
|
58
|
+
* is not one makes `bcryptVerifyN` an `Err` for the whole batch; the share
|
|
59
|
+
* then verifies each of that batch's lanes alone, so only the damaged row
|
|
60
|
+
* answers the `Err` and the rest answer as they would have. A `hash` whose
|
|
61
|
+
* salt is not 16 bytes is answered with its `Err` at the next `run`, without
|
|
62
|
+
* being batched. The answers of a run come back in the order their requests
|
|
63
|
+
* were taken; match them to requests by `ticket`.
|
|
64
|
+
*
|
|
65
|
+
* Names carry the `bcryptBatch` prefix because a `std/` module's private
|
|
66
|
+
* functions share the importing program's flat symbol namespace
|
|
67
|
+
* (`docs/wp26-stdlib.md` §3e).
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
import {
|
|
71
|
+
BCRYPT_MAX_COST,
|
|
72
|
+
BCRYPT_MIN_COST,
|
|
73
|
+
bcryptHashN,
|
|
74
|
+
bcryptVerify,
|
|
75
|
+
bcryptVerifyN,
|
|
76
|
+
} from "nish/crypto/bcrypt"
|
|
77
|
+
import { Secret, expose, exposeWith, secret, wipe } from "nish:secret"
|
|
78
|
+
import { scope } from "nish/threads"
|
|
79
|
+
|
|
80
|
+
/** A request's kind: a verify of a password against a stored hash. */
|
|
81
|
+
const BCRYPT_BATCH_VERIFY: i32 = 0
|
|
82
|
+
|
|
83
|
+
/** A request's kind: a new hash of a password under a salt. */
|
|
84
|
+
const BCRYPT_BATCH_HASH: i32 = 1
|
|
85
|
+
|
|
86
|
+
/** The most lanes `bcryptVerifyN` and `bcryptHashN` take. */
|
|
87
|
+
const BCRYPT_BATCH_MAX_WIDTH: i32 = 6
|
|
88
|
+
|
|
89
|
+
/** How a batcher sends its work: see `bcryptBatcher`. */
|
|
90
|
+
export interface BcryptBatchConfig {
|
|
91
|
+
/** Lanes per batch, 1 to 6: `bcryptCalibrate()`'s answer. */
|
|
92
|
+
width: i32
|
|
93
|
+
/** How long the oldest request may wait for a batch to fill, in nanoseconds; 0 never waits. */
|
|
94
|
+
holdNanos: i64
|
|
95
|
+
/** How many tasks a run may use, at least 1: `availableParallelism()`. */
|
|
96
|
+
cores: i32
|
|
97
|
+
/** The cost `hash` requests are made at, 4 to 31. Verifies take theirs from the hash. */
|
|
98
|
+
cost: i32
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** What a request answered, under the ticket `verify` or `hash` gave it. */
|
|
102
|
+
export class BcryptAnswer {
|
|
103
|
+
/** The ticket of the request. */
|
|
104
|
+
ticket: i32
|
|
105
|
+
/** A verify: whether the password is the hash's. A hash: whether it was made. False with an error. */
|
|
106
|
+
ok: boolean
|
|
107
|
+
/** A hash request's `$2b$` hash; "" for a verify and for an error. */
|
|
108
|
+
hash: string
|
|
109
|
+
/** "" unless the request is an `Err`, as `bcryptVerify` or `bcryptHash` words it. */
|
|
110
|
+
error: string
|
|
111
|
+
|
|
112
|
+
constructor(ticket: i32, ok: boolean, hash: string, error: string) {
|
|
113
|
+
this.ticket = ticket
|
|
114
|
+
this.ok = ok
|
|
115
|
+
this.hash = hash
|
|
116
|
+
this.error = error
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The batcher's copy of one queued password: a holder, so it may sit in the
|
|
122
|
+
* queue's array, made at the password's length and filled by `bcryptBatchTake`.
|
|
123
|
+
*/
|
|
124
|
+
class BcryptBatchKey {
|
|
125
|
+
readonly key: Secret<u8[]>
|
|
126
|
+
|
|
127
|
+
constructor(length: i32) {
|
|
128
|
+
this.key = secret(new Array<u8>(length))
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
wipe(): void {
|
|
132
|
+
wipe(this.key)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** A password's length, the one fact about it the batcher reads outside a batch. */
|
|
137
|
+
const bcryptBatchLengthOf = (bytes: u8[]): i32 => toI32(bytes.length)
|
|
138
|
+
|
|
139
|
+
/** Copies `from` into the exposed key `into`, which has its length. */
|
|
140
|
+
const bcryptBatchCopy = (into: u8[], from: u8[]): i32 => {
|
|
141
|
+
for (let i: i32 = 0; i < toI32(into.length) && i < toI32(from.length); i++) {
|
|
142
|
+
into[i] = from[i]
|
|
143
|
+
}
|
|
144
|
+
return 0
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Copies the exposed password `bytes` into `held`'s key. */
|
|
148
|
+
const bcryptBatchFill = (bytes: u8[], held: BcryptBatchKey): i32 =>
|
|
149
|
+
exposeWith(held.key, bytes, bcryptBatchCopy)
|
|
150
|
+
|
|
151
|
+
/** A new holder with a copy of `password`; the caller keeps, and wipes, its own. */
|
|
152
|
+
const bcryptBatchTake = (password: Secret<u8[]>): BcryptBatchKey => {
|
|
153
|
+
const held: BcryptBatchKey = new BcryptBatchKey(expose(password, bcryptBatchLengthOf))
|
|
154
|
+
exposeWith(password, held, bcryptBatchFill)
|
|
155
|
+
return held
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* One task's work: requests in the order the share takes them. The task
|
|
160
|
+
* reads it and writes none of it; its answer is one string a request, "1"
|
|
161
|
+
* or "0" for a verify, the hash for a hash, and "!" and the error for an
|
|
162
|
+
* `Err`.
|
|
163
|
+
*/
|
|
164
|
+
class BcryptBatchShare {
|
|
165
|
+
passwords: BcryptBatchKey[]
|
|
166
|
+
kinds: i32[]
|
|
167
|
+
/** A verify's stored hash, "" for a hash. */
|
|
168
|
+
hashes: string[]
|
|
169
|
+
/** 16 bytes a request; a verify's are zeros and are not read. */
|
|
170
|
+
salts: u8[]
|
|
171
|
+
cost: i32
|
|
172
|
+
width: i32
|
|
173
|
+
|
|
174
|
+
constructor(cost: i32, width: i32) {
|
|
175
|
+
this.passwords = []
|
|
176
|
+
this.kinds = []
|
|
177
|
+
this.hashes = []
|
|
178
|
+
this.salts = []
|
|
179
|
+
this.cost = cost
|
|
180
|
+
this.width = width
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** The holder at `list[i]` of `share`, or at `list[0]` past the batch's end (that lane is then passed as null). */
|
|
185
|
+
const bcryptBatchLane = (share: BcryptBatchShare, list: i32[], i: i32): BcryptBatchKey => {
|
|
186
|
+
const at: i32 = i < toI32(list.length) ? list[i] : list[0]
|
|
187
|
+
return share.passwords[at]
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** The answer string of one verify alone, for a lane of a batch that answered an `Err`. */
|
|
191
|
+
const bcryptBatchVerifyAlone = (held: BcryptBatchKey, hash: string): string => {
|
|
192
|
+
const said: Result<boolean, string> = bcryptVerify(held.key, hash)
|
|
193
|
+
if (said.isErr()) {
|
|
194
|
+
return `!${said.error}`
|
|
195
|
+
}
|
|
196
|
+
return said.value ? "1" : "0"
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* One `bcryptVerifyN` over the first `n` of `list`: a holder's field is
|
|
201
|
+
* passed only as a call's argument, so each lane count is its own call.
|
|
202
|
+
*/
|
|
203
|
+
const bcryptBatchVerifyLanes = (
|
|
204
|
+
share: BcryptBatchShare,
|
|
205
|
+
list: i32[],
|
|
206
|
+
n: i32,
|
|
207
|
+
hashes: string[]
|
|
208
|
+
): Result<boolean[], string> => {
|
|
209
|
+
const a: BcryptBatchKey = bcryptBatchLane(share, list, 0)
|
|
210
|
+
const b: BcryptBatchKey = bcryptBatchLane(share, list, 1)
|
|
211
|
+
const c: BcryptBatchKey = bcryptBatchLane(share, list, 2)
|
|
212
|
+
const d: BcryptBatchKey = bcryptBatchLane(share, list, 3)
|
|
213
|
+
const e: BcryptBatchKey = bcryptBatchLane(share, list, 4)
|
|
214
|
+
const f: BcryptBatchKey = bcryptBatchLane(share, list, 5)
|
|
215
|
+
if (n <= 1) {
|
|
216
|
+
return bcryptVerifyN(a.key, null, null, null, null, null, hashes)
|
|
217
|
+
}
|
|
218
|
+
if (n === 2) {
|
|
219
|
+
return bcryptVerifyN(a.key, b.key, null, null, null, null, hashes)
|
|
220
|
+
}
|
|
221
|
+
if (n === 3) {
|
|
222
|
+
return bcryptVerifyN(a.key, b.key, c.key, null, null, null, hashes)
|
|
223
|
+
}
|
|
224
|
+
if (n === 4) {
|
|
225
|
+
return bcryptVerifyN(a.key, b.key, c.key, d.key, null, null, hashes)
|
|
226
|
+
}
|
|
227
|
+
if (n === 5) {
|
|
228
|
+
return bcryptVerifyN(a.key, b.key, c.key, d.key, e.key, null, hashes)
|
|
229
|
+
}
|
|
230
|
+
return bcryptVerifyN(a.key, b.key, c.key, d.key, e.key, f.key, hashes)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** One `bcryptHashN` over the first `n` of `list`, as `bcryptBatchVerifyLanes` is for verifies. */
|
|
234
|
+
const bcryptBatchHashLanes = (
|
|
235
|
+
share: BcryptBatchShare,
|
|
236
|
+
list: i32[],
|
|
237
|
+
n: i32,
|
|
238
|
+
salts: u8[]
|
|
239
|
+
): Result<string[], string> => {
|
|
240
|
+
const a: BcryptBatchKey = bcryptBatchLane(share, list, 0)
|
|
241
|
+
const b: BcryptBatchKey = bcryptBatchLane(share, list, 1)
|
|
242
|
+
const c: BcryptBatchKey = bcryptBatchLane(share, list, 2)
|
|
243
|
+
const d: BcryptBatchKey = bcryptBatchLane(share, list, 3)
|
|
244
|
+
const e: BcryptBatchKey = bcryptBatchLane(share, list, 4)
|
|
245
|
+
const f: BcryptBatchKey = bcryptBatchLane(share, list, 5)
|
|
246
|
+
if (n <= 1) {
|
|
247
|
+
return bcryptHashN(a.key, null, null, null, null, null, salts, share.cost)
|
|
248
|
+
}
|
|
249
|
+
if (n === 2) {
|
|
250
|
+
return bcryptHashN(a.key, b.key, null, null, null, null, salts, share.cost)
|
|
251
|
+
}
|
|
252
|
+
if (n === 3) {
|
|
253
|
+
return bcryptHashN(a.key, b.key, c.key, null, null, null, salts, share.cost)
|
|
254
|
+
}
|
|
255
|
+
if (n === 4) {
|
|
256
|
+
return bcryptHashN(a.key, b.key, c.key, d.key, null, null, salts, share.cost)
|
|
257
|
+
}
|
|
258
|
+
if (n === 5) {
|
|
259
|
+
return bcryptHashN(a.key, b.key, c.key, d.key, e.key, null, salts, share.cost)
|
|
260
|
+
}
|
|
261
|
+
return bcryptHashN(a.key, b.key, c.key, d.key, e.key, f.key, salts, share.cost)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** The verifies of `share` at `list[from..from + n)` as one `bcryptVerifyN`, each lane's answer into `out` at its index. */
|
|
265
|
+
const bcryptBatchVerifyRun = (
|
|
266
|
+
share: BcryptBatchShare,
|
|
267
|
+
list: i32[],
|
|
268
|
+
from: i32,
|
|
269
|
+
n: i32,
|
|
270
|
+
out: string[]
|
|
271
|
+
): void => {
|
|
272
|
+
const lanes: i32[] = []
|
|
273
|
+
for (let i: i32 = from; i < from + n && i < toI32(list.length); i++) {
|
|
274
|
+
if (i >= 0) {
|
|
275
|
+
lanes.push(list[i])
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (toI32(lanes.length) === 0) {
|
|
279
|
+
return
|
|
280
|
+
}
|
|
281
|
+
const hashes: string[] = ["", "", "", "", "", ""]
|
|
282
|
+
for (let s: i32 = 0; s < toI32(lanes.length) && s < 6; s++) {
|
|
283
|
+
const at: i32 = lanes[s]
|
|
284
|
+
if (at >= 0 && at < toI32(share.hashes.length)) {
|
|
285
|
+
hashes[s] = share.hashes[at]
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const said: Result<boolean[], string> = bcryptBatchVerifyLanes(share, lanes, toI32(lanes.length), hashes)
|
|
289
|
+
for (let s: i32 = 0; s < toI32(lanes.length); s++) {
|
|
290
|
+
const at: i32 = lanes[s]
|
|
291
|
+
let line: string = "0"
|
|
292
|
+
if (said.isErr()) {
|
|
293
|
+
// One damaged hash fails the whole call: answer each lane alone.
|
|
294
|
+
if (at >= 0 && at < toI32(share.passwords.length) && s < toI32(hashes.length)) {
|
|
295
|
+
line = bcryptBatchVerifyAlone(share.passwords[at], hashes[s])
|
|
296
|
+
}
|
|
297
|
+
} else {
|
|
298
|
+
const ok: boolean[] = said.value
|
|
299
|
+
line = s < toI32(ok.length) && ok[s] ? "1" : "0"
|
|
300
|
+
}
|
|
301
|
+
if (at >= 0 && at < toI32(out.length)) {
|
|
302
|
+
out[at] = line
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** The hashes of `share` at `list[from..from + n)` as one `bcryptHashN`, each lane's answer into `out` at its index. */
|
|
308
|
+
const bcryptBatchHashRun = (share: BcryptBatchShare, list: i32[], from: i32, n: i32, out: string[]): void => {
|
|
309
|
+
const lanes: i32[] = []
|
|
310
|
+
for (let i: i32 = from; i < from + n && i < toI32(list.length); i++) {
|
|
311
|
+
if (i >= 0) {
|
|
312
|
+
lanes.push(list[i])
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (toI32(lanes.length) === 0) {
|
|
316
|
+
return
|
|
317
|
+
}
|
|
318
|
+
const salts: u8[] = new Array<u8>(96)
|
|
319
|
+
for (let s: i32 = 0; s < toI32(lanes.length) && s < 6; s++) {
|
|
320
|
+
const at: i32 = lanes[s]
|
|
321
|
+
for (let j: i32 = 0; j < 16 && 16 * at + j < toI32(share.salts.length); j++) {
|
|
322
|
+
if (at >= 0) {
|
|
323
|
+
salts[16 * s + j] = share.salts[16 * at + j]
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
const made: Result<string[], string> = bcryptBatchHashLanes(share, lanes, toI32(lanes.length), salts)
|
|
328
|
+
for (let s: i32 = 0; s < toI32(lanes.length); s++) {
|
|
329
|
+
const at: i32 = lanes[s]
|
|
330
|
+
let line: string = ""
|
|
331
|
+
if (made.isErr()) {
|
|
332
|
+
line = `!${made.error}`
|
|
333
|
+
} else {
|
|
334
|
+
const all: string[] = made.value
|
|
335
|
+
line = s < toI32(all.length) ? all[s] : ""
|
|
336
|
+
}
|
|
337
|
+
if (at >= 0 && at < toI32(out.length)) {
|
|
338
|
+
out[at] = line
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** The indices of `share`'s requests of `kind`, in order. */
|
|
344
|
+
const bcryptBatchOfKind = (share: BcryptBatchShare, kind: i32): i32[] => {
|
|
345
|
+
const list: i32[] = []
|
|
346
|
+
for (let i: i32 = 0; i < toI32(share.kinds.length); i++) {
|
|
347
|
+
if (share.kinds[i] === kind) {
|
|
348
|
+
list.push(i)
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return list
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* One task: every request of `share`, the verifies and then the hashes, in
|
|
356
|
+
* batches of up to `width`. It reads `share` and writes only arrays it made,
|
|
357
|
+
* and answers one line a request, joined by "\n": a task hands back a string,
|
|
358
|
+
* not an array of them. No answer holds a newline: a `$2b$` hash is base-64
|
|
359
|
+
* and bcrypt's errors are one line.
|
|
360
|
+
*/
|
|
361
|
+
const bcryptBatchShareRun = (share: BcryptBatchShare): string => {
|
|
362
|
+
const out: string[] = []
|
|
363
|
+
for (let i: i32 = 0; i < toI32(share.kinds.length); i++) {
|
|
364
|
+
out.push("")
|
|
365
|
+
}
|
|
366
|
+
const width: i32 = share.width < 1 ? 1 : share.width
|
|
367
|
+
const verifies: i32[] = bcryptBatchOfKind(share, BCRYPT_BATCH_VERIFY)
|
|
368
|
+
for (let i: i32 = 0; i < toI32(verifies.length); i += width) {
|
|
369
|
+
bcryptBatchVerifyRun(share, verifies, i, width, out)
|
|
370
|
+
}
|
|
371
|
+
const hashes: i32[] = bcryptBatchOfKind(share, BCRYPT_BATCH_HASH)
|
|
372
|
+
for (let i: i32 = 0; i < toI32(hashes.length); i += width) {
|
|
373
|
+
bcryptBatchHashRun(share, hashes, i, width, out)
|
|
374
|
+
}
|
|
375
|
+
return out.join("\n")
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* The cost a verify's stored hash names, read from its fourth and fifth
|
|
380
|
+
* characters, or -1 when they are not two digits; only the order of a run
|
|
381
|
+
* reads it, so that one cost's verifies share a batch.
|
|
382
|
+
*/
|
|
383
|
+
const bcryptBatchCostOf = (hash: string): i32 => {
|
|
384
|
+
if (toI32(hash.length) < 7) {
|
|
385
|
+
return -1
|
|
386
|
+
}
|
|
387
|
+
const tens: i32 = toI32(hash.charCodeAt(4)) - 48
|
|
388
|
+
const ones: i32 = toI32(hash.charCodeAt(5)) - 48
|
|
389
|
+
if (tens < 0 || tens > 9 || ones < 0 || ones > 9) {
|
|
390
|
+
return -1
|
|
391
|
+
}
|
|
392
|
+
return tens * 10 + ones
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** The lines of a task's answer, split at "\n". */
|
|
396
|
+
const bcryptBatchLines = (joined: string): string[] => {
|
|
397
|
+
const lines: string[] = []
|
|
398
|
+
let from: i32 = 0
|
|
399
|
+
while (from >= 0 && from <= toI32(joined.length)) {
|
|
400
|
+
const at: i32 = toI32(joined.indexOf("\n", from))
|
|
401
|
+
if (at < from || at > toI32(joined.length)) {
|
|
402
|
+
lines.push(joined.slice(from))
|
|
403
|
+
return lines
|
|
404
|
+
}
|
|
405
|
+
lines.push(joined.slice(from, at))
|
|
406
|
+
from = at + 1
|
|
407
|
+
}
|
|
408
|
+
return lines
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/** The answer a share's string stands for. */
|
|
412
|
+
const bcryptBatchAnswerOf = (ticket: i32, kind: i32, said: string): BcryptAnswer => {
|
|
413
|
+
if (said.startsWith("!")) {
|
|
414
|
+
return new BcryptAnswer(ticket, false, "", said.substring(1))
|
|
415
|
+
}
|
|
416
|
+
if (kind === BCRYPT_BATCH_HASH) {
|
|
417
|
+
return new BcryptAnswer(ticket, said !== "", said, "")
|
|
418
|
+
}
|
|
419
|
+
return new BcryptAnswer(ticket, said === "1", "", "")
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* A queue of bcrypt requests and the policy that sends them: made by
|
|
424
|
+
* `bcryptBatcher`. `verify` and `hash` queue, `run` sends and answers,
|
|
425
|
+
* `waitNanos` says when the next `run` is due.
|
|
426
|
+
*/
|
|
427
|
+
export class BcryptBatcher {
|
|
428
|
+
holdNanos: i64
|
|
429
|
+
/** The queue, from `head`: each request's copy of its password, kind, stored hash, salt (16 bytes each), ticket and arrival. */
|
|
430
|
+
passwords: BcryptBatchKey[]
|
|
431
|
+
kinds: i32[]
|
|
432
|
+
hashes: string[]
|
|
433
|
+
salts: u8[]
|
|
434
|
+
tickets: i32[]
|
|
435
|
+
arrived: i64[]
|
|
436
|
+
/** Answers made without a batch (a salt of the wrong length), handed out by the next `run`. */
|
|
437
|
+
early: BcryptAnswer[]
|
|
438
|
+
width: i32
|
|
439
|
+
cores: i32
|
|
440
|
+
cost: i32
|
|
441
|
+
head: i32
|
|
442
|
+
/** The next ticket. */
|
|
443
|
+
next: i32
|
|
444
|
+
|
|
445
|
+
constructor(config: BcryptBatchConfig) {
|
|
446
|
+
this.width = config.width
|
|
447
|
+
this.holdNanos = config.holdNanos
|
|
448
|
+
this.cores = config.cores
|
|
449
|
+
this.cost = config.cost
|
|
450
|
+
this.passwords = []
|
|
451
|
+
this.kinds = []
|
|
452
|
+
this.hashes = []
|
|
453
|
+
this.salts = []
|
|
454
|
+
this.tickets = []
|
|
455
|
+
this.arrived = []
|
|
456
|
+
this.head = 0
|
|
457
|
+
this.early = []
|
|
458
|
+
this.next = 0
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/** How many requests are queued or answered and not yet handed out by `run`. */
|
|
462
|
+
pending(): i32 {
|
|
463
|
+
return toI32(this.kinds.length) - this.head + toI32(this.early.length)
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Queues a verify of `password` against `hash`, which arrived at `now`
|
|
468
|
+
* (`monotonicNanos()`), and answers its ticket. The batcher queues a copy
|
|
469
|
+
* of `password` and wipes the copy once the verify has run; the caller
|
|
470
|
+
* wipes its own.
|
|
471
|
+
*/
|
|
472
|
+
verify(password: Secret<u8[]>, hash: string, now: i64): i32 {
|
|
473
|
+
const ticket: i32 = this.next
|
|
474
|
+
this.next = this.next + 1
|
|
475
|
+
this.enqueue(password, BCRYPT_BATCH_VERIFY, hash, new Array<u8>(16), ticket, now)
|
|
476
|
+
return ticket
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Queues a new hash of `password` under `salt` (16 bytes, fresh from
|
|
481
|
+
* `crypto.getRandomValues`) at the batcher's cost, which arrived at `now`,
|
|
482
|
+
* and answers its ticket. The batcher queues a copy of `password` and
|
|
483
|
+
* wipes the copy once the hash is made; a salt of another length is
|
|
484
|
+
* answered with its `Err` by the next `run`, and no copy is made.
|
|
485
|
+
*/
|
|
486
|
+
hash(password: Secret<u8[]>, salt: u8[], now: i64): i32 {
|
|
487
|
+
const ticket: i32 = this.next
|
|
488
|
+
this.next = this.next + 1
|
|
489
|
+
if (toI32(salt.length) !== 16) {
|
|
490
|
+
this.early.push(new BcryptAnswer(ticket, false, "", "bcrypt: the salt is not 16 bytes"))
|
|
491
|
+
return ticket
|
|
492
|
+
}
|
|
493
|
+
this.enqueue(password, BCRYPT_BATCH_HASH, "", salt, ticket, now)
|
|
494
|
+
return ticket
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/** Appends one request to the queue. */
|
|
498
|
+
enqueue(password: Secret<u8[]>, kind: i32, hash: string, salt: u8[], ticket: i32, now: i64): void {
|
|
499
|
+
this.passwords.push(bcryptBatchTake(password))
|
|
500
|
+
this.kinds.push(kind)
|
|
501
|
+
this.hashes.push(hash)
|
|
502
|
+
for (let j: i32 = 0; j < 16 && j < toI32(salt.length); j++) {
|
|
503
|
+
this.salts.push(salt[j])
|
|
504
|
+
}
|
|
505
|
+
this.tickets.push(ticket)
|
|
506
|
+
this.arrived.push(now)
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Nanoseconds from `now` until a `run` is due: 0 when it is due now (a full
|
|
511
|
+
* batch is queued, the oldest request has waited `holdNanos`, or an answer
|
|
512
|
+
* is waiting), -1 when nothing is queued.
|
|
513
|
+
*/
|
|
514
|
+
waitNanos(now: i64): i64 {
|
|
515
|
+
if (toI32(this.early.length) > 0) {
|
|
516
|
+
return 0
|
|
517
|
+
}
|
|
518
|
+
const queued: i32 = toI32(this.kinds.length) - this.head
|
|
519
|
+
if (queued <= 0) {
|
|
520
|
+
return -1
|
|
521
|
+
}
|
|
522
|
+
if (queued >= this.width || this.head >= toI32(this.arrived.length)) {
|
|
523
|
+
return 0
|
|
524
|
+
}
|
|
525
|
+
const waited: i64 = now - this.arrived[this.head]
|
|
526
|
+
return waited >= this.holdNanos ? 0 : this.holdNanos - waited
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Sends what is due and answers it: nothing (bar answers made without a
|
|
531
|
+
* batch) until `waitNanos(now)` is 0, then up to `width × cores` of the
|
|
532
|
+
* oldest requests, spread evenly over up to `cores` tasks, each running
|
|
533
|
+
* its share as batches of up to `width`. Returns once every task has
|
|
534
|
+
* finished, with the copy of each password it ran wiped.
|
|
535
|
+
*/
|
|
536
|
+
run(now: i64): BcryptAnswer[] {
|
|
537
|
+
const answers: BcryptAnswer[] = []
|
|
538
|
+
for (const a of this.early) {
|
|
539
|
+
answers.push(a)
|
|
540
|
+
}
|
|
541
|
+
this.early = []
|
|
542
|
+
if (this.waitNanos(now) !== 0) {
|
|
543
|
+
return answers
|
|
544
|
+
}
|
|
545
|
+
const queued: i32 = toI32(this.kinds.length) - this.head
|
|
546
|
+
if (queued <= 0) {
|
|
547
|
+
return answers
|
|
548
|
+
}
|
|
549
|
+
const most: i32 = this.width * this.cores
|
|
550
|
+
const taken: i32 = queued < most ? queued : most
|
|
551
|
+
const order: i32[] = this.orderOf(taken)
|
|
552
|
+
const tasks: i32 = taken < this.cores ? taken : this.cores
|
|
553
|
+
const shares: BcryptBatchShare[] = []
|
|
554
|
+
// Share k takes order[taken·k/tasks, taken·(k+1)/tasks): contiguous, so one cost's verifies stay together.
|
|
555
|
+
const owner: i32[] = []
|
|
556
|
+
for (let k: i32 = 0; k < tasks; k++) {
|
|
557
|
+
const share: BcryptBatchShare = new BcryptBatchShare(this.cost, this.width)
|
|
558
|
+
const lo: i32 = (taken * k) / tasks
|
|
559
|
+
const hi: i32 = (taken * (k + 1)) / tasks
|
|
560
|
+
for (let i: i32 = lo; i >= 0 && i < hi && i < toI32(order.length); i++) {
|
|
561
|
+
const at: i32 = order[i]
|
|
562
|
+
if (at >= 0 && at < toI32(this.passwords.length) && at < toI32(this.kinds.length)) {
|
|
563
|
+
share.passwords.push(this.passwords[at])
|
|
564
|
+
share.kinds.push(this.kinds[at])
|
|
565
|
+
share.hashes.push(at < toI32(this.hashes.length) ? this.hashes[at] : "")
|
|
566
|
+
for (let j: i32 = 0; j < 16; j++) {
|
|
567
|
+
share.salts.push(16 * at + j < toI32(this.salts.length) ? this.salts[16 * at + j] : toU8(0))
|
|
568
|
+
}
|
|
569
|
+
owner.push(at)
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
shares.push(share)
|
|
573
|
+
}
|
|
574
|
+
const said: string[] = []
|
|
575
|
+
for (let k: i32 = 0; k < tasks; k++) {
|
|
576
|
+
said.push("")
|
|
577
|
+
}
|
|
578
|
+
{
|
|
579
|
+
using s = scope()
|
|
580
|
+
for (let k: i32 = 0; k < toI32(shares.length); k++) {
|
|
581
|
+
s.spawn(bcryptBatchShareRun, shares[k], said, k)
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
let flat: i32 = 0
|
|
585
|
+
for (let k: i32 = 0; k < toI32(said.length); k++) {
|
|
586
|
+
const one: string[] = bcryptBatchLines(said[k])
|
|
587
|
+
for (let i: i32 = 0; i < toI32(one.length) && flat < toI32(owner.length); i++) {
|
|
588
|
+
const at: i32 = owner[flat]
|
|
589
|
+
flat++
|
|
590
|
+
if (at >= 0 && at < toI32(this.tickets.length) && at < toI32(this.kinds.length)) {
|
|
591
|
+
answers.push(bcryptBatchAnswerOf(this.tickets[at], this.kinds[at], one[i]))
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
for (const at of order) {
|
|
596
|
+
if (at >= 0 && at < toI32(this.passwords.length)) {
|
|
597
|
+
const held: BcryptBatchKey = this.passwords[at]
|
|
598
|
+
held.wipe()
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
this.head = this.head + taken
|
|
602
|
+
this.compact()
|
|
603
|
+
return answers
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* The queue indices of the `taken` oldest requests, verifies by the cost
|
|
608
|
+
* their hash names and then hashes, each group in arrival order.
|
|
609
|
+
*/
|
|
610
|
+
orderOf(taken: i32): i32[] {
|
|
611
|
+
const keyed: i32[] = []
|
|
612
|
+
const keys: i32[] = []
|
|
613
|
+
for (let i: i32 = this.head; i < this.head + taken && i < toI32(this.kinds.length); i++) {
|
|
614
|
+
keyed.push(i)
|
|
615
|
+
let key: i32 = 1000
|
|
616
|
+
if (this.kinds[i] !== BCRYPT_BATCH_HASH) {
|
|
617
|
+
key = i < toI32(this.hashes.length) ? bcryptBatchCostOf(this.hashes[i]) : -1
|
|
618
|
+
}
|
|
619
|
+
keys.push(key)
|
|
620
|
+
}
|
|
621
|
+
// Stable: one pass a distinct key, lowest first, each in arrival order.
|
|
622
|
+
// A run takes at most 36 requests, of a few costs.
|
|
623
|
+
const sorted: i32[] = []
|
|
624
|
+
let last: i32 = -2
|
|
625
|
+
while (toI32(sorted.length) < toI32(keyed.length)) {
|
|
626
|
+
let low: i32 = 1001
|
|
627
|
+
for (const key of keys) {
|
|
628
|
+
if (key > last && key < low) {
|
|
629
|
+
low = key
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
if (low > 1000) {
|
|
633
|
+
return sorted
|
|
634
|
+
}
|
|
635
|
+
for (let i: i32 = 0; i < toI32(keyed.length) && i < toI32(keys.length); i++) {
|
|
636
|
+
if (keys[i] === low) {
|
|
637
|
+
sorted.push(keyed[i])
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
last = low
|
|
641
|
+
}
|
|
642
|
+
return sorted
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/** Drops the requests before `head` once they are most of the queue, so a long-lived batcher's arrays stay small. */
|
|
646
|
+
compact(): void {
|
|
647
|
+
if (this.head < 64 || this.head * 2 < toI32(this.kinds.length)) {
|
|
648
|
+
return
|
|
649
|
+
}
|
|
650
|
+
const passwords: BcryptBatchKey[] = []
|
|
651
|
+
const kinds: i32[] = []
|
|
652
|
+
const hashes: string[] = []
|
|
653
|
+
const salts: u8[] = []
|
|
654
|
+
const tickets: i32[] = []
|
|
655
|
+
const arrived: i64[] = []
|
|
656
|
+
for (let i: i32 = this.head; i < toI32(this.kinds.length); i++) {
|
|
657
|
+
if (
|
|
658
|
+
i < toI32(this.passwords.length) &&
|
|
659
|
+
i < toI32(this.hashes.length) &&
|
|
660
|
+
i < toI32(this.tickets.length) &&
|
|
661
|
+
i < toI32(this.arrived.length)
|
|
662
|
+
) {
|
|
663
|
+
passwords.push(this.passwords[i])
|
|
664
|
+
kinds.push(this.kinds[i])
|
|
665
|
+
hashes.push(this.hashes[i])
|
|
666
|
+
for (let j: i32 = 0; j < 16; j++) {
|
|
667
|
+
salts.push(16 * i + j < toI32(this.salts.length) ? this.salts[16 * i + j] : toU8(0))
|
|
668
|
+
}
|
|
669
|
+
tickets.push(this.tickets[i])
|
|
670
|
+
arrived.push(this.arrived[i])
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
this.passwords = passwords
|
|
674
|
+
this.kinds = kinds
|
|
675
|
+
this.hashes = hashes
|
|
676
|
+
this.salts = salts
|
|
677
|
+
this.tickets = tickets
|
|
678
|
+
this.arrived = arrived
|
|
679
|
+
this.head = 0
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* A new batcher for `config`, or an `Err` naming the field out of range: a
|
|
685
|
+
* width outside 1 to 6, a negative hold, fewer than one core, or a cost
|
|
686
|
+
* outside 4 to 31.
|
|
687
|
+
*/
|
|
688
|
+
export const bcryptBatcher = (config: BcryptBatchConfig): Result<BcryptBatcher, string> => {
|
|
689
|
+
if (config.width < 1 || config.width > BCRYPT_BATCH_MAX_WIDTH) {
|
|
690
|
+
return Err("bcrypt: the batch width is outside 1 to 6")
|
|
691
|
+
}
|
|
692
|
+
if (config.holdNanos < 0) {
|
|
693
|
+
return Err("bcrypt: the hold is negative")
|
|
694
|
+
}
|
|
695
|
+
if (config.cores < 1) {
|
|
696
|
+
return Err("bcrypt: a batcher needs at least one core")
|
|
697
|
+
}
|
|
698
|
+
if (config.cost < BCRYPT_MIN_COST || config.cost > BCRYPT_MAX_COST) {
|
|
699
|
+
return Err("bcrypt: the cost is outside 4 to 31")
|
|
700
|
+
}
|
|
701
|
+
return Ok(new BcryptBatcher(config))
|
|
702
|
+
}
|
package/std/crypto/bcrypt.ts
CHANGED
|
@@ -1024,7 +1024,9 @@ const bcryptLoadSlot = (state: Secret<u32[]>, password: Secret<u8[]>, slot: i32)
|
|
|
1024
1024
|
// the lane count changed (bench/crypto/bcrypt/gen-body.mjs writes them), not
|
|
1025
1025
|
// one loop over a lane count: the lanes' l and r must live in registers and
|
|
1026
1026
|
// their loads be scheduled together, which a loop over an array of lanes
|
|
1027
|
-
// does not give.
|
|
1027
|
+
// does not give. Two lanes take each byte by its own shift and write every
|
|
1028
|
+
// lane's bytes before any lane's update, the form all widths had before the
|
|
1029
|
+
// rotated copy, which measured slower there.
|
|
1028
1030
|
|
|
1029
1031
|
/** `bcryptBody4`'s interleave on two lanes. */
|
|
1030
1032
|
const bcryptBody2 = (st: u32[]): void => {
|
|
@@ -1089,35 +1091,41 @@ const bcryptBody3 = (st: u32[]): void => {
|
|
|
1089
1091
|
l1 ^= st[1042]
|
|
1090
1092
|
l2 ^= st[2084]
|
|
1091
1093
|
for (let n: i32 = 1; n < 17; n += 2) {
|
|
1094
|
+
const l0e: u32 = ctSelect((l0 >>> 8) | (l0 << 24), 0xffffffff, 0)
|
|
1092
1095
|
const l0a: i32 = toI32(l0 >>> 24) & 0xff
|
|
1093
|
-
const l0b: i32 = toI32(
|
|
1094
|
-
const l0c: i32 = toI32(
|
|
1096
|
+
const l0b: i32 = toI32(l0e >>> 8) & 0xff
|
|
1097
|
+
const l0c: i32 = toI32(l0e) & 0xff
|
|
1095
1098
|
const l0d: i32 = toI32(l0) & 0xff
|
|
1099
|
+
r0 ^= st[n] ^ (((st[18 + l0a] + st[274 + l0b]) ^ st[530 + l0c]) + st[786 + l0d])
|
|
1100
|
+
const l1e: u32 = ctSelect((l1 >>> 8) | (l1 << 24), 0xffffffff, 0)
|
|
1096
1101
|
const l1a: i32 = toI32(l1 >>> 24) & 0xff
|
|
1097
|
-
const l1b: i32 = toI32(
|
|
1098
|
-
const l1c: i32 = toI32(
|
|
1102
|
+
const l1b: i32 = toI32(l1e >>> 8) & 0xff
|
|
1103
|
+
const l1c: i32 = toI32(l1e) & 0xff
|
|
1099
1104
|
const l1d: i32 = toI32(l1) & 0xff
|
|
1105
|
+
r1 ^= st[1042 + n] ^ (((st[1060 + l1a] + st[1316 + l1b]) ^ st[1572 + l1c]) + st[1828 + l1d])
|
|
1106
|
+
const l2e: u32 = ctSelect((l2 >>> 8) | (l2 << 24), 0xffffffff, 0)
|
|
1100
1107
|
const l2a: i32 = toI32(l2 >>> 24) & 0xff
|
|
1101
|
-
const l2b: i32 = toI32(
|
|
1102
|
-
const l2c: i32 = toI32(
|
|
1108
|
+
const l2b: i32 = toI32(l2e >>> 8) & 0xff
|
|
1109
|
+
const l2c: i32 = toI32(l2e) & 0xff
|
|
1103
1110
|
const l2d: i32 = toI32(l2) & 0xff
|
|
1104
|
-
r0 ^= st[n] ^ (((st[18 + l0a] + st[274 + l0b]) ^ st[530 + l0c]) + st[786 + l0d])
|
|
1105
|
-
r1 ^= st[1042 + n] ^ (((st[1060 + l1a] + st[1316 + l1b]) ^ st[1572 + l1c]) + st[1828 + l1d])
|
|
1106
1111
|
r2 ^= st[2084 + n] ^ (((st[2102 + l2a] + st[2358 + l2b]) ^ st[2614 + l2c]) + st[2870 + l2d])
|
|
1112
|
+
const r0e: u32 = ctSelect((r0 >>> 8) | (r0 << 24), 0xffffffff, 0)
|
|
1107
1113
|
const r0a: i32 = toI32(r0 >>> 24) & 0xff
|
|
1108
|
-
const r0b: i32 = toI32(
|
|
1109
|
-
const r0c: i32 = toI32(
|
|
1114
|
+
const r0b: i32 = toI32(r0e >>> 8) & 0xff
|
|
1115
|
+
const r0c: i32 = toI32(r0e) & 0xff
|
|
1110
1116
|
const r0d: i32 = toI32(r0) & 0xff
|
|
1117
|
+
l0 ^= st[n + 1] ^ (((st[18 + r0a] + st[274 + r0b]) ^ st[530 + r0c]) + st[786 + r0d])
|
|
1118
|
+
const r1e: u32 = ctSelect((r1 >>> 8) | (r1 << 24), 0xffffffff, 0)
|
|
1111
1119
|
const r1a: i32 = toI32(r1 >>> 24) & 0xff
|
|
1112
|
-
const r1b: i32 = toI32(
|
|
1113
|
-
const r1c: i32 = toI32(
|
|
1120
|
+
const r1b: i32 = toI32(r1e >>> 8) & 0xff
|
|
1121
|
+
const r1c: i32 = toI32(r1e) & 0xff
|
|
1114
1122
|
const r1d: i32 = toI32(r1) & 0xff
|
|
1123
|
+
l1 ^= st[1042 + n + 1] ^ (((st[1060 + r1a] + st[1316 + r1b]) ^ st[1572 + r1c]) + st[1828 + r1d])
|
|
1124
|
+
const r2e: u32 = ctSelect((r2 >>> 8) | (r2 << 24), 0xffffffff, 0)
|
|
1115
1125
|
const r2a: i32 = toI32(r2 >>> 24) & 0xff
|
|
1116
|
-
const r2b: i32 = toI32(
|
|
1117
|
-
const r2c: i32 = toI32(
|
|
1126
|
+
const r2b: i32 = toI32(r2e >>> 8) & 0xff
|
|
1127
|
+
const r2c: i32 = toI32(r2e) & 0xff
|
|
1118
1128
|
const r2d: i32 = toI32(r2) & 0xff
|
|
1119
|
-
l0 ^= st[n + 1] ^ (((st[18 + r0a] + st[274 + r0b]) ^ st[530 + r0c]) + st[786 + r0d])
|
|
1120
|
-
l1 ^= st[1042 + n + 1] ^ (((st[1060 + r1a] + st[1316 + r1b]) ^ st[1572 + r1c]) + st[1828 + r1d])
|
|
1121
1129
|
l2 ^= st[2084 + n + 1] ^ (((st[2102 + r2a] + st[2358 + r2b]) ^ st[2614 + r2c]) + st[2870 + r2d])
|
|
1122
1130
|
}
|
|
1123
1131
|
const t0: u32 = r0
|
|
@@ -1143,6 +1151,19 @@ const bcryptBody3 = (st: u32[]): void => {
|
|
|
1143
1151
|
* place, the four encryption chains interleaved round by round so that one
|
|
1144
1152
|
* lane's S-box loads overlap the others' instead of waiting on their own. The
|
|
1145
1153
|
* hot loop of the batch: 2^(cost+1) calls of 521 blocks per lane.
|
|
1154
|
+
*
|
|
1155
|
+
* Each lane's half-round is written whole, its bytes and then its update,
|
|
1156
|
+
* before the next lane's. The two middle index bytes come from one copy of the
|
|
1157
|
+
* word rotated right by 8 (`e`: byte 1 is its low byte, byte 2 its second),
|
|
1158
|
+
* behind the value barrier: `ctSelect(v, 0xffffffff, 0)` is `v`, opaque, so
|
|
1159
|
+
* LLVM cannot fold the two bytes back into two shifts of the word, and copies
|
|
1160
|
+
* it once instead of twice; on a BMI2 target the copy is one `rorx`. Three
|
|
1161
|
+
* and five lanes rotate the copy the same way; six shift it right by 8, which
|
|
1162
|
+
* has the same two low bytes and measured faster there. On a
|
|
1163
|
+
* Xeon Platinum 8260 that is 6% less time a hash at the best width, 9% under
|
|
1164
|
+
* `--cpu x86-64-v3` (docs/security/crypto-bcrypt.md, "Cascade Lake"). The
|
|
1165
|
+
* barrier is an identity on a register: it adds no branch and no load, and
|
|
1166
|
+
* the round reads the same four S-box words.
|
|
1146
1167
|
*/
|
|
1147
1168
|
const bcryptBody4 = (st: u32[]): void => {
|
|
1148
1169
|
if (st.length < 4168) {
|
|
@@ -1162,45 +1183,53 @@ const bcryptBody4 = (st: u32[]): void => {
|
|
|
1162
1183
|
l2 ^= st[2084]
|
|
1163
1184
|
l3 ^= st[3126]
|
|
1164
1185
|
for (let n: i32 = 1; n < 17; n += 2) {
|
|
1186
|
+
const l0e: u32 = ctSelect((l0 >>> 8) | (l0 << 24), 0xffffffff, 0)
|
|
1165
1187
|
const l0a: i32 = toI32(l0 >>> 24) & 0xff
|
|
1166
|
-
const l0b: i32 = toI32(
|
|
1167
|
-
const l0c: i32 = toI32(
|
|
1188
|
+
const l0b: i32 = toI32(l0e >>> 8) & 0xff
|
|
1189
|
+
const l0c: i32 = toI32(l0e) & 0xff
|
|
1168
1190
|
const l0d: i32 = toI32(l0) & 0xff
|
|
1191
|
+
r0 ^= st[n] ^ (((st[18 + l0a] + st[274 + l0b]) ^ st[530 + l0c]) + st[786 + l0d])
|
|
1192
|
+
const l1e: u32 = ctSelect((l1 >>> 8) | (l1 << 24), 0xffffffff, 0)
|
|
1169
1193
|
const l1a: i32 = toI32(l1 >>> 24) & 0xff
|
|
1170
|
-
const l1b: i32 = toI32(
|
|
1171
|
-
const l1c: i32 = toI32(
|
|
1194
|
+
const l1b: i32 = toI32(l1e >>> 8) & 0xff
|
|
1195
|
+
const l1c: i32 = toI32(l1e) & 0xff
|
|
1172
1196
|
const l1d: i32 = toI32(l1) & 0xff
|
|
1197
|
+
r1 ^= st[1042 + n] ^ (((st[1060 + l1a] + st[1316 + l1b]) ^ st[1572 + l1c]) + st[1828 + l1d])
|
|
1198
|
+
const l2e: u32 = ctSelect((l2 >>> 8) | (l2 << 24), 0xffffffff, 0)
|
|
1173
1199
|
const l2a: i32 = toI32(l2 >>> 24) & 0xff
|
|
1174
|
-
const l2b: i32 = toI32(
|
|
1175
|
-
const l2c: i32 = toI32(
|
|
1200
|
+
const l2b: i32 = toI32(l2e >>> 8) & 0xff
|
|
1201
|
+
const l2c: i32 = toI32(l2e) & 0xff
|
|
1176
1202
|
const l2d: i32 = toI32(l2) & 0xff
|
|
1203
|
+
r2 ^= st[2084 + n] ^ (((st[2102 + l2a] + st[2358 + l2b]) ^ st[2614 + l2c]) + st[2870 + l2d])
|
|
1204
|
+
const l3e: u32 = ctSelect((l3 >>> 8) | (l3 << 24), 0xffffffff, 0)
|
|
1177
1205
|
const l3a: i32 = toI32(l3 >>> 24) & 0xff
|
|
1178
|
-
const l3b: i32 = toI32(
|
|
1179
|
-
const l3c: i32 = toI32(
|
|
1206
|
+
const l3b: i32 = toI32(l3e >>> 8) & 0xff
|
|
1207
|
+
const l3c: i32 = toI32(l3e) & 0xff
|
|
1180
1208
|
const l3d: i32 = toI32(l3) & 0xff
|
|
1181
|
-
r0 ^= st[n] ^ (((st[18 + l0a] + st[274 + l0b]) ^ st[530 + l0c]) + st[786 + l0d])
|
|
1182
|
-
r1 ^= st[1042 + n] ^ (((st[1060 + l1a] + st[1316 + l1b]) ^ st[1572 + l1c]) + st[1828 + l1d])
|
|
1183
|
-
r2 ^= st[2084 + n] ^ (((st[2102 + l2a] + st[2358 + l2b]) ^ st[2614 + l2c]) + st[2870 + l2d])
|
|
1184
1209
|
r3 ^= st[3126 + n] ^ (((st[3144 + l3a] + st[3400 + l3b]) ^ st[3656 + l3c]) + st[3912 + l3d])
|
|
1210
|
+
const r0e: u32 = ctSelect((r0 >>> 8) | (r0 << 24), 0xffffffff, 0)
|
|
1185
1211
|
const r0a: i32 = toI32(r0 >>> 24) & 0xff
|
|
1186
|
-
const r0b: i32 = toI32(
|
|
1187
|
-
const r0c: i32 = toI32(
|
|
1212
|
+
const r0b: i32 = toI32(r0e >>> 8) & 0xff
|
|
1213
|
+
const r0c: i32 = toI32(r0e) & 0xff
|
|
1188
1214
|
const r0d: i32 = toI32(r0) & 0xff
|
|
1215
|
+
l0 ^= st[n + 1] ^ (((st[18 + r0a] + st[274 + r0b]) ^ st[530 + r0c]) + st[786 + r0d])
|
|
1216
|
+
const r1e: u32 = ctSelect((r1 >>> 8) | (r1 << 24), 0xffffffff, 0)
|
|
1189
1217
|
const r1a: i32 = toI32(r1 >>> 24) & 0xff
|
|
1190
|
-
const r1b: i32 = toI32(
|
|
1191
|
-
const r1c: i32 = toI32(
|
|
1218
|
+
const r1b: i32 = toI32(r1e >>> 8) & 0xff
|
|
1219
|
+
const r1c: i32 = toI32(r1e) & 0xff
|
|
1192
1220
|
const r1d: i32 = toI32(r1) & 0xff
|
|
1221
|
+
l1 ^= st[1042 + n + 1] ^ (((st[1060 + r1a] + st[1316 + r1b]) ^ st[1572 + r1c]) + st[1828 + r1d])
|
|
1222
|
+
const r2e: u32 = ctSelect((r2 >>> 8) | (r2 << 24), 0xffffffff, 0)
|
|
1193
1223
|
const r2a: i32 = toI32(r2 >>> 24) & 0xff
|
|
1194
|
-
const r2b: i32 = toI32(
|
|
1195
|
-
const r2c: i32 = toI32(
|
|
1224
|
+
const r2b: i32 = toI32(r2e >>> 8) & 0xff
|
|
1225
|
+
const r2c: i32 = toI32(r2e) & 0xff
|
|
1196
1226
|
const r2d: i32 = toI32(r2) & 0xff
|
|
1227
|
+
l2 ^= st[2084 + n + 1] ^ (((st[2102 + r2a] + st[2358 + r2b]) ^ st[2614 + r2c]) + st[2870 + r2d])
|
|
1228
|
+
const r3e: u32 = ctSelect((r3 >>> 8) | (r3 << 24), 0xffffffff, 0)
|
|
1197
1229
|
const r3a: i32 = toI32(r3 >>> 24) & 0xff
|
|
1198
|
-
const r3b: i32 = toI32(
|
|
1199
|
-
const r3c: i32 = toI32(
|
|
1230
|
+
const r3b: i32 = toI32(r3e >>> 8) & 0xff
|
|
1231
|
+
const r3c: i32 = toI32(r3e) & 0xff
|
|
1200
1232
|
const r3d: i32 = toI32(r3) & 0xff
|
|
1201
|
-
l0 ^= st[n + 1] ^ (((st[18 + r0a] + st[274 + r0b]) ^ st[530 + r0c]) + st[786 + r0d])
|
|
1202
|
-
l1 ^= st[1042 + n + 1] ^ (((st[1060 + r1a] + st[1316 + r1b]) ^ st[1572 + r1c]) + st[1828 + r1d])
|
|
1203
|
-
l2 ^= st[2084 + n + 1] ^ (((st[2102 + r2a] + st[2358 + r2b]) ^ st[2614 + r2c]) + st[2870 + r2d])
|
|
1204
1233
|
l3 ^= st[3126 + n + 1] ^ (((st[3144 + r3a] + st[3400 + r3b]) ^ st[3656 + r3c]) + st[3912 + r3d])
|
|
1205
1234
|
}
|
|
1206
1235
|
const t0: u32 = r0
|
|
@@ -1248,55 +1277,65 @@ const bcryptBody5 = (st: u32[]): void => {
|
|
|
1248
1277
|
l3 ^= st[3126]
|
|
1249
1278
|
l4 ^= st[4168]
|
|
1250
1279
|
for (let n: i32 = 1; n < 17; n += 2) {
|
|
1280
|
+
const l0e: u32 = ctSelect((l0 >>> 8) | (l0 << 24), 0xffffffff, 0)
|
|
1251
1281
|
const l0a: i32 = toI32(l0 >>> 24) & 0xff
|
|
1252
|
-
const l0b: i32 = toI32(
|
|
1253
|
-
const l0c: i32 = toI32(
|
|
1282
|
+
const l0b: i32 = toI32(l0e >>> 8) & 0xff
|
|
1283
|
+
const l0c: i32 = toI32(l0e) & 0xff
|
|
1254
1284
|
const l0d: i32 = toI32(l0) & 0xff
|
|
1285
|
+
r0 ^= st[n] ^ (((st[18 + l0a] + st[274 + l0b]) ^ st[530 + l0c]) + st[786 + l0d])
|
|
1286
|
+
const l1e: u32 = ctSelect((l1 >>> 8) | (l1 << 24), 0xffffffff, 0)
|
|
1255
1287
|
const l1a: i32 = toI32(l1 >>> 24) & 0xff
|
|
1256
|
-
const l1b: i32 = toI32(
|
|
1257
|
-
const l1c: i32 = toI32(
|
|
1288
|
+
const l1b: i32 = toI32(l1e >>> 8) & 0xff
|
|
1289
|
+
const l1c: i32 = toI32(l1e) & 0xff
|
|
1258
1290
|
const l1d: i32 = toI32(l1) & 0xff
|
|
1291
|
+
r1 ^= st[1042 + n] ^ (((st[1060 + l1a] + st[1316 + l1b]) ^ st[1572 + l1c]) + st[1828 + l1d])
|
|
1292
|
+
const l2e: u32 = ctSelect((l2 >>> 8) | (l2 << 24), 0xffffffff, 0)
|
|
1259
1293
|
const l2a: i32 = toI32(l2 >>> 24) & 0xff
|
|
1260
|
-
const l2b: i32 = toI32(
|
|
1261
|
-
const l2c: i32 = toI32(
|
|
1294
|
+
const l2b: i32 = toI32(l2e >>> 8) & 0xff
|
|
1295
|
+
const l2c: i32 = toI32(l2e) & 0xff
|
|
1262
1296
|
const l2d: i32 = toI32(l2) & 0xff
|
|
1297
|
+
r2 ^= st[2084 + n] ^ (((st[2102 + l2a] + st[2358 + l2b]) ^ st[2614 + l2c]) + st[2870 + l2d])
|
|
1298
|
+
const l3e: u32 = ctSelect((l3 >>> 8) | (l3 << 24), 0xffffffff, 0)
|
|
1263
1299
|
const l3a: i32 = toI32(l3 >>> 24) & 0xff
|
|
1264
|
-
const l3b: i32 = toI32(
|
|
1265
|
-
const l3c: i32 = toI32(
|
|
1300
|
+
const l3b: i32 = toI32(l3e >>> 8) & 0xff
|
|
1301
|
+
const l3c: i32 = toI32(l3e) & 0xff
|
|
1266
1302
|
const l3d: i32 = toI32(l3) & 0xff
|
|
1303
|
+
r3 ^= st[3126 + n] ^ (((st[3144 + l3a] + st[3400 + l3b]) ^ st[3656 + l3c]) + st[3912 + l3d])
|
|
1304
|
+
const l4e: u32 = ctSelect((l4 >>> 8) | (l4 << 24), 0xffffffff, 0)
|
|
1267
1305
|
const l4a: i32 = toI32(l4 >>> 24) & 0xff
|
|
1268
|
-
const l4b: i32 = toI32(
|
|
1269
|
-
const l4c: i32 = toI32(
|
|
1306
|
+
const l4b: i32 = toI32(l4e >>> 8) & 0xff
|
|
1307
|
+
const l4c: i32 = toI32(l4e) & 0xff
|
|
1270
1308
|
const l4d: i32 = toI32(l4) & 0xff
|
|
1271
|
-
r0 ^= st[n] ^ (((st[18 + l0a] + st[274 + l0b]) ^ st[530 + l0c]) + st[786 + l0d])
|
|
1272
|
-
r1 ^= st[1042 + n] ^ (((st[1060 + l1a] + st[1316 + l1b]) ^ st[1572 + l1c]) + st[1828 + l1d])
|
|
1273
|
-
r2 ^= st[2084 + n] ^ (((st[2102 + l2a] + st[2358 + l2b]) ^ st[2614 + l2c]) + st[2870 + l2d])
|
|
1274
|
-
r3 ^= st[3126 + n] ^ (((st[3144 + l3a] + st[3400 + l3b]) ^ st[3656 + l3c]) + st[3912 + l3d])
|
|
1275
1309
|
r4 ^= st[4168 + n] ^ (((st[4186 + l4a] + st[4442 + l4b]) ^ st[4698 + l4c]) + st[4954 + l4d])
|
|
1310
|
+
const r0e: u32 = ctSelect((r0 >>> 8) | (r0 << 24), 0xffffffff, 0)
|
|
1276
1311
|
const r0a: i32 = toI32(r0 >>> 24) & 0xff
|
|
1277
|
-
const r0b: i32 = toI32(
|
|
1278
|
-
const r0c: i32 = toI32(
|
|
1312
|
+
const r0b: i32 = toI32(r0e >>> 8) & 0xff
|
|
1313
|
+
const r0c: i32 = toI32(r0e) & 0xff
|
|
1279
1314
|
const r0d: i32 = toI32(r0) & 0xff
|
|
1315
|
+
l0 ^= st[n + 1] ^ (((st[18 + r0a] + st[274 + r0b]) ^ st[530 + r0c]) + st[786 + r0d])
|
|
1316
|
+
const r1e: u32 = ctSelect((r1 >>> 8) | (r1 << 24), 0xffffffff, 0)
|
|
1280
1317
|
const r1a: i32 = toI32(r1 >>> 24) & 0xff
|
|
1281
|
-
const r1b: i32 = toI32(
|
|
1282
|
-
const r1c: i32 = toI32(
|
|
1318
|
+
const r1b: i32 = toI32(r1e >>> 8) & 0xff
|
|
1319
|
+
const r1c: i32 = toI32(r1e) & 0xff
|
|
1283
1320
|
const r1d: i32 = toI32(r1) & 0xff
|
|
1321
|
+
l1 ^= st[1042 + n + 1] ^ (((st[1060 + r1a] + st[1316 + r1b]) ^ st[1572 + r1c]) + st[1828 + r1d])
|
|
1322
|
+
const r2e: u32 = ctSelect((r2 >>> 8) | (r2 << 24), 0xffffffff, 0)
|
|
1284
1323
|
const r2a: i32 = toI32(r2 >>> 24) & 0xff
|
|
1285
|
-
const r2b: i32 = toI32(
|
|
1286
|
-
const r2c: i32 = toI32(
|
|
1324
|
+
const r2b: i32 = toI32(r2e >>> 8) & 0xff
|
|
1325
|
+
const r2c: i32 = toI32(r2e) & 0xff
|
|
1287
1326
|
const r2d: i32 = toI32(r2) & 0xff
|
|
1327
|
+
l2 ^= st[2084 + n + 1] ^ (((st[2102 + r2a] + st[2358 + r2b]) ^ st[2614 + r2c]) + st[2870 + r2d])
|
|
1328
|
+
const r3e: u32 = ctSelect((r3 >>> 8) | (r3 << 24), 0xffffffff, 0)
|
|
1288
1329
|
const r3a: i32 = toI32(r3 >>> 24) & 0xff
|
|
1289
|
-
const r3b: i32 = toI32(
|
|
1290
|
-
const r3c: i32 = toI32(
|
|
1330
|
+
const r3b: i32 = toI32(r3e >>> 8) & 0xff
|
|
1331
|
+
const r3c: i32 = toI32(r3e) & 0xff
|
|
1291
1332
|
const r3d: i32 = toI32(r3) & 0xff
|
|
1333
|
+
l3 ^= st[3126 + n + 1] ^ (((st[3144 + r3a] + st[3400 + r3b]) ^ st[3656 + r3c]) + st[3912 + r3d])
|
|
1334
|
+
const r4e: u32 = ctSelect((r4 >>> 8) | (r4 << 24), 0xffffffff, 0)
|
|
1292
1335
|
const r4a: i32 = toI32(r4 >>> 24) & 0xff
|
|
1293
|
-
const r4b: i32 = toI32(
|
|
1294
|
-
const r4c: i32 = toI32(
|
|
1336
|
+
const r4b: i32 = toI32(r4e >>> 8) & 0xff
|
|
1337
|
+
const r4c: i32 = toI32(r4e) & 0xff
|
|
1295
1338
|
const r4d: i32 = toI32(r4) & 0xff
|
|
1296
|
-
l0 ^= st[n + 1] ^ (((st[18 + r0a] + st[274 + r0b]) ^ st[530 + r0c]) + st[786 + r0d])
|
|
1297
|
-
l1 ^= st[1042 + n + 1] ^ (((st[1060 + r1a] + st[1316 + r1b]) ^ st[1572 + r1c]) + st[1828 + r1d])
|
|
1298
|
-
l2 ^= st[2084 + n + 1] ^ (((st[2102 + r2a] + st[2358 + r2b]) ^ st[2614 + r2c]) + st[2870 + r2d])
|
|
1299
|
-
l3 ^= st[3126 + n + 1] ^ (((st[3144 + r3a] + st[3400 + r3b]) ^ st[3656 + r3c]) + st[3912 + r3d])
|
|
1300
1339
|
l4 ^= st[4168 + n + 1] ^ (((st[4186 + r4a] + st[4442 + r4b]) ^ st[4698 + r4c]) + st[4954 + r4d])
|
|
1301
1340
|
}
|
|
1302
1341
|
const t0: u32 = r0
|
|
@@ -1352,65 +1391,77 @@ const bcryptBody6 = (st: u32[]): void => {
|
|
|
1352
1391
|
l4 ^= st[4168]
|
|
1353
1392
|
l5 ^= st[5210]
|
|
1354
1393
|
for (let n: i32 = 1; n < 17; n += 2) {
|
|
1394
|
+
const l0e: u32 = ctSelect(l0 >>> 8, 0xffffffff, 0)
|
|
1355
1395
|
const l0a: i32 = toI32(l0 >>> 24) & 0xff
|
|
1356
|
-
const l0b: i32 = toI32(
|
|
1357
|
-
const l0c: i32 = toI32(
|
|
1396
|
+
const l0b: i32 = toI32(l0e >>> 8) & 0xff
|
|
1397
|
+
const l0c: i32 = toI32(l0e) & 0xff
|
|
1358
1398
|
const l0d: i32 = toI32(l0) & 0xff
|
|
1399
|
+
r0 ^= st[n] ^ (((st[18 + l0a] + st[274 + l0b]) ^ st[530 + l0c]) + st[786 + l0d])
|
|
1400
|
+
const l1e: u32 = ctSelect(l1 >>> 8, 0xffffffff, 0)
|
|
1359
1401
|
const l1a: i32 = toI32(l1 >>> 24) & 0xff
|
|
1360
|
-
const l1b: i32 = toI32(
|
|
1361
|
-
const l1c: i32 = toI32(
|
|
1402
|
+
const l1b: i32 = toI32(l1e >>> 8) & 0xff
|
|
1403
|
+
const l1c: i32 = toI32(l1e) & 0xff
|
|
1362
1404
|
const l1d: i32 = toI32(l1) & 0xff
|
|
1405
|
+
r1 ^= st[1042 + n] ^ (((st[1060 + l1a] + st[1316 + l1b]) ^ st[1572 + l1c]) + st[1828 + l1d])
|
|
1406
|
+
const l2e: u32 = ctSelect(l2 >>> 8, 0xffffffff, 0)
|
|
1363
1407
|
const l2a: i32 = toI32(l2 >>> 24) & 0xff
|
|
1364
|
-
const l2b: i32 = toI32(
|
|
1365
|
-
const l2c: i32 = toI32(
|
|
1408
|
+
const l2b: i32 = toI32(l2e >>> 8) & 0xff
|
|
1409
|
+
const l2c: i32 = toI32(l2e) & 0xff
|
|
1366
1410
|
const l2d: i32 = toI32(l2) & 0xff
|
|
1411
|
+
r2 ^= st[2084 + n] ^ (((st[2102 + l2a] + st[2358 + l2b]) ^ st[2614 + l2c]) + st[2870 + l2d])
|
|
1412
|
+
const l3e: u32 = ctSelect(l3 >>> 8, 0xffffffff, 0)
|
|
1367
1413
|
const l3a: i32 = toI32(l3 >>> 24) & 0xff
|
|
1368
|
-
const l3b: i32 = toI32(
|
|
1369
|
-
const l3c: i32 = toI32(
|
|
1414
|
+
const l3b: i32 = toI32(l3e >>> 8) & 0xff
|
|
1415
|
+
const l3c: i32 = toI32(l3e) & 0xff
|
|
1370
1416
|
const l3d: i32 = toI32(l3) & 0xff
|
|
1417
|
+
r3 ^= st[3126 + n] ^ (((st[3144 + l3a] + st[3400 + l3b]) ^ st[3656 + l3c]) + st[3912 + l3d])
|
|
1418
|
+
const l4e: u32 = ctSelect(l4 >>> 8, 0xffffffff, 0)
|
|
1371
1419
|
const l4a: i32 = toI32(l4 >>> 24) & 0xff
|
|
1372
|
-
const l4b: i32 = toI32(
|
|
1373
|
-
const l4c: i32 = toI32(
|
|
1420
|
+
const l4b: i32 = toI32(l4e >>> 8) & 0xff
|
|
1421
|
+
const l4c: i32 = toI32(l4e) & 0xff
|
|
1374
1422
|
const l4d: i32 = toI32(l4) & 0xff
|
|
1423
|
+
r4 ^= st[4168 + n] ^ (((st[4186 + l4a] + st[4442 + l4b]) ^ st[4698 + l4c]) + st[4954 + l4d])
|
|
1424
|
+
const l5e: u32 = ctSelect(l5 >>> 8, 0xffffffff, 0)
|
|
1375
1425
|
const l5a: i32 = toI32(l5 >>> 24) & 0xff
|
|
1376
|
-
const l5b: i32 = toI32(
|
|
1377
|
-
const l5c: i32 = toI32(
|
|
1426
|
+
const l5b: i32 = toI32(l5e >>> 8) & 0xff
|
|
1427
|
+
const l5c: i32 = toI32(l5e) & 0xff
|
|
1378
1428
|
const l5d: i32 = toI32(l5) & 0xff
|
|
1379
|
-
r0 ^= st[n] ^ (((st[18 + l0a] + st[274 + l0b]) ^ st[530 + l0c]) + st[786 + l0d])
|
|
1380
|
-
r1 ^= st[1042 + n] ^ (((st[1060 + l1a] + st[1316 + l1b]) ^ st[1572 + l1c]) + st[1828 + l1d])
|
|
1381
|
-
r2 ^= st[2084 + n] ^ (((st[2102 + l2a] + st[2358 + l2b]) ^ st[2614 + l2c]) + st[2870 + l2d])
|
|
1382
|
-
r3 ^= st[3126 + n] ^ (((st[3144 + l3a] + st[3400 + l3b]) ^ st[3656 + l3c]) + st[3912 + l3d])
|
|
1383
|
-
r4 ^= st[4168 + n] ^ (((st[4186 + l4a] + st[4442 + l4b]) ^ st[4698 + l4c]) + st[4954 + l4d])
|
|
1384
1429
|
r5 ^= st[5210 + n] ^ (((st[5228 + l5a] + st[5484 + l5b]) ^ st[5740 + l5c]) + st[5996 + l5d])
|
|
1430
|
+
const r0e: u32 = ctSelect(r0 >>> 8, 0xffffffff, 0)
|
|
1385
1431
|
const r0a: i32 = toI32(r0 >>> 24) & 0xff
|
|
1386
|
-
const r0b: i32 = toI32(
|
|
1387
|
-
const r0c: i32 = toI32(
|
|
1432
|
+
const r0b: i32 = toI32(r0e >>> 8) & 0xff
|
|
1433
|
+
const r0c: i32 = toI32(r0e) & 0xff
|
|
1388
1434
|
const r0d: i32 = toI32(r0) & 0xff
|
|
1435
|
+
l0 ^= st[n + 1] ^ (((st[18 + r0a] + st[274 + r0b]) ^ st[530 + r0c]) + st[786 + r0d])
|
|
1436
|
+
const r1e: u32 = ctSelect(r1 >>> 8, 0xffffffff, 0)
|
|
1389
1437
|
const r1a: i32 = toI32(r1 >>> 24) & 0xff
|
|
1390
|
-
const r1b: i32 = toI32(
|
|
1391
|
-
const r1c: i32 = toI32(
|
|
1438
|
+
const r1b: i32 = toI32(r1e >>> 8) & 0xff
|
|
1439
|
+
const r1c: i32 = toI32(r1e) & 0xff
|
|
1392
1440
|
const r1d: i32 = toI32(r1) & 0xff
|
|
1441
|
+
l1 ^= st[1042 + n + 1] ^ (((st[1060 + r1a] + st[1316 + r1b]) ^ st[1572 + r1c]) + st[1828 + r1d])
|
|
1442
|
+
const r2e: u32 = ctSelect(r2 >>> 8, 0xffffffff, 0)
|
|
1393
1443
|
const r2a: i32 = toI32(r2 >>> 24) & 0xff
|
|
1394
|
-
const r2b: i32 = toI32(
|
|
1395
|
-
const r2c: i32 = toI32(
|
|
1444
|
+
const r2b: i32 = toI32(r2e >>> 8) & 0xff
|
|
1445
|
+
const r2c: i32 = toI32(r2e) & 0xff
|
|
1396
1446
|
const r2d: i32 = toI32(r2) & 0xff
|
|
1447
|
+
l2 ^= st[2084 + n + 1] ^ (((st[2102 + r2a] + st[2358 + r2b]) ^ st[2614 + r2c]) + st[2870 + r2d])
|
|
1448
|
+
const r3e: u32 = ctSelect(r3 >>> 8, 0xffffffff, 0)
|
|
1397
1449
|
const r3a: i32 = toI32(r3 >>> 24) & 0xff
|
|
1398
|
-
const r3b: i32 = toI32(
|
|
1399
|
-
const r3c: i32 = toI32(
|
|
1450
|
+
const r3b: i32 = toI32(r3e >>> 8) & 0xff
|
|
1451
|
+
const r3c: i32 = toI32(r3e) & 0xff
|
|
1400
1452
|
const r3d: i32 = toI32(r3) & 0xff
|
|
1453
|
+
l3 ^= st[3126 + n + 1] ^ (((st[3144 + r3a] + st[3400 + r3b]) ^ st[3656 + r3c]) + st[3912 + r3d])
|
|
1454
|
+
const r4e: u32 = ctSelect(r4 >>> 8, 0xffffffff, 0)
|
|
1401
1455
|
const r4a: i32 = toI32(r4 >>> 24) & 0xff
|
|
1402
|
-
const r4b: i32 = toI32(
|
|
1403
|
-
const r4c: i32 = toI32(
|
|
1456
|
+
const r4b: i32 = toI32(r4e >>> 8) & 0xff
|
|
1457
|
+
const r4c: i32 = toI32(r4e) & 0xff
|
|
1404
1458
|
const r4d: i32 = toI32(r4) & 0xff
|
|
1459
|
+
l4 ^= st[4168 + n + 1] ^ (((st[4186 + r4a] + st[4442 + r4b]) ^ st[4698 + r4c]) + st[4954 + r4d])
|
|
1460
|
+
const r5e: u32 = ctSelect(r5 >>> 8, 0xffffffff, 0)
|
|
1405
1461
|
const r5a: i32 = toI32(r5 >>> 24) & 0xff
|
|
1406
|
-
const r5b: i32 = toI32(
|
|
1407
|
-
const r5c: i32 = toI32(
|
|
1462
|
+
const r5b: i32 = toI32(r5e >>> 8) & 0xff
|
|
1463
|
+
const r5c: i32 = toI32(r5e) & 0xff
|
|
1408
1464
|
const r5d: i32 = toI32(r5) & 0xff
|
|
1409
|
-
l0 ^= st[n + 1] ^ (((st[18 + r0a] + st[274 + r0b]) ^ st[530 + r0c]) + st[786 + r0d])
|
|
1410
|
-
l1 ^= st[1042 + n + 1] ^ (((st[1060 + r1a] + st[1316 + r1b]) ^ st[1572 + r1c]) + st[1828 + r1d])
|
|
1411
|
-
l2 ^= st[2084 + n + 1] ^ (((st[2102 + r2a] + st[2358 + r2b]) ^ st[2614 + r2c]) + st[2870 + r2d])
|
|
1412
|
-
l3 ^= st[3126 + n + 1] ^ (((st[3144 + r3a] + st[3400 + r3b]) ^ st[3656 + r3c]) + st[3912 + r3d])
|
|
1413
|
-
l4 ^= st[4168 + n + 1] ^ (((st[4186 + r4a] + st[4442 + r4b]) ^ st[4698 + r4c]) + st[4954 + r4d])
|
|
1414
1465
|
l5 ^= st[5210 + n + 1] ^ (((st[5228 + r5a] + st[5484 + r5b]) ^ st[5740 + r5c]) + st[5996 + r5d])
|
|
1415
1466
|
}
|
|
1416
1467
|
const t0: u32 = r0
|
package/std/sort.ts
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `std/sort` — `xs.sort(cmp)` on an array, in Nish: a stable merge sort,
|
|
3
|
+
* compiled once per comparator.
|
|
4
|
+
*
|
|
5
|
+
* A program does not import this module. A `.sort(cmp)` call is what loads it
|
|
6
|
+
* (the checker's `importSort`), and the checker routes the call to `sortWith`
|
|
7
|
+
* here. `cmp` is a compile-time function parameter, so each comparator is an
|
|
8
|
+
* instantiation of its own whose body calls the comparator directly — there is
|
|
9
|
+
* no function pointer. Under Node and `tsc` the call is the platform's
|
|
10
|
+
* `Array.prototype.sort`, so nothing here runs there.
|
|
11
|
+
*
|
|
12
|
+
* **Same order as Node.** `Array.prototype.sort` is stable (ES2019), so for a
|
|
13
|
+
* comparator that is a consistent total preorder there is exactly one answer,
|
|
14
|
+
* and any stable sort gives it. A comparator's result is read only as "greater
|
|
15
|
+
* than zero or not": a later element moves before an earlier one only when
|
|
16
|
+
* the comparator answers a positive number for (earlier, later), so a negative
|
|
17
|
+
* zero and NaN are zero, as Node treats them.
|
|
18
|
+
*
|
|
19
|
+
* **Shape.** The array is copied once into a scratch array of the same
|
|
20
|
+
* length, the only allocation, which the function's arena scope gives back
|
|
21
|
+
* before it returns, so a sort leaves `Arena.used()` where it was. Runs of
|
|
22
|
+
* `RUN` are sorted by insertion from the scratch into the array (a strictly
|
|
23
|
+
* descending run is copied reversed instead), then merged
|
|
24
|
+
* bottom-up, alternating between the two, and copied back once if the last
|
|
25
|
+
* pass ended in the scratch. A pair of runs already in order is copied without
|
|
26
|
+
* a merge. Every merge takes from the left run on a tie, which is what makes
|
|
27
|
+
* it stable.
|
|
28
|
+
*
|
|
29
|
+
* **No element is held across a write.** An `interface` element is stored
|
|
30
|
+
* inline, and `const v = xs[i]` is a pointer into the array, so an insertion
|
|
31
|
+
* sort in place would overwrite the element it is inserting. Every step here
|
|
32
|
+
* reads from one array and writes the other, and moves a whole slot, which for
|
|
33
|
+
* an inline record is one copy of its bytes.
|
|
34
|
+
*
|
|
35
|
+
* **Proofs.** Every index is proven in range by the guard written beside it,
|
|
36
|
+
* so no access keeps its bounds check. The run arithmetic wraps only where
|
|
37
|
+
* the operands are proven to lie in `0..n`, so it cannot wrap: a difference
|
|
38
|
+
* of two of them, or a sum taken only when it stays below `n`.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
import { wrappingAdd, wrappingSub } from "nish:unsafe"
|
|
42
|
+
|
|
43
|
+
/** The length of the runs insertion sort makes before the first merge. */
|
|
44
|
+
const RUN: i32 = 32
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* `lo + step`, or `n` when that is not below `n`. Every caller has
|
|
48
|
+
* `0 <= lo <= n`, so `n - lo` cannot wrap, and the sum is taken only below `n`.
|
|
49
|
+
*/
|
|
50
|
+
const stepTo = (lo: i32, step: i32, n: i32): i32 => (wrappingSub(n, lo) > step ? wrappingAdd(lo, step) : n)
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* `src[lo..hi)` sorted by insertion into `dst[lo..hi)`. The comparator call
|
|
54
|
+
* forgets every array length, so each access after it repeats its guard.
|
|
55
|
+
*/
|
|
56
|
+
const insertionRun = <T>(src: T[], dst: T[], cmp: (a: T, b: T) => number, lo: i32, hi: i32): void => {
|
|
57
|
+
// A strictly descending run is copied reversed: no two of its elements are
|
|
58
|
+
// equal, so reversing it keeps the sort stable, and costs one comparison each.
|
|
59
|
+
// `lo < hi`, and `d` steps only while below `hi`, so neither `+ 1` wraps.
|
|
60
|
+
let d = wrappingAdd(lo, 1)
|
|
61
|
+
while (d > 0 && d < hi && d < toI32(src.length)) {
|
|
62
|
+
const p = d - 1
|
|
63
|
+
if (!(cmp(src[p], src[d]) > 0)) {
|
|
64
|
+
break
|
|
65
|
+
}
|
|
66
|
+
d = wrappingAdd(d, 1)
|
|
67
|
+
}
|
|
68
|
+
if (d >= hi) {
|
|
69
|
+
// `hi > lo >= 0`, so `hi - 1` does not wrap, and `r` steps down only from `r >= 0`.
|
|
70
|
+
let r = wrappingSub(hi, 1)
|
|
71
|
+
for (let k: i32 = lo; k >= 0 && k < hi && k < toI32(dst.length) && r >= 0 && r < toI32(src.length); k++) {
|
|
72
|
+
dst[k] = src[r]
|
|
73
|
+
r = wrappingSub(r, 1)
|
|
74
|
+
}
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
for (let i: i32 = lo; i >= 0 && i < hi; i++) {
|
|
78
|
+
let j = i
|
|
79
|
+
while (j > lo && j > 0 && j < toI32(dst.length) && i < toI32(src.length)) {
|
|
80
|
+
const k = j - 1
|
|
81
|
+
if (!(cmp(dst[k], src[i]) > 0) || k < 0 || k >= toI32(dst.length) || j >= toI32(dst.length)) {
|
|
82
|
+
break
|
|
83
|
+
}
|
|
84
|
+
dst[j] = dst[k]
|
|
85
|
+
j = k
|
|
86
|
+
}
|
|
87
|
+
if (i < toI32(src.length) && j >= 0 && j < toI32(dst.length)) {
|
|
88
|
+
dst[j] = src[i]
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** `src[lo..hi)` copied into `dst[lo..hi)`. */
|
|
94
|
+
const copyRun = <T>(src: T[], dst: T[], lo: i32, hi: i32): void => {
|
|
95
|
+
for (let k: i32 = lo; k >= 0 && k < hi && k < toI32(src.length) && k < toI32(dst.length); k++) {
|
|
96
|
+
dst[k] = src[k]
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The sorted runs `src[lo..mid)` and `src[mid..hi)` merged into `dst[lo..hi)`,
|
|
102
|
+
* or copied when the last of the left run is not after the first of the right.
|
|
103
|
+
*/
|
|
104
|
+
const mergeRuns = <T>(src: T[], dst: T[], cmp: (a: T, b: T) => number, lo: i32, mid: i32, hi: i32): void => {
|
|
105
|
+
if (mid > 0 && mid < hi && mid < toI32(src.length)) {
|
|
106
|
+
const last = mid - 1
|
|
107
|
+
if (last < toI32(src.length) && !(cmp(src[last], src[mid]) > 0)) {
|
|
108
|
+
copyRun(src, dst, lo, hi)
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
let i = lo
|
|
113
|
+
let j = mid
|
|
114
|
+
for (let k: i32 = lo; k >= 0 && k < hi; k++) {
|
|
115
|
+
let right = i >= mid
|
|
116
|
+
if (!right && j < hi && i >= 0 && i < toI32(src.length) && j >= 0 && j < toI32(src.length)) {
|
|
117
|
+
right = cmp(src[i], src[j]) > 0
|
|
118
|
+
}
|
|
119
|
+
if (right) {
|
|
120
|
+
if (j >= 0 && j < toI32(src.length) && k < toI32(dst.length)) {
|
|
121
|
+
dst[k] = src[j]
|
|
122
|
+
}
|
|
123
|
+
// `j` steps at most `hi - mid` times from `mid`, so it stays at most `hi`.
|
|
124
|
+
j = wrappingAdd(j, 1)
|
|
125
|
+
} else {
|
|
126
|
+
if (i >= 0 && i < toI32(src.length) && k < toI32(dst.length)) {
|
|
127
|
+
dst[k] = src[i]
|
|
128
|
+
}
|
|
129
|
+
// `i` steps at most `mid - lo` times from `lo`, so it stays at most `mid`.
|
|
130
|
+
i = wrappingAdd(i, 1)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** One bottom-up pass: every pair of adjacent `width`-runs of `src` merged into `dst`. */
|
|
136
|
+
const mergePass = <T>(src: T[], dst: T[], cmp: (a: T, b: T) => number, n: i32, width: i32): void => {
|
|
137
|
+
let lo: i32 = 0
|
|
138
|
+
while (lo >= 0 && lo < n) {
|
|
139
|
+
const mid = stepTo(lo, width, n)
|
|
140
|
+
const hi = stepTo(mid, width, n)
|
|
141
|
+
mergeRuns(src, dst, cmp, lo, mid, hi)
|
|
142
|
+
lo = hi
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** `xs` sorted in place by `cmp`, stably, and answered: what `xs.sort(cmp)` compiles to. */
|
|
147
|
+
export const sortWith = <T>(xs: T[], cmp: (a: T, b: T) => number): T[] => {
|
|
148
|
+
const n: i32 = toI32(xs.length)
|
|
149
|
+
if (n < 2) {
|
|
150
|
+
return xs
|
|
151
|
+
}
|
|
152
|
+
// One allocation at full size, every slot written before any is read: the
|
|
153
|
+
// checker lets this `new Array<T>(n)` hold any `T`, and neither emitter
|
|
154
|
+
// zero-fills it (`isSortInstance`).
|
|
155
|
+
const tmp = new Array<T>(n)
|
|
156
|
+
copyRun(xs, tmp, 0, n)
|
|
157
|
+
let lo: i32 = 0
|
|
158
|
+
while (lo >= 0 && lo < n) {
|
|
159
|
+
const hi = stepTo(lo, RUN, n)
|
|
160
|
+
insertionRun(tmp, xs, cmp, lo, hi)
|
|
161
|
+
lo = hi
|
|
162
|
+
}
|
|
163
|
+
let inTmp = false
|
|
164
|
+
let width: i32 = RUN
|
|
165
|
+
while (width > 0 && width < n) {
|
|
166
|
+
if (inTmp) {
|
|
167
|
+
mergePass(tmp, xs, cmp, n, width)
|
|
168
|
+
} else {
|
|
169
|
+
mergePass(xs, tmp, cmp, n, width)
|
|
170
|
+
}
|
|
171
|
+
inTmp = !inTmp
|
|
172
|
+
width = stepTo(width, width, n)
|
|
173
|
+
}
|
|
174
|
+
if (inTmp) {
|
|
175
|
+
copyRun(tmp, xs, 0, n)
|
|
176
|
+
}
|
|
177
|
+
return xs
|
|
178
|
+
}
|