1mod constants;
4mod table;
5#[cfg(all(feature = "alloc", test))]
6mod tests;
7
8#[cfg(feature = "alloc")]
9use crate::PosProofs;
10use crate::chiapos::constants::NUM_TABLES;
11#[cfg(feature = "alloc")]
12pub use crate::chiapos::table::TablesCache;
13#[cfg(feature = "alloc")]
14use crate::chiapos::table::types::Position;
15use crate::chiapos::table::types::{Metadata, X, Y};
16#[cfg(feature = "alloc")]
17use crate::chiapos::table::{PrunedTable, Table};
18use crate::chiapos::table::{compute_f1, compute_fn, has_match};
19#[cfg(feature = "alloc")]
20use ab_core_primitives::pieces::Record;
21#[cfg(feature = "alloc")]
22use ab_core_primitives::pos::PosProof;
23#[cfg(feature = "alloc")]
24use ab_core_primitives::sectors::SBucket;
25#[cfg(feature = "alloc")]
26use alloc::boxed::Box;
27use core::array;
28use core::mem::MaybeUninit;
29#[cfg(feature = "alloc")]
30use core::mem::offset_of;
31#[cfg(any(feature = "full-chiapos", test))]
32use sha2::{Digest, Sha256};
33
34pub impl(self) trait SupportedKValue {}
36
37pub const PROOF_SIZE<const K: u8>: usize =
39 2usize.pow(u32::from(NUM_TABLES - 1)) * usize::from(K) / u8::BITS as usize;
40
41#[derive(Debug)]
43#[cfg(feature = "alloc")]
44#[repr(C)]
45pub struct Proofs<const K: u8> {
46 pub found_proofs: [u8; Record::NUM_S_BUCKETS / u8::BITS as usize],
54 pub proofs: [[u8; PROOF_SIZE::<K>]; const { Record::NUM_CHUNKS }],
56}
57
58#[cfg(feature = "alloc")]
59impl From<Box<Proofs<const { PosProof::K }>>> for Box<PosProofs> {
60 fn from(proofs: Box<Proofs<const { PosProof::K }>>) -> Self {
61 const {
63 assert!(PosProof::K == 20);
66 assert!(size_of::<Proofs<20>>() == size_of::<PosProofs>());
67 assert!(align_of::<Proofs<20>>() == align_of::<PosProofs>());
68 assert!(offset_of!(Proofs<20>, found_proofs) == offset_of!(PosProofs, found_proofs));
69 assert!(offset_of!(Proofs<20>, proofs) == offset_of!(PosProofs, proofs));
70 }
71 unsafe { Box::from_raw(Box::into_raw(proofs).cast()) }
73 }
74}
75
76#[cfg(feature = "alloc")]
77impl<const K: u8> Proofs<K> {
78 #[inline]
83 pub fn for_s_bucket(&self, s_bucket: SBucket) -> Option<[u8; PROOF_SIZE::<K>]> {
84 let proof_index = PosProofs::proof_index_for_s_bucket(&self.found_proofs, s_bucket)?;
85
86 Some(self.proofs[proof_index])
87 }
88}
89
90type Seed = [u8; 32];
91#[cfg(any(feature = "full-chiapos", test))]
92type Challenge = [u8; 32];
93#[cfg(any(feature = "full-chiapos", test))]
94type Quality = [u8; 32];
95
96#[cfg(all(feature = "alloc", any(feature = "full-chiapos", test)))]
98const fn pick_position(
99 [left_position, right_position]: [Position; 2],
100 last_5_challenge_bits: u8,
101 table_number: u8,
102) -> Position {
103 if ((last_5_challenge_bits >> (table_number - 2)) & 1) == 0 {
104 left_position
105 } else {
106 right_position
107 }
108}
109
110#[derive(Debug)]
112pub struct Tables<const K: u8>
113where
114 Self: SupportedKValue,
115{
116 #[cfg(feature = "alloc")]
117 table_2: PrunedTable<K, 2>,
118 #[cfg(feature = "alloc")]
119 table_3: PrunedTable<K, 3>,
120 #[cfg(feature = "alloc")]
121 table_4: PrunedTable<K, 4>,
122 #[cfg(feature = "alloc")]
123 table_5: PrunedTable<K, 5>,
124 #[cfg(feature = "alloc")]
125 table_6: PrunedTable<K, 6>,
126 #[cfg(feature = "alloc")]
127 table_7: Table<K, 7>,
128}
129
130impl<const K: u8> Tables<K>
131where
132 Self: SupportedKValue,
133{
134 #[cfg(all(feature = "alloc", any(feature = "full-chiapos", test)))]
139 pub fn create(seed: Seed, cache: &TablesCache) -> Self {
140 let table_1 = Table::<K, 1>::create(seed);
141 let (table_2, _) = Table::<K, 2>::create(table_1, cache);
142 let (table_3, table_2) = Table::<K, 3>::create(table_2, cache);
143 let (table_4, table_3) = Table::<K, 4>::create(table_3, cache);
144 let (table_5, table_4) = Table::<K, 5>::create(table_4, cache);
145 let (table_6, table_5) = Table::<K, 6>::create(table_5, cache);
146 let (table_7, table_6) = Table::<K, 7>::create(table_6, cache);
147
148 Self {
149 table_2,
150 table_3,
151 table_4,
152 table_5,
153 table_6,
154 table_7,
155 }
156 }
157
158 #[cfg(feature = "alloc")]
165 pub fn create_proofs(seed: Seed, cache: &TablesCache) -> Box<Proofs<K>> {
166 let table_1 = Table::<K, 1>::create(seed);
167 let (table_2, _) = Table::<K, 2>::create(table_1, cache);
168 let (table_3, table_2) = Table::<K, 3>::create(table_2, cache);
169 let (table_4, table_3) = Table::<K, 4>::create(table_3, cache);
170 let (table_5, table_4) = Table::<K, 5>::create(table_4, cache);
171 let (table_6, table_5) = Table::<K, 6>::create(table_5, cache);
172 let (table_6_proof_targets, table_6) = Table::<K, 7>::create_proof_targets(table_6, cache);
173
174 let mut proofs = Box::<Proofs<K>>::new_uninit();
176 {
177 let proofs_ptr = proofs.as_mut().as_mut_ptr();
178 let found_proofs = unsafe {
180 (&raw mut (*proofs_ptr).found_proofs)
181 .as_uninit_mut()
182 .expect("Not null; qed")
183 };
184 let found_proofs = found_proofs.write([0; _]);
185 let proofs = unsafe {
187 (&raw mut (*proofs_ptr).proofs)
188 .cast::<[MaybeUninit<_>; const { Record::NUM_CHUNKS }]>()
189 .as_mut_unchecked()
190 };
191
192 let mut num_found_proofs = 0_usize;
193 'outer: for (table_6_proof_targets, found_proofs) in table_6_proof_targets
194 .as_chunks::<{ u8::BITS as usize }>()
195 .0
196 .iter()
197 .zip(found_proofs)
198 {
199 for (proof_offset, table_6_proof_targets) in
201 table_6_proof_targets.iter().enumerate()
202 {
203 if table_6_proof_targets != &[Position::ZERO; 2] {
204 let proof = Self::find_proof_raw_internal(
205 &table_2,
206 &table_3,
207 &table_4,
208 &table_5,
209 &table_6,
210 *table_6_proof_targets,
211 );
212
213 *found_proofs |= 1 << proof_offset;
214
215 proofs[num_found_proofs].write(proof);
216 num_found_proofs += 1;
217
218 if num_found_proofs == Record::NUM_CHUNKS {
219 break 'outer;
220 }
221 }
222 }
223 }
224
225 debug_assert_eq!(num_found_proofs, Record::NUM_CHUNKS);
228 }
229
230 unsafe { proofs.assume_init() }
232 }
233
234 #[cfg(all(feature = "parallel", any(feature = "full-chiapos", test)))]
237 pub fn create_parallel(seed: Seed, cache: &TablesCache) -> Self {
238 let table_1 = Table::<K, 1>::create_parallel(seed);
239 let (table_2, _) = Table::<K, 2>::create_parallel(table_1, cache);
240 let (table_3, table_2) = Table::<K, 3>::create_parallel(table_2, cache);
241 let (table_4, table_3) = Table::<K, 4>::create_parallel(table_3, cache);
242 let (table_5, table_4) = Table::<K, 5>::create_parallel(table_4, cache);
243 let (table_6, table_5) = Table::<K, 6>::create_parallel(table_5, cache);
244 let (table_7, table_6) = Table::<K, 7>::create_parallel(table_6, cache);
245
246 Self {
247 table_2,
248 table_3,
249 table_4,
250 table_5,
251 table_6,
252 table_7,
253 }
254 }
255
256 #[cfg(feature = "parallel")]
259 pub fn create_proofs_parallel(seed: Seed, cache: &TablesCache) -> Box<Proofs<K>> {
260 let table_1 = Table::<K, 1>::create_parallel(seed);
261 let (table_2, _) = Table::<K, 2>::create_parallel(table_1, cache);
262 let (table_3, table_2) = Table::<K, 3>::create_parallel(table_2, cache);
263 let (table_4, table_3) = Table::<K, 4>::create_parallel(table_3, cache);
264 let (table_5, table_4) = Table::<K, 5>::create_parallel(table_4, cache);
265 let (table_6, table_5) = Table::<K, 6>::create_parallel(table_5, cache);
266 let (table_6_proof_targets, table_6) =
267 Table::<K, 7>::create_proof_targets_parallel(table_6, cache);
268
269 let mut proofs = Box::<Proofs<K>>::new_uninit();
271 {
272 let proofs_ptr = proofs.as_mut().as_mut_ptr();
273 let found_proofs = unsafe {
275 (&raw mut (*proofs_ptr).found_proofs)
276 .as_uninit_mut()
277 .expect("Not null; qed")
278 };
279 let found_proofs = found_proofs.write([0; _]);
280 let proofs = unsafe {
282 (&raw mut (*proofs_ptr).proofs)
283 .cast::<[MaybeUninit<_>; const { Record::NUM_CHUNKS }]>()
284 .as_mut_unchecked()
285 };
286
287 let mut num_found_proofs = 0_usize;
288 'outer: for (table_6_proof_targets, found_proofs) in table_6_proof_targets
289 .as_chunks::<{ u8::BITS as usize }>()
290 .0
291 .iter()
292 .zip(found_proofs)
293 {
294 for (proof_offset, table_6_proof_targets) in
296 table_6_proof_targets.iter().enumerate()
297 {
298 if table_6_proof_targets != &[Position::ZERO; 2] {
299 let proof = Self::find_proof_raw_internal(
300 &table_2,
301 &table_3,
302 &table_4,
303 &table_5,
304 &table_6,
305 *table_6_proof_targets,
306 );
307
308 *found_proofs |= 1 << proof_offset;
309
310 proofs[num_found_proofs].write(proof);
311 num_found_proofs += 1;
312
313 if num_found_proofs == Record::NUM_CHUNKS {
314 break 'outer;
315 }
316 }
317 }
318 }
319
320 debug_assert_eq!(num_found_proofs, Record::NUM_CHUNKS);
323 }
324
325 unsafe { proofs.assume_init() }
327 }
328
329 #[cfg(all(feature = "alloc", any(feature = "full-chiapos", test)))]
331 pub fn find_quality<'a>(
332 &'a self,
333 challenge: &'a Challenge,
334 ) -> impl Iterator<Item = Quality> + 'a {
335 let last_5_challenge_bits = challenge[challenge.len() - 1] & 0b0001_1111;
336
337 let first_k_challenge_bits = u32::from_be_bytes(
338 challenge[..size_of::<u32>()]
339 .try_into()
340 .expect("Challenge is known to statically have enough bytes; qed"),
341 ) >> (u32::BITS as usize - usize::from(K));
342
343 self.table_7.buckets()[Y::bucket_range_from_first_k_bits(first_k_challenge_bits)]
345 .iter()
346 .flat_map(move |positions| {
347 positions
348 .iter()
349 .take_while(|&&(position, _y)| position != Position::SENTINEL)
350 .filter(move |&&(_position, y)| y.first_k_bits() == first_k_challenge_bits)
351 })
352 .map(move |&(position, _y)| {
353 let positions = unsafe { self.table_7.position(position) };
355 let positions = unsafe {
357 self.table_6
358 .position(pick_position(positions, last_5_challenge_bits, 6))
359 };
360 let positions = unsafe {
362 self.table_5
363 .position(pick_position(positions, last_5_challenge_bits, 5))
364 };
365 let positions = unsafe {
367 self.table_4
368 .position(pick_position(positions, last_5_challenge_bits, 4))
369 };
370 let positions = unsafe {
372 self.table_3
373 .position(pick_position(positions, last_5_challenge_bits, 3))
374 };
375 let [left_position, right_position] = unsafe {
377 self.table_2
378 .position(pick_position(positions, last_5_challenge_bits, 2))
379 };
380
381 let left_x = X::from(u32::from(left_position));
383 let right_x = X::from(u32::from(right_position));
384
385 let mut hasher = Sha256::new();
386 hasher.update(challenge);
387 let left_right_xs = (u64::from(left_x) << (u64::BITS as usize - usize::from(K)))
388 | (u64::from(right_x) << (u64::BITS as usize - usize::from(K * 2)));
389 hasher.update(
390 &left_right_xs.to_be_bytes()
391 [..(usize::from(K) * 2).div_ceil(u8::BITS as usize)],
392 );
393 hasher.finalize().into()
394 })
395 }
396
397 #[cfg(feature = "alloc")]
400 pub fn find_proof_raw(
401 &self,
402 first_k_challenge_bits: u32,
403 ) -> impl Iterator<Item = [u8; PROOF_SIZE::<K>]> + '_ {
404 self.table_7.buckets()[Y::bucket_range_from_first_k_bits(first_k_challenge_bits)]
406 .iter()
407 .flat_map(move |positions| {
408 positions
409 .iter()
410 .take_while(|&&(position, _y)| position != Position::SENTINEL)
411 .filter(move |&&(_position, y)| y.first_k_bits() == first_k_challenge_bits)
412 })
413 .map(move |&(position, _y)| {
414 let table_6_proof_targets = unsafe { self.table_7.position(position) };
416
417 Self::find_proof_raw_internal(
418 &self.table_2,
419 &self.table_3,
420 &self.table_4,
421 &self.table_5,
422 &self.table_6,
423 table_6_proof_targets,
424 )
425 })
426 }
427
428 #[cfg(feature = "alloc")]
429 #[inline(always)]
430 fn find_proof_raw_internal(
431 table_2: &PrunedTable<K, 2>,
432 table_3: &PrunedTable<K, 3>,
433 table_4: &PrunedTable<K, 4>,
434 table_5: &PrunedTable<K, 5>,
435 table_6: &PrunedTable<K, 6>,
436 table_6_proof_targets: [Position; 2],
437 ) -> [u8; PROOF_SIZE::<K>] {
438 let mut proof = [0u8; _];
439
440 table_6_proof_targets
442 .into_iter()
443 .flat_map(|position| {
444 unsafe { table_6.position(position) }
446 })
447 .flat_map(|position| {
448 unsafe { table_5.position(position) }
450 })
451 .flat_map(|position| {
452 unsafe { table_4.position(position) }
454 })
455 .flat_map(|position| {
456 unsafe { table_3.position(position) }
458 })
459 .flat_map(|position| {
460 unsafe { table_2.position(position) }
462 })
463 .map(|position| {
464 X::from(u32::from(position))
466 })
467 .enumerate()
468 .for_each(|(offset, x)| {
469 let x_offset_in_bits = usize::from(K) * offset;
470 let proof_bytes = &mut proof[x_offset_in_bits / u8::BITS as usize..]
472 [..(x_offset_in_bits % u8::BITS as usize + usize::from(K))
473 .div_ceil(u8::BITS as usize)];
474
475 let x_shifted = u32::from(x)
478 << (u32::BITS as usize
479 - (usize::from(K) + x_offset_in_bits % u8::BITS as usize));
480
481 x_shifted
484 .to_be_bytes()
485 .iter()
486 .zip(proof_bytes)
487 .for_each(|(from, to)| {
488 *to |= from;
489 });
490 });
491
492 proof
493 }
494
495 #[cfg(all(feature = "alloc", any(feature = "full-chiapos", test)))]
497 pub fn find_proof(
498 &self,
499 first_challenge_bytes: [u8; 4],
500 ) -> impl Iterator<Item = [u8; PROOF_SIZE::<K>]> + '_ {
501 let first_k_challenge_bits =
502 u32::from_be_bytes(first_challenge_bytes) >> (u32::BITS as usize - usize::from(K));
503
504 self.find_proof_raw(first_k_challenge_bits)
505 }
506
507 pub fn verify_only_raw(
510 seed: &Seed,
511 first_k_challenge_bits: u32,
512 proof_of_space: &[u8; PROOF_SIZE::<K>],
513 ) -> bool {
514 let ys_and_metadata = array::from_fn::<_, 64, _>(|offset| {
515 let mut pre_x_bytes = 0u64.to_be_bytes();
516 let offset_in_bits = usize::from(K) * offset;
517 let bytes_to_copy =
518 (offset_in_bits % u8::BITS as usize + usize::from(K)).div_ceil(u8::BITS as usize);
519 pre_x_bytes[..bytes_to_copy].copy_from_slice(
521 &proof_of_space[offset_in_bits / u8::BITS as usize..][..bytes_to_copy],
522 );
523 let pre_x = u64::from_be_bytes(pre_x_bytes)
525 >> (u64::BITS as usize - (usize::from(K) + offset_in_bits % u8::BITS as usize));
526 let x = X::from(pre_x as u32 & (u32::MAX >> (u32::BITS as usize - usize::from(K))));
528
529 let y = compute_f1::<K>(x, seed);
530
531 (y, Metadata::from(x))
532 });
533
534 let mut next_ys_and_metadata = [MaybeUninit::uninit(); _];
535 let ys_and_metadata =
536 Self::collect_ys_and_metadata::<2, 1, 64>(&ys_and_metadata, &mut next_ys_and_metadata);
537 let mut next_ys_and_metadata = [MaybeUninit::uninit(); _];
538 let ys_and_metadata =
539 Self::collect_ys_and_metadata::<3, 2, 32>(ys_and_metadata, &mut next_ys_and_metadata);
540 let mut next_ys_and_metadata = [MaybeUninit::uninit(); _];
541 let ys_and_metadata =
542 Self::collect_ys_and_metadata::<4, 3, 16>(ys_and_metadata, &mut next_ys_and_metadata);
543 let mut next_ys_and_metadata = [MaybeUninit::uninit(); _];
544 let ys_and_metadata =
545 Self::collect_ys_and_metadata::<5, 4, 8>(ys_and_metadata, &mut next_ys_and_metadata);
546 let mut next_ys_and_metadata = [MaybeUninit::uninit(); _];
547 let ys_and_metadata =
548 Self::collect_ys_and_metadata::<6, 5, 4>(ys_and_metadata, &mut next_ys_and_metadata);
549 let mut next_ys_and_metadata = [MaybeUninit::uninit(); _];
550 let ys_and_metadata =
551 Self::collect_ys_and_metadata::<7, 6, 2>(ys_and_metadata, &mut next_ys_and_metadata);
552
553 let Some((y, _metadata)) = ys_and_metadata.first() else {
554 return false;
555 };
556
557 y.first_k_bits() == first_k_challenge_bits
559 }
560
561 #[cfg(any(feature = "full-chiapos", test))]
563 pub fn verify(
564 seed: &Seed,
565 challenge: &Challenge,
566 proof_of_space: &[u8; PROOF_SIZE::<K>],
567 ) -> Option<Quality> {
568 let first_k_challenge_bits =
569 u32::from_be_bytes([challenge[0], challenge[1], challenge[2], challenge[3]])
570 >> (u32::BITS as usize - usize::from(K));
571
572 if !Self::verify_only_raw(seed, first_k_challenge_bits, proof_of_space) {
573 return None;
574 }
575
576 let last_5_challenge_bits = challenge[challenge.len() - 1] & 0b0001_1111;
577
578 let mut quality_index = 0_usize.to_be_bytes();
579 quality_index[0] = last_5_challenge_bits;
580 let quality_index = usize::from_be_bytes(quality_index);
581
582 let left_right_xs_bit_offset = quality_index * usize::from(K * 2);
585 let left_right_xs_bytes = &proof_of_space[left_right_xs_bit_offset / u8::BITS as usize..]
588 [..(left_right_xs_bit_offset % u8::BITS as usize + usize::from(K * 2))
589 .div_ceil(u8::BITS as usize)];
590
591 let mut left_right_xs = 0u64.to_be_bytes();
592 left_right_xs[..left_right_xs_bytes.len()].copy_from_slice(left_right_xs_bytes);
593 let left_right_xs =
595 u64::from_be_bytes(left_right_xs) << (left_right_xs_bit_offset % u8::BITS as usize);
596 let left_right_xs_mask = u64::MAX << (u64::BITS as usize - usize::from(K * 2));
598 let left_right_xs = left_right_xs & left_right_xs_mask;
599
600 let mut hasher = Sha256::new();
601 hasher.update(challenge);
602 hasher
603 .update(&left_right_xs.to_be_bytes()[..usize::from(K * 2).div_ceil(u8::BITS as usize)]);
604 Some(hasher.finalize().into())
605 }
606
607 fn collect_ys_and_metadata<
608 'a,
609 const TABLE_NUMBER: u8,
610 const PARENT_TABLE_NUMBER: u8,
611 const N: usize,
612 >(
613 ys_and_metadata: &[(Y, Metadata<K, PARENT_TABLE_NUMBER>)],
614 next_ys_and_metadata: &'a mut [MaybeUninit<(Y, Metadata<K, TABLE_NUMBER>)>; N],
615 ) -> &'a [(Y, Metadata<K, TABLE_NUMBER>)] {
616 let mut next_offset = 0_usize;
617 for &[(left_y, left_metadata), (right_y, right_metadata)] in
618 ys_and_metadata.as_chunks::<2>().0
619 {
620 if !has_match(left_y, right_y) {
621 continue;
622 }
623
624 next_ys_and_metadata[next_offset].write(compute_fn::<
625 K,
626 TABLE_NUMBER,
627 PARENT_TABLE_NUMBER,
628 >(
629 left_y, left_metadata, right_metadata
630 ));
631 next_offset += 1;
632 }
633
634 unsafe { next_ys_and_metadata[..next_offset].assume_init_ref() }
636 }
637}
638
639macro_rules! impl_supported {
640 ($($k: expr$(,)? )*) => {
641 $(
642impl SupportedKValue for Tables<$k> {}
643 )*
644 }
645}
646
647#[cfg(feature = "full-chiapos")]
649impl_supported!(15, 16, 18, 19, 21, 22, 23, 24, 25);
650#[cfg(any(feature = "full-chiapos", test))]
651impl_supported!(17);
652impl_supported!(20);