1use std::ffi::{CString, c_char};
2use std::fs::File;
3use std::io::{Error, Read, Result};
4use std::path::{Path, PathBuf};
5
6use serde::{Deserialize, Serialize};
7
8mod bits;
9
10pub const NUM_CHAIN_LINKS: usize = 16;
11
12#[repr(C)]
13#[derive(Default, Clone)]
14#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
15pub struct QualityChain {
18 pub chain_links: [u64; NUM_CHAIN_LINKS],
19}
20
21unsafe extern "C" {
22 fn validate_proof(
25 plot_id: *const u8,
26 k_size: u8,
27 strength: u8,
28 challenge: *const u8,
29 proof: *const u32,
30 quality: *mut QualityChain,
31 ) -> bool;
32
33 fn qualities_for_challenge(
34 plot_file: *const c_char,
35 challenge: *const u8,
36 output: *mut QualityChain,
37 num_outputs: u32,
38 ) -> u32;
39
40 fn solve_partial_proof(
44 quality: *const QualityChain,
45 plot_id: *const u8,
46 k: u8,
47 strength: u8,
48 output: *mut u32,
49 ) -> bool;
50
51 fn proof_to_quality_string(
56 plot_id: *const u8,
57 k: u8,
58 strength: u8,
59 proof: *const u32,
60 quality: *mut QualityChain,
61 ) -> bool;
62
63 fn create_plot(
64 filename: *const c_char,
65 k: u8,
66 strength: u8,
67 plot_id: *const u8,
68 index: u16,
69 meta_group: u8,
70 memo: *const u8,
71 memo_length: u8,
72 ) -> bool;
73}
74
75pub type Bytes32 = [u8; 32];
76
77pub fn solve_proof(
78 quality_proof: &QualityChain,
79 plot_id: &Bytes32,
80 k: u8,
81 strength: u8,
82) -> Vec<u8> {
83 let mut proof = [0_u32; 128];
84 if !unsafe {
89 solve_partial_proof(
90 quality_proof,
91 plot_id.as_ptr(),
92 k,
93 strength,
94 proof.as_mut_ptr(),
95 )
96 } {
97 return vec![];
98 }
99
100 bits::compact_bits(&proof, k)
101}
102
103pub fn validate_proof_v2(
104 plot_id: &Bytes32,
105 size: u8,
106 challenge: &Bytes32,
107 strength: u8,
108 proof: &[u8],
109) -> Option<QualityChain> {
110 let x_values = bits::expand_bits(proof, size)?;
111
112 if x_values.len() != NUM_CHAIN_LINKS * 8 {
113 return None;
115 }
116
117 let mut quality = QualityChain::default();
118 let valid = unsafe {
123 validate_proof(
124 plot_id.as_ptr(),
125 size,
126 strength,
127 challenge.as_ptr(),
128 x_values.as_ptr(),
129 &mut quality,
130 )
131 };
132 if valid { Some(quality) } else { None }
133}
134
135pub fn quality_string_from_proof(
138 plot_id: &Bytes32,
139 k: u8,
140 strength: u8,
141 proof: &[u8],
142) -> Option<QualityChain> {
143 let x_values = bits::expand_bits(proof, k)?;
144
145 if x_values.len() != NUM_CHAIN_LINKS * 8 {
146 return None;
147 }
148
149 let mut quality = QualityChain::default();
150 let ok = unsafe {
152 proof_to_quality_string(
154 plot_id.as_ptr(),
155 k,
156 strength,
157 x_values.as_ptr(),
158 &mut quality,
159 )
160 };
161 if ok { Some(quality) } else { None }
162}
163
164pub fn create_v2_plot(
165 filename: &Path,
166 k: u8,
167 strength: u8,
168 plot_id: &Bytes32,
169 index: u16,
170 meta_group: u8,
171 memo: &[u8],
172) -> Result<()> {
173 let Some(filename) = filename.to_str() else {
174 return Err(Error::other("invalid path"));
175 };
176
177 if memo.len() > 255 {
178 return Err(Error::other("invalid memo"));
179 };
180
181 let filename = CString::new(filename)?;
182 let success: bool = unsafe {
191 create_plot(
192 filename.as_ptr(),
193 k,
194 strength,
195 plot_id.as_ptr(),
196 index,
197 meta_group,
198 memo.as_ptr(),
199 memo.len() as u8,
200 )
201 };
202 if success {
203 Ok(())
204 } else {
205 Err(Error::other("failed to create plot file"))
206 }
207}
208
209pub fn serialize_quality(
217 fragments: &[u64; NUM_CHAIN_LINKS],
218 strength: u8,
219) -> [u8; NUM_CHAIN_LINKS * 8 + 1] {
220 let mut ret = [0_u8; 129];
221
222 ret[0] = strength;
223 let mut idx = 1;
224 for cl in fragments {
225 ret[idx..(idx + 8)].clone_from_slice(&cl.to_le_bytes());
226 idx += 8;
227 }
228 ret
229}
230
231#[derive(Serialize, Deserialize)]
233pub struct Prover {
234 path: PathBuf,
235 plot_id: Bytes32,
236 memo: Vec<u8>,
237 strength: u8,
238 index: u16,
239 meta_group: u8,
240 size: u8,
241}
242
243impl Prover {
244 pub fn new(plot_path: &Path) -> Result<Prover> {
245 let mut file = File::open(plot_path)?;
246
247 let mut header = [0_u8; 4 + 1 + 32 + 1 + 1 + 1 + 2 + 1 + 128];
259 file.read_exact(&mut header)?;
260
261 let mut offset: usize = 0;
262 if &header[offset..(offset + 4)] != b"pos2" {
263 return Err(Error::other("Not a plotfile"));
264 }
265 offset += 4;
266 if header[offset] != 1 {
267 return Err(Error::other("unsupported plot version"));
268 }
269 offset += 1;
270 let plot_id: [u8; 32] = header[offset..(offset + 32)].try_into().unwrap();
271 offset += 32;
272 let size = header[offset];
273 if !(18..=32).contains(&size) || (size % 2) != 0 {
274 return Err(Error::other("invalid k-size"));
275 }
276 offset += 1;
277
278 let strength = header[offset];
279 if strength < 2 {
280 return Err(Error::other("invalid strength"));
281 }
282 offset += 1;
283
284 let index = u16::from_le_bytes(header[offset..offset + 2].try_into().unwrap());
285 offset += 2;
286
287 let meta_group = header[offset];
288 offset += 1;
289
290 let memo_len = header[offset];
291 offset += 1;
292
293 let memo: &[u8] = &header[offset..(offset + memo_len as usize)];
294
295 Ok(Prover {
296 path: plot_path.to_path_buf(),
297 plot_id,
298 memo: memo.to_vec(),
299 strength,
300 index,
301 meta_group,
302 size,
303 })
304 }
305
306 pub fn get_qualities_for_challenge(&self, challenge: &Bytes32) -> Result<Vec<QualityChain>> {
307 let Some(plot_path) = self.path.to_str() else {
308 return Err(Error::other("invalid path"));
309 };
310
311 let plot_path = CString::new(plot_path)?;
312
313 let mut results = Vec::<QualityChain>::with_capacity(10);
314 unsafe {
320 let num_results = qualities_for_challenge(
321 plot_path.as_ptr(),
322 challenge.as_ptr(),
323 results.as_mut_ptr(),
324 10,
325 );
326 results.set_len(num_results as usize);
327 }
328 Ok(results)
329 }
330
331 pub fn size(&self) -> u8 {
332 self.size
333 }
334
335 pub fn plot_id(&self) -> &Bytes32 {
336 &self.plot_id
337 }
338
339 pub fn get_strength(&self) -> u8 {
340 self.strength
341 }
342
343 pub fn get_filename(&self) -> String {
344 self.path.to_string_lossy().into_owned()
347 }
348
349 pub fn get_memo(&self) -> &[u8] {
350 &self.memo
351 }
352}
353
354#[cfg(test)]
355mod tests {
356 use super::*;
357 use rstest::rstest;
358
359 #[test]
362 fn test_plot_roundtrip() {
363 let k = 20u8;
364 let strength = 2u8;
365 let index = 0u16;
366 let meta_group = 0u8;
367 let plot_id: Bytes32 = [0xab; 32];
368 let memo = [0u8; 112];
369 let plot_path = std::env::temp_dir().join("pos2_chia_test_k20.plot");
370
371 if !plot_path.exists() {
372 create_v2_plot(&plot_path, k, strength, &plot_id, index, meta_group, &memo)
373 .expect("create_v2_plot");
374 }
375
376 let prover = Prover::new(&plot_path).expect("open prover");
377 assert_eq!(prover.size(), k);
378 assert_eq!(prover.get_strength(), strength);
379 let plot_id = *prover.plot_id();
380
381 let mut num_proofs = 0;
382 let mut challenge = [0u8; 32];
383 for challenge_idx in 0..300u32 {
384 challenge[0..4].copy_from_slice(&challenge_idx.to_le_bytes());
385
386 let qualities = prover
387 .get_qualities_for_challenge(&challenge)
388 .expect("get_qualities_for_challenge");
389
390 for quality in qualities {
391 let proof = solve_proof(&quality, &plot_id, k, strength);
392 assert!(!proof.is_empty(), "failed to solve proof");
393 num_proofs += 1;
394 let validated = validate_proof_v2(&plot_id, k, &challenge, strength, &proof);
395 assert!(
396 validated.is_some(),
397 "proof should validate for challenge {challenge_idx}",
398 );
399 let recovered = quality_string_from_proof(&plot_id, k, strength, &proof);
400 let recovered = recovered.expect("quality_string_from_proof");
401 assert_eq!(
402 quality.chain_links, recovered.chain_links,
403 "challenge {challenge_idx}: quality roundtrip must match",
404 );
405 }
406 }
407 assert_eq!(num_proofs, 9);
408 }
409
410 #[rstest]
411 fn test_serialize_quality(
412 #[values(1, 0xff00, 0x777777)] step_size: u64,
413 #[values(0, 0xffffffff00000000, 0xff00ff00ff00ff00)] fragment_start: u64,
414 #[values(
415 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 23, 26, 28, 33, 63, 64, 100, 200, 240, 255
416 )]
417 strength: u8,
418 ) {
419 let mut quality = QualityChain::default();
420
421 let mut idx = fragment_start;
422 for link in &mut quality.chain_links {
423 *link = idx;
424 idx += step_size;
425 }
426
427 let quality_str = serialize_quality(&quality.chain_links, strength);
428 assert_eq!(quality_str[0], strength);
429 idx = fragment_start;
430 for i in (1..(NUM_CHAIN_LINKS * 8 + 1)).step_by(8) {
431 assert_eq!(
432 u64::from_le_bytes(quality_str[i..(i + 8)].try_into().unwrap()),
433 idx
434 );
435 idx += step_size;
436 }
437 }
438}