Squeeze a block at a time when computing the matrix in Kyber
We were currently squeezing 3 bytes at a time. And because kyber.c and
keccak.c are separate files, we've got an optimization barrier, at least
in non-LTO builds, at a very inconvenient place.
Instead, extract the full 168 bytes (SHAKE-128 rate) at a time. This is
conveniently a multiple of three, so we don't need to worry about
partial coefficients. We're still doing a copy due to the Keccak
abstraction, but it should extend cleanly to either LTO or a different
abstraction to read from the state directly. Even without that, it's a
significant perf win.
Benchmarks on an M1:
Before:
Did 87390 Kyber generate + decap operations in 4001590us (21838.8 ops/sec)
Did 119000 Kyber parse + encap operations in 4009460us (29679.8 ops/sec)
After:
Did 96747 Kyber generate + decap operations in 4003687us (24164.5 ops/sec) [+10.6%]
Did 152000 Kyber parse + encap operations in 4018360us (37826.4 ops/sec) [+27.4%]
Change-Id: Iada393edf0c634b7410a34374fb90242a392a9d3
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/59265
Commit-Queue: Adam Langley <agl@google.com>
Reviewed-by: Adam Langley <agl@google.com>
Auto-Submit: David Benjamin <davidben@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/crypto/kyber/kyber.c b/crypto/kyber/kyber.c
index f2ac30b..010d9d4 100644
--- a/crypto/kyber/kyber.c
+++ b/crypto/kyber/kyber.c
@@ -283,16 +283,23 @@
// operates on public inputs.
static void scalar_from_keccak_vartime(scalar *out,
struct BORINGSSL_keccak_st *keccak_ctx) {
- uint8_t bytes[3];
- for (int i = 0; i < DEGREE;) {
- BORINGSSL_keccak_squeeze(keccak_ctx, bytes, sizeof(bytes));
- uint16_t d1 = bytes[0] + 256 * (bytes[1] % 16);
- uint16_t d2 = bytes[1] / 16 + 16 * bytes[2];
- if (d1 < kPrime) {
- out->c[i++] = d1;
- }
- if (d2 < kPrime && i < DEGREE) {
- out->c[i++] = d2;
+ assert(keccak_ctx->offset == 0);
+ assert(keccak_ctx->rate_bytes == 168);
+ static_assert(168 % 3 == 0, "block and coefficient boundaries do not align");
+
+ int done = 0;
+ while (done < DEGREE) {
+ uint8_t block[168];
+ BORINGSSL_keccak_squeeze(keccak_ctx, block, sizeof(block));
+ for (size_t i = 0; i < sizeof(block) && done < DEGREE; i += 3) {
+ uint16_t d1 = block[i] + 256 * (block[i + 1] % 16);
+ uint16_t d2 = block[i + 1] / 16 + 16 * block[i + 2];
+ if (d1 < kPrime) {
+ out->c[done++] = d1;
+ }
+ if (d2 < kPrime && done < DEGREE) {
+ out->c[done++] = d2;
+ }
}
}
}