|
| 1 | +// Original Keccak-256 (0x01 padding) |
| 2 | +// |
| 3 | +// Used by the lambda-vm prover's Merkle commit: |
| 4 | +// leaf = Keccak-256(concat(col_0[br_idx].to_be_bytes(), col_1[br_idx].to_be_bytes(), ...)) |
| 5 | +// where `br_idx = bit_reverse(row_idx, log_num_rows)` and each element is |
| 6 | +// written in BIG-ENDIAN canonical form (per `FieldElement::write_bytes_be`). |
| 7 | +// |
| 8 | +// Keccak state is 5x5 lanes of u64, interpreted little-endian. Rate = 136 B |
| 9 | +// (17 lanes) for 256-bit output, capacity = 64 B (8 lanes). |
| 10 | +// |
| 11 | +// Since every input byte is u64-aligned (each field element is 8 or 24 bytes), |
| 12 | +// we can absorb lane-by-lane instead of byte-by-byte. Canonicalise + byte-swap |
| 13 | +// each u64 on read to turn a BE-serialised element into its LE-interpreted |
| 14 | +// lane value. |
| 15 | + |
| 16 | +#include <cstdint> |
| 17 | +#include "goldilocks.cuh" |
| 18 | + |
| 19 | +__device__ __constant__ uint64_t KECCAK_RC[24] = { |
| 20 | + 0x0000000000000001ULL, 0x0000000000008082ULL, 0x800000000000808aULL, |
| 21 | + 0x8000000080008000ULL, 0x000000000000808bULL, 0x0000000080000001ULL, |
| 22 | + 0x8000000080008081ULL, 0x8000000000008009ULL, 0x000000000000008aULL, |
| 23 | + 0x0000000000000088ULL, 0x0000000080008009ULL, 0x000000008000000aULL, |
| 24 | + 0x000000008000808bULL, 0x800000000000008bULL, 0x8000000000008089ULL, |
| 25 | + 0x8000000000008003ULL, 0x8000000000008002ULL, 0x8000000000000080ULL, |
| 26 | + 0x000000000000800aULL, 0x800000008000000aULL, 0x8000000080008081ULL, |
| 27 | + 0x8000000000008080ULL, 0x0000000080000001ULL, 0x8000000080008008ULL, |
| 28 | +}; |
| 29 | + |
| 30 | +// Rotation offsets indexed by lane position x + 5*y. Standard Keccak rho. |
| 31 | +__device__ __constant__ uint32_t KECCAK_RHO_OFFSETS[25] = { |
| 32 | + 0, 1, 62, 28, 27, // y=0: x=0..4 |
| 33 | + 36, 44, 6, 55, 20, // y=1 |
| 34 | + 3, 10, 43, 25, 39, // y=2 |
| 35 | + 41, 45, 15, 21, 8, // y=3 |
| 36 | + 18, 2, 61, 56, 14, // y=4 |
| 37 | +}; |
| 38 | + |
| 39 | +__device__ __forceinline__ uint64_t rotl64(uint64_t x, uint32_t n) { |
| 40 | + return (n == 0) ? x : ((x << n) | (x >> (64 - n))); |
| 41 | +} |
| 42 | + |
| 43 | +__device__ __forceinline__ uint64_t bswap64(uint64_t x) { |
| 44 | + // Reverse byte order: turns a BE-serialised u64 into its LE-read lane. |
| 45 | + x = ((x & 0x00ff00ff00ff00ffULL) << 8) | ((x & 0xff00ff00ff00ff00ULL) >> 8); |
| 46 | + x = ((x & 0x0000ffff0000ffffULL) << 16) | ((x & 0xffff0000ffff0000ULL) >> 16); |
| 47 | + return (x << 32) | (x >> 32); |
| 48 | +} |
| 49 | + |
| 50 | +__device__ __forceinline__ void keccak_f1600(uint64_t st[25]) { |
| 51 | + uint64_t C[5], D[5], B[25]; |
| 52 | + // No outer unroll: fully unrolling the 24 rounds slowed the kernel ~7.5% on RTX 5090. |
| 53 | + for (int r = 0; r < 24; ++r) { |
| 54 | + // Theta |
| 55 | + #pragma unroll |
| 56 | + for (int x = 0; x < 5; ++x) { |
| 57 | + C[x] = st[x] ^ st[x + 5] ^ st[x + 10] ^ st[x + 15] ^ st[x + 20]; |
| 58 | + } |
| 59 | + #pragma unroll |
| 60 | + for (int x = 0; x < 5; ++x) { |
| 61 | + D[x] = C[(x + 4) % 5] ^ rotl64(C[(x + 1) % 5], 1); |
| 62 | + } |
| 63 | + #pragma unroll |
| 64 | + for (int y = 0; y < 5; ++y) { |
| 65 | + #pragma unroll |
| 66 | + for (int x = 0; x < 5; ++x) { |
| 67 | + st[x + 5 * y] ^= D[x]; |
| 68 | + } |
| 69 | + } |
| 70 | + |
| 71 | + // Rho + Pi: B[pi(x,y)] = rotl(st[x,y], rho(x,y)) |
| 72 | + // pi: (x', y') = (y, (2x + 3y) mod 5) |
| 73 | + #pragma unroll |
| 74 | + for (int y = 0; y < 5; ++y) { |
| 75 | + #pragma unroll |
| 76 | + for (int x = 0; x < 5; ++x) { |
| 77 | + int nx = y; |
| 78 | + int ny = (2 * x + 3 * y) % 5; |
| 79 | + B[nx + 5 * ny] = rotl64(st[x + 5 * y], KECCAK_RHO_OFFSETS[x + 5 * y]); |
| 80 | + } |
| 81 | + } |
| 82 | + |
| 83 | + // Chi |
| 84 | + #pragma unroll |
| 85 | + for (int y = 0; y < 5; ++y) { |
| 86 | + #pragma unroll |
| 87 | + for (int x = 0; x < 5; ++x) { |
| 88 | + st[x + 5 * y] = |
| 89 | + B[x + 5 * y] ^ ((~B[((x + 1) % 5) + 5 * y]) & B[((x + 2) % 5) + 5 * y]); |
| 90 | + } |
| 91 | + } |
| 92 | + |
| 93 | + // Iota |
| 94 | + st[0] ^= KECCAK_RC[r]; |
| 95 | + } |
| 96 | +} |
| 97 | + |
| 98 | +// --------------------------------------------------------------------------- |
| 99 | +// Helper: absorb one 8-byte lane (already byte-swapped from BE serialisation |
| 100 | +// into Keccak's LE lane form) into the sponge at `rate_pos` (in bytes). |
| 101 | +// Permutes when a full 136-byte block has been absorbed. |
| 102 | +// --------------------------------------------------------------------------- |
| 103 | +__device__ __forceinline__ void absorb_lane(uint64_t st[25], |
| 104 | + uint32_t &rate_pos, |
| 105 | + uint64_t lane) { |
| 106 | + st[rate_pos / 8] ^= lane; |
| 107 | + rate_pos += 8; |
| 108 | + if (rate_pos == 136) { |
| 109 | + keccak_f1600(st); |
| 110 | + rate_pos = 0; |
| 111 | + } |
| 112 | +} |
| 113 | + |
| 114 | +// --------------------------------------------------------------------------- |
| 115 | +// After all data lanes absorbed, apply Keccak (pre-SHA-3) padding: a single |
| 116 | +// 0x01 byte at the current position, then bit 0x80 on the last rate byte |
| 117 | +// (byte 135 = last byte of lane 16). Then permute and squeeze 32 bytes from |
| 118 | +// the first four lanes in LE order. |
| 119 | +// --------------------------------------------------------------------------- |
| 120 | +__device__ __forceinline__ void finalize_keccak256(uint64_t st[25], |
| 121 | + uint32_t rate_pos, |
| 122 | + uint8_t *out32) { |
| 123 | + // 0x01 at rate_pos |
| 124 | + st[rate_pos / 8] ^= ((uint64_t)0x01) << ((rate_pos & 7) * 8); |
| 125 | + // 0x80 at byte 135 (last byte of lane 16) |
| 126 | + st[16] ^= ((uint64_t)0x80) << 56; |
| 127 | + keccak_f1600(st); |
| 128 | + |
| 129 | + // Squeeze 32 bytes: 4 lanes, each LE-serialised. |
| 130 | + #pragma unroll |
| 131 | + for (int i = 0; i < 4; ++i) { |
| 132 | + uint64_t lane = st[i]; |
| 133 | + #pragma unroll |
| 134 | + for (int b = 0; b < 8; ++b) { |
| 135 | + out32[i * 8 + b] = (uint8_t)((lane >> (b * 8)) & 0xff); |
| 136 | + } |
| 137 | + } |
| 138 | +} |
| 139 | + |
| 140 | +// --------------------------------------------------------------------------- |
| 141 | +// Goldilocks BASE-FIELD leaf hashing. |
| 142 | +// |
| 143 | +// For output row `row_idx` (natural order), the leaf hashes the canonical BE |
| 144 | +// byte representation of `columns[c][bit_reverse(row_idx, log_num_rows)]` for |
| 145 | +// `c` in `[0, num_cols)`, concatenated in column order. Writes 32 bytes to |
| 146 | +// `hashed_leaves_out[row_idx * 32 ..]`. |
| 147 | +// |
| 148 | +// `columns_base_ptr` points to a `num_cols * col_stride * u64` buffer; column |
| 149 | +// `c` is the contiguous slab `[c*col_stride .. c*col_stride + num_rows]`. The |
| 150 | +// remaining `col_stride - num_rows` entries (if any) are ignored. |
| 151 | +// --------------------------------------------------------------------------- |
| 152 | +extern "C" __global__ void keccak256_leaves_base_batched( |
| 153 | + const uint64_t *columns_base_ptr, |
| 154 | + uint64_t col_stride, |
| 155 | + uint64_t num_cols, |
| 156 | + uint64_t num_rows, |
| 157 | + uint64_t log_num_rows, |
| 158 | + uint8_t *hashed_leaves_out) { |
| 159 | + uint64_t tid = (uint64_t)blockIdx.x * blockDim.x + threadIdx.x; |
| 160 | + if (tid >= num_rows) return; |
| 161 | + |
| 162 | + // Bit-reverse the row index so we read columns at `br` but write the |
| 163 | + // hashed leaf at `tid` — matching the CPU `commit_columns_bit_reversed`. |
| 164 | + uint64_t br = __brevll(tid) >> (64 - log_num_rows); |
| 165 | + |
| 166 | + uint64_t st[25]; |
| 167 | + #pragma unroll |
| 168 | + for (int i = 0; i < 25; ++i) st[i] = 0; |
| 169 | + |
| 170 | + uint32_t rate_pos = 0; |
| 171 | + for (uint64_t c = 0; c < num_cols; ++c) { |
| 172 | + uint64_t v = columns_base_ptr[c * col_stride + br]; |
| 173 | + // Canonicalise to match `canonical_u64().to_be_bytes()` on host. |
| 174 | + uint64_t canon = goldilocks::canonical(v); |
| 175 | + // The on-disk leaf bytes are canon.to_be_bytes(). Keccak reads those |
| 176 | + // as a LE lane, which equals bswap64(canon). |
| 177 | + uint64_t lane = bswap64(canon); |
| 178 | + absorb_lane(st, rate_pos, lane); |
| 179 | + } |
| 180 | + |
| 181 | + finalize_keccak256(st, rate_pos, hashed_leaves_out + tid * 32); |
| 182 | +} |
| 183 | + |
| 184 | +// --------------------------------------------------------------------------- |
| 185 | +// Goldilocks EXT3 leaf hashing (3 base-field components per ext3 element). |
| 186 | +// |
| 187 | +// Components live in three separate base-field slabs (our de-interleaved |
| 188 | +// layout). Column `c` component `k` is at `columns_base_ptr[(c*3 + k)*col_stride |
| 189 | +// + br]`. Per-element BE bytes are `[comp0, comp1, comp2]` each 8 BE bytes |
| 190 | +// (matches `FieldElement::<Ext3>::write_bytes_be`). |
| 191 | +// --------------------------------------------------------------------------- |
| 192 | +extern "C" __global__ void keccak256_leaves_ext3_batched( |
| 193 | + const uint64_t *columns_base_ptr, |
| 194 | + uint64_t col_stride, |
| 195 | + uint64_t num_cols, // number of ext3 columns (NOT slabs) |
| 196 | + uint64_t num_rows, |
| 197 | + uint64_t log_num_rows, |
| 198 | + uint8_t *hashed_leaves_out) { |
| 199 | + uint64_t tid = (uint64_t)blockIdx.x * blockDim.x + threadIdx.x; |
| 200 | + if (tid >= num_rows) return; |
| 201 | + uint64_t br = __brevll(tid) >> (64 - log_num_rows); |
| 202 | + |
| 203 | + uint64_t st[25]; |
| 204 | + #pragma unroll |
| 205 | + for (int i = 0; i < 25; ++i) st[i] = 0; |
| 206 | + |
| 207 | + uint32_t rate_pos = 0; |
| 208 | + for (uint64_t c = 0; c < num_cols; ++c) { |
| 209 | + #pragma unroll |
| 210 | + for (int k = 0; k < 3; ++k) { |
| 211 | + uint64_t v = columns_base_ptr[(c * 3 + (uint64_t)k) * col_stride + br]; |
| 212 | + uint64_t canon = goldilocks::canonical(v); |
| 213 | + uint64_t lane = bswap64(canon); |
| 214 | + absorb_lane(st, rate_pos, lane); |
| 215 | + } |
| 216 | + } |
| 217 | + |
| 218 | + finalize_keccak256(st, rate_pos, hashed_leaves_out + tid * 32); |
| 219 | +} |
| 220 | + |
| 221 | +// --------------------------------------------------------------------------- |
| 222 | +// R2 composition-polynomial leaf hashing. |
| 223 | +// |
| 224 | +// Each leaf hashes `2 * num_parts` ext3 values taken from bit-reversed rows |
| 225 | +// `br_0 = reverse_index(2*leaf_idx)` and `br_1 = reverse_index(2*leaf_idx+1)` |
| 226 | +// across all `num_parts` parts, in (br_0 row: part 0..K-1) then (br_1 row: |
| 227 | +// part 0..K-1) order. Each ext3 value is 3 base components × 8 BE bytes. |
| 228 | +// |
| 229 | +// Columns arrive in the de-interleaved 3-slab layout: part `p` component |
| 230 | +// `k` is at `parts_base_ptr[(p*3 + k) * col_stride + row]`. |
| 231 | +// --------------------------------------------------------------------------- |
| 232 | +extern "C" __global__ void keccak_comp_poly_leaves_ext3( |
| 233 | + const uint64_t *parts_base_ptr, |
| 234 | + uint64_t col_stride, |
| 235 | + uint64_t num_parts, |
| 236 | + uint64_t num_rows, |
| 237 | + uint64_t log_num_rows, |
| 238 | + uint8_t *leaves_out) { |
| 239 | + uint64_t tid = (uint64_t)blockIdx.x * blockDim.x + threadIdx.x; |
| 240 | + uint64_t num_leaves = num_rows >> 1; |
| 241 | + if (tid >= num_leaves) return; |
| 242 | + |
| 243 | + uint64_t br_0 = __brevll(2 * tid) >> (64 - log_num_rows); |
| 244 | + uint64_t br_1 = __brevll(2 * tid + 1) >> (64 - log_num_rows); |
| 245 | + |
| 246 | + uint64_t st[25]; |
| 247 | + #pragma unroll |
| 248 | + for (int i = 0; i < 25; ++i) st[i] = 0; |
| 249 | + |
| 250 | + uint32_t rate_pos = 0; |
| 251 | + // First row (br_0): part 0..K-1 × 3 components each. |
| 252 | + for (uint64_t p = 0; p < num_parts; ++p) { |
| 253 | + #pragma unroll |
| 254 | + for (int k = 0; k < 3; ++k) { |
| 255 | + uint64_t v = parts_base_ptr[(p * 3 + (uint64_t)k) * col_stride + br_0]; |
| 256 | + uint64_t canon = goldilocks::canonical(v); |
| 257 | + absorb_lane(st, rate_pos, bswap64(canon)); |
| 258 | + } |
| 259 | + } |
| 260 | + // Second row (br_1). |
| 261 | + for (uint64_t p = 0; p < num_parts; ++p) { |
| 262 | + #pragma unroll |
| 263 | + for (int k = 0; k < 3; ++k) { |
| 264 | + uint64_t v = parts_base_ptr[(p * 3 + (uint64_t)k) * col_stride + br_1]; |
| 265 | + uint64_t canon = goldilocks::canonical(v); |
| 266 | + absorb_lane(st, rate_pos, bswap64(canon)); |
| 267 | + } |
| 268 | + } |
| 269 | + |
| 270 | + finalize_keccak256(st, rate_pos, leaves_out + tid * 32); |
| 271 | +} |
| 272 | + |
| 273 | +// --------------------------------------------------------------------------- |
| 274 | +// FRI layer leaf hashing. |
| 275 | +// |
| 276 | +// Each leaf hashes 2 consecutive ext3 values: Keccak256 over |
| 277 | +// evals[2j].to_bytes_be() ++ evals[2j+1].to_bytes_be() |
| 278 | +// = 48 BE bytes = 6 u64 BE lanes. No bit reversal, no column slab layout. |
| 279 | +// The input is a single interleaved ext3 eval vector `[a0,a1,a2,b0,b1,b2,...]`. |
| 280 | +// --------------------------------------------------------------------------- |
| 281 | +extern "C" __global__ void keccak_fri_leaves_ext3( |
| 282 | + const uint64_t *evals_interleaved, // 3 * num_evals u64s (ext3 interleaved) |
| 283 | + uint64_t num_leaves, // = num_evals / 2 |
| 284 | + uint8_t *leaves_out) { |
| 285 | + uint64_t tid = (uint64_t)blockIdx.x * blockDim.x + threadIdx.x; |
| 286 | + if (tid >= num_leaves) return; |
| 287 | + |
| 288 | + uint64_t st[25]; |
| 289 | + #pragma unroll |
| 290 | + for (int i = 0; i < 25; ++i) st[i] = 0; |
| 291 | + uint32_t rate_pos = 0; |
| 292 | + |
| 293 | + const uint64_t *left = evals_interleaved + 2 * tid * 3; // 3 u64s |
| 294 | + const uint64_t *right = left + 3; |
| 295 | + #pragma unroll |
| 296 | + for (int i = 0; i < 3; ++i) { |
| 297 | + uint64_t canon = goldilocks::canonical(left[i]); |
| 298 | + absorb_lane(st, rate_pos, bswap64(canon)); |
| 299 | + } |
| 300 | + #pragma unroll |
| 301 | + for (int i = 0; i < 3; ++i) { |
| 302 | + uint64_t canon = goldilocks::canonical(right[i]); |
| 303 | + absorb_lane(st, rate_pos, bswap64(canon)); |
| 304 | + } |
| 305 | + |
| 306 | + finalize_keccak256(st, rate_pos, leaves_out + tid * 32); |
| 307 | +} |
| 308 | + |
| 309 | +// --------------------------------------------------------------------------- |
| 310 | +// Merkle inner-tree pair hash: one level of the inner Merkle tree. |
| 311 | +// |
| 312 | +// `nodes` is the full Merkle node buffer (length `2*leaves_len - 1`, each |
| 313 | +// element 32 bytes). `parent_begin` is the node-index offset of the first |
| 314 | +// parent slot in this level. Children live at `parent_begin + n_pairs`. |
| 315 | +// The layout mirrors `crypto/crypto/src/merkle_tree/merkle.rs`: |
| 316 | +// |
| 317 | +// children: nodes[parent_begin + n_pairs .. parent_begin + 3 * n_pairs] |
| 318 | +// parents: nodes[parent_begin .. parent_begin + n_pairs] |
| 319 | +// |
| 320 | +// Each thread hashes one child pair → one parent. Keccak-256 of the |
| 321 | +// concatenation of two 32-byte siblings, identical to |
| 322 | +// `FieldElementVectorBackend::hash_new_parent` on host. |
| 323 | +// --------------------------------------------------------------------------- |
| 324 | +extern "C" __global__ void keccak_merkle_level( |
| 325 | + uint8_t *nodes, |
| 326 | + uint64_t parent_begin, // node index (counted in 32-byte nodes) |
| 327 | + uint64_t n_pairs) { |
| 328 | + uint64_t tid = (uint64_t)blockIdx.x * blockDim.x + threadIdx.x; |
| 329 | + if (tid >= n_pairs) return; |
| 330 | + |
| 331 | + uint64_t st[25]; |
| 332 | + #pragma unroll |
| 333 | + for (int i = 0; i < 25; ++i) st[i] = 0; |
| 334 | + |
| 335 | + uint32_t rate_pos = 0; |
| 336 | + // `nodes` comes from cuMemAlloc (256-byte aligned); each 32-byte node |
| 337 | + // sits at a 32-byte-aligned offset, so the u64 cast is safe. |
| 338 | + const uint64_t *left = reinterpret_cast<const uint64_t *>( |
| 339 | + nodes + (parent_begin + n_pairs + 2 * tid) * 32); |
| 340 | + #pragma unroll |
| 341 | + for (int i = 0; i < 4; ++i) absorb_lane(st, rate_pos, left[i]); |
| 342 | + |
| 343 | + const uint64_t *right = reinterpret_cast<const uint64_t *>( |
| 344 | + nodes + (parent_begin + n_pairs + 2 * tid + 1) * 32); |
| 345 | + #pragma unroll |
| 346 | + for (int i = 0; i < 4; ++i) absorb_lane(st, rate_pos, right[i]); |
| 347 | + |
| 348 | + finalize_keccak256(st, rate_pos, nodes + (parent_begin + tid) * 32); |
| 349 | +} |
0 commit comments