1use crate::chiplet::ChipletDef;
6use crate::constraint::{BoundaryTarget, ConstraintAst, ConstraintExpr, ExprId};
7use crate::expander::ExpansionEntry;
8use crate::permutation::{BusKind, PermutationCheckSpec, Source};
9use crate::{Air, FixedColumn, FixedShape, InlineKernelHint, Program};
10use alloc::string::String;
11use alloc::vec::Vec;
12use hekate_core::errors;
13use hekate_core::trace::ColumnType;
14use hekate_crypto::{DefaultHasher, Hasher};
15use hekate_math::TowerField;
16
17const FLUSH_BYTES: usize = 64 * 1024;
20
21struct Absorb {
22 hasher: DefaultHasher,
23 buf: Vec<u8>,
24}
25
26impl Absorb {
27 fn new() -> Self {
28 Self {
29 hasher: DefaultHasher::new(),
30 buf: Vec::with_capacity(FLUSH_BYTES + 64),
31 }
32 }
33
34 fn update(&mut self, bytes: &[u8]) {
35 self.buf.extend_from_slice(bytes);
36
37 if self.buf.len() >= FLUSH_BYTES {
38 self.hasher.update(&self.buf);
39 self.buf.clear();
40 }
41 }
42
43 fn finalize(mut self) -> [u8; 32] {
44 self.hasher.update(&self.buf);
45
46 self.hasher.finalize()
47 }
48}
49
50pub fn program_id<F: TowerField, P: Program<F>>(program: &P) -> errors::Result<[u8; 32]> {
53 Ok(program_id_of(
54 program,
55 &program.chiplet_defs()?,
56 &program.inline_chiplets()?,
57 ))
58}
59
60pub fn program_id_hex<F: TowerField, P: Program<F>>(program: &P) -> errors::Result<String> {
62 const HEX: &[u8; 16] = b"0123456789abcdef";
63
64 let bytes = program_id::<F, P>(program)?;
65
66 let mut s = String::with_capacity(64);
67 for &b in bytes.iter() {
68 s.push(HEX[(b >> 4) as usize] as char);
69 s.push(HEX[(b & 0xf) as usize] as char);
70 }
71
72 Ok(s)
73}
74
75pub fn program_id_of<F: TowerField, P: Program<F>>(
76 program: &P,
77 chiplet_defs: &[ChipletDef<F>],
78 inline_chiplets: &[ChipletDef<F>],
79) -> [u8; 32] {
80 let mut h = Absorb::new();
81 h.update(b"hekate-program-id-v2");
82
83 let main_name = program.name();
84 h.update(&(main_name.len() as u64).to_le_bytes());
85 h.update(main_name.as_bytes());
86
87 absorb_layout(&mut h, program.column_layout());
88 absorb_ast::<F>(&mut h, &program.constraint_ast());
89 absorb_boundaries(&mut h, &program.boundary_constraints());
90 absorb_permutation_checks(&mut h, &program.permutation_checks());
91
92 h.update(&(chiplet_defs.len() as u64).to_le_bytes());
93
94 for cd in chiplet_defs {
95 absorb_chiplet_def::<F>(&mut h, cd);
96 }
97
98 if let Some(exp) = program.virtual_expander() {
99 h.update(&[1]);
100
101 absorb_expander(&mut h, exp);
102 } else {
103 h.update(&[0]);
104 }
105
106 absorb_fixed_columns(&mut h, &program.fixed_columns());
107
108 h.update(&(program.num_columns() as u64).to_le_bytes());
109 h.update(&(program.num_public_inputs() as u64).to_le_bytes());
110
111 absorb_inline_chiplets::<F>(&mut h, inline_chiplets, &program.inline_chiplet_kernels());
112
113 h.finalize()
114}
115
116fn absorb_chiplet_def<F: TowerField>(h: &mut Absorb, cd: &ChipletDef<F>) {
117 let name = Air::<F>::name(cd);
118
119 h.update(&(name.len() as u64).to_le_bytes());
120 h.update(name.as_bytes());
121
122 absorb_layout(h, cd.column_layout());
123
124 let cd_ast = cd.constraint_ast();
125 absorb_ast::<F>(h, &cd_ast);
126
127 absorb_boundaries(h, &cd.boundary_constraints());
128 absorb_permutation_checks(h, &cd.permutation_checks());
129 absorb_fixed_columns(h, &Air::<F>::fixed_columns(cd));
130
131 if let Some(exp) = cd.virtual_expander() {
132 h.update(&[1]);
133
134 absorb_expander(h, exp);
135 } else {
136 h.update(&[0]);
137 }
138}
139
140fn absorb_inline_chiplets<F: TowerField>(
141 h: &mut Absorb,
142 inline_chiplets: &[ChipletDef<F>],
143 inline_kernel_hints: &[InlineKernelHint],
144) {
145 h.update(&(inline_chiplets.len() as u64).to_le_bytes());
146
147 for cd in inline_chiplets {
148 absorb_chiplet_def::<F>(h, cd);
149 }
150
151 h.update(&(inline_kernel_hints.len() as u64).to_le_bytes());
152
153 for hint in inline_kernel_hints {
154 h.update(&(hint.chiplet_idx as u64).to_le_bytes());
155 h.update(&(hint.root_offset as u64).to_le_bytes());
156 h.update(&(hint.column_offset as u64).to_le_bytes());
157 }
158}
159
160fn absorb_layout(h: &mut Absorb, layout: &[ColumnType]) {
161 h.update(&(layout.len() as u64).to_le_bytes());
162
163 for ct in layout {
164 h.update(&[column_type_tag(*ct)]);
165 }
166}
167
168fn absorb_ast<F: TowerField>(h: &mut Absorb, ast: &ConstraintAst<F>) {
169 h.update(&(ast.arena.len() as u64).to_le_bytes());
170
171 for i in 0..ast.arena.len() {
172 absorb_expr(h, ast.arena.get(ExprId(i as u32)));
173 }
174
175 h.update(&(ast.roots.len() as u64).to_le_bytes());
176
177 for root in &ast.roots {
178 h.update(&root.0.to_le_bytes());
179 }
180}
181
182fn absorb_expr<F: TowerField>(h: &mut Absorb, expr: &ConstraintExpr<F>) {
183 match expr {
184 ConstraintExpr::Cell(cell) => {
185 h.update(&[0]);
186 h.update(&(cell.col_idx as u32).to_le_bytes());
187 h.update(&[cell.next_row as u8]);
188 }
189 ConstraintExpr::Const(val) => {
190 h.update(&[1]);
191 h.update(&val.to_bytes());
192 }
193 ConstraintExpr::Add(l, r) => {
194 h.update(&[2]);
195 h.update(&l.0.to_le_bytes());
196 h.update(&r.0.to_le_bytes());
197 }
198 ConstraintExpr::Mul(l, r) => {
199 h.update(&[3]);
200 h.update(&l.0.to_le_bytes());
201 h.update(&r.0.to_le_bytes());
202 }
203 ConstraintExpr::Scale(scalar, child) => {
204 h.update(&[4]);
205 h.update(&scalar.to_bytes());
206 h.update(&child.0.to_le_bytes());
207 }
208 ConstraintExpr::Sum(children) => {
209 h.update(&[5]);
210 h.update(&(children.len() as u64).to_le_bytes());
211
212 for c in children {
213 h.update(&c.0.to_le_bytes());
214 }
215 }
216 }
217}
218
219fn absorb_boundaries<F: TowerField>(
220 h: &mut Absorb,
221 boundaries: &[crate::constraint::BoundaryConstraint<F>],
222) {
223 h.update(&(boundaries.len() as u64).to_le_bytes());
224
225 for bc in boundaries {
226 h.update(&(bc.col_idx as u64).to_le_bytes());
227 h.update(&(bc.row_idx as u64).to_le_bytes());
228
229 match &bc.target {
230 BoundaryTarget::PublicInput(idx) => {
231 h.update(&[0]);
232 h.update(&(*idx as u64).to_le_bytes());
233 }
234 BoundaryTarget::Constant(v) => {
235 h.update(&[1]);
236 h.update(&v.to_bytes());
237 }
238 }
239 }
240}
241
242fn absorb_fixed_columns<F: TowerField>(h: &mut Absorb, fixed: &[FixedColumn<F>]) {
243 h.update(&(fixed.len() as u64).to_le_bytes());
244
245 for fc in fixed {
246 h.update(&(fc.col_idx as u64).to_le_bytes());
247
248 match &fc.shape {
249 FixedShape::LastRow => h.update(&[0]),
250 FixedShape::FirstRow => h.update(&[1]),
251 FixedShape::Custom(bits) => {
252 h.update(&[2]);
253 h.update(&(bits.len() as u64).to_le_bytes());
254
255 for &b in bits {
256 h.update(&[b as u8]);
257 }
258 }
259 FixedShape::Periodic { period, values } => {
260 h.update(&[3]);
261 h.update(&(*period as u64).to_le_bytes());
262 h.update(&(values.len() as u64).to_le_bytes());
263
264 for v in values {
265 h.update(&v.to_bytes());
266 }
267 }
268 FixedShape::Sparse(entries) => {
269 h.update(&[4]);
270 h.update(&(entries.len() as u64).to_le_bytes());
271
272 for (row, v) in entries {
273 h.update(&(*row as u64).to_le_bytes());
274 h.update(&v.to_bytes());
275 }
276 }
277 FixedShape::Dense(values) => {
278 h.update(&[5]);
279 h.update(&(values.len() as u64).to_le_bytes());
280
281 for v in values {
282 h.update(&v.to_bytes());
283 }
284 }
285 FixedShape::Cadence {
286 stride,
287 count,
288 origin,
289 values,
290 } => {
291 h.update(&[6]);
292 h.update(&(*stride as u64).to_le_bytes());
293 h.update(&(*count as u64).to_le_bytes());
294 h.update(&(*origin as u64).to_le_bytes());
295 h.update(&(values.len() as u64).to_le_bytes());
296
297 for v in values {
298 h.update(&v.to_bytes());
299 }
300 }
301 FixedShape::Segments(segments) => {
302 h.update(&[7]);
303 h.update(&(segments.len() as u64).to_le_bytes());
304
305 for seg in segments {
306 h.update(&(seg.stride as u64).to_le_bytes());
307 h.update(&(seg.count as u64).to_le_bytes());
308 h.update(&(seg.origin as u64).to_le_bytes());
309 h.update(&(seg.values.len() as u64).to_le_bytes());
310
311 for v in &seg.values {
312 h.update(&v.to_bytes());
313 }
314 }
315 }
316 }
317 }
318}
319
320fn absorb_permutation_checks(h: &mut Absorb, checks: &[(String, PermutationCheckSpec)]) {
321 h.update(&(checks.len() as u64).to_le_bytes());
322
323 for (bus_id, spec) in checks {
324 h.update(&(bus_id.len() as u64).to_le_bytes());
325 h.update(bus_id.as_bytes());
326
327 absorb_perm_spec(h, spec);
328 }
329}
330
331fn absorb_perm_spec(h: &mut Absorb, spec: &PermutationCheckSpec) {
332 h.update(&[match spec.kind {
333 BusKind::Permutation => 0,
334 BusKind::Lookup => 1,
335 }]);
336
337 h.update(&(spec.sources.len() as u64).to_le_bytes());
338
339 for (source, label) in &spec.sources {
340 absorb_source(h, source);
341
342 h.update(&(label.len() as u64).to_le_bytes());
343 h.update(label);
344 }
345
346 match spec.selector {
347 Some(sel) => {
348 h.update(&[1]);
349 h.update(&(sel as u64).to_le_bytes());
350 }
351 None => h.update(&[0]),
352 }
353
354 match spec.recv_selector {
355 Some(sel) => {
356 h.update(&[1]);
357 h.update(&(sel as u64).to_le_bytes());
358 }
359 None => h.update(&[0]),
360 }
361
362 match spec.clock_waiver.as_deref() {
363 Some(reason) => {
364 h.update(&[1]);
365 h.update(&(reason.len() as u64).to_le_bytes());
366 h.update(reason.as_bytes());
367 }
368 None => h.update(&[0]),
369 }
370}
371
372fn absorb_source(h: &mut Absorb, source: &Source) {
373 match source {
374 Source::Column(idx) => {
375 h.update(&[0]);
376 h.update(&(*idx as u64).to_le_bytes());
377 }
378 Source::Columns(indices) => {
379 h.update(&[1]);
380 h.update(&(indices.len() as u64).to_le_bytes());
381
382 for idx in indices {
383 h.update(&(*idx as u64).to_le_bytes());
384 }
385 }
386 Source::RowIndexLeBytes(n) => {
387 h.update(&[2]);
388 h.update(&(*n as u64).to_le_bytes());
389 }
390 Source::Const(val) => {
391 h.update(&[3]);
392 h.update(&val.to_le_bytes());
393 }
394 Source::RowIndexByte(n) => {
395 h.update(&[4]);
396 h.update(&(*n as u64).to_le_bytes());
397 }
398 }
399}
400
401fn absorb_expander(h: &mut Absorb, exp: &crate::expander::VirtualExpander) {
402 let entries = exp.expansion_entries();
403 h.update(&(entries.len() as u64).to_le_bytes());
404
405 for entry in &entries {
406 match *entry {
407 ExpansionEntry::ExpandBits { count, storage } => {
408 h.update(&[0]);
409 h.update(&(count as u64).to_le_bytes());
410 h.update(&[column_type_tag(storage)]);
411 }
412 ExpansionEntry::PassThrough { count, storage } => {
413 h.update(&[1]);
414 h.update(&(count as u64).to_le_bytes());
415 h.update(&[column_type_tag(storage)]);
416 }
417 ExpansionEntry::ControlBits { count } => {
418 h.update(&[2]);
419 h.update(&(count as u64).to_le_bytes());
420 }
421 ExpansionEntry::ReusePassThrough {
422 phy_col_start,
423 count,
424 storage,
425 } => {
426 h.update(&[3]);
427 h.update(&(phy_col_start as u64).to_le_bytes());
428 h.update(&(count as u64).to_le_bytes());
429 h.update(&[column_type_tag(storage)]);
430 }
431 ExpansionEntry::ReuseExpandBits {
432 phy_col_start,
433 count,
434 storage,
435 } => {
436 h.update(&[4]);
437 h.update(&(phy_col_start as u64).to_le_bytes());
438 h.update(&(count as u64).to_le_bytes());
439 h.update(&[column_type_tag(storage)]);
440 }
441 }
442 }
443}
444
445fn column_type_tag(ct: ColumnType) -> u8 {
446 match ct {
447 ColumnType::Bit => 0,
448 ColumnType::B8 => 1,
449 ColumnType::B16 => 2,
450 ColumnType::B32 => 3,
451 ColumnType::B64 => 4,
452 ColumnType::B128 => 5,
453 }
454}
455
456#[cfg(test)]
457mod tests {
458 use super::*;
459
460 #[test]
461 fn buffering_does_not_move_digest() {
462 let fields: Vec<Vec<u8>> = (0..FLUSH_BYTES / 4 + 17)
463 .map(|i| (i as u32).to_le_bytes().to_vec())
464 .collect();
465
466 let mut buffered = Absorb::new();
467 let mut direct = DefaultHasher::new();
468
469 for field in &fields {
470 buffered.update(field);
471 direct.update(field);
472 }
473
474 assert_eq!(buffered.finalize(), direct.finalize());
475 }
476}