Skip to content

Commit a54ce1c

Browse files
committed
Merge origin/main into feat/disk-spill-v2-parallel-r2
2 parents 369bd94 + 57a8ab9 commit a54ce1c

12 files changed

Lines changed: 1977 additions & 388 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crypto/crypto/src/merkle_tree/merkle.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,13 @@ where
185185
self.nodes.get(idx)
186186
}
187187

188+
/// Read-only access to the full node buffer in standard layout:
189+
/// `nodes[0..leaves_len - 1]` are inner nodes (root at index 0) and
190+
/// `nodes[leaves_len - 1..]` are the leaves.
191+
pub fn nodes(&self) -> &[B::Node] {
192+
&self.nodes
193+
}
194+
188195
/// Returns a Merkle proof for the element/s at position pos
189196
/// For example, give me an inclusion proof for the 3rd element in the
190197
/// Merkle tree

crypto/math-cuda/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ math = { path = "../math" }
1717
rayon = "1.7"
1818

1919
[dev-dependencies]
20+
crypto = { path = "../crypto" }
2021
rand = { version = "0.8.5", features = ["std"] }
2122
rand_chacha = "0.3.1"
2223
rayon = "1.7"
2324
sha3 = "0.10.8"
25+
stark = { path = "../stark" }

crypto/math-cuda/build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,5 @@ fn main() {
110110

111111
compile_ptx("arith.cu", "arith.ptx", have_nvcc);
112112
compile_ptx("ntt.cu", "ntt.ptx", have_nvcc);
113+
compile_ptx("keccak.cu", "keccak.ptx", have_nvcc);
113114
}

crypto/math-cuda/kernels/keccak.cu

Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
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

Comments
 (0)