diff --git a/benches/verifying.rs b/benches/verifying.rs index 22a6f97f..a1c36d8f 100644 --- a/benches/verifying.rs +++ b/benches/verifying.rs @@ -1,48 +1,59 @@ +use std::sync::atomic::AtomicBool; + use criterion::{criterion_group, criterion_main, Criterion}; use post::{ - config::ScryptParams, + config::{InitConfig, ProofConfig, ScryptParams}, + initialize::{CpuInitializer, Initialize}, metadata::ProofMetadata, pow::randomx::{PoW, RandomXFlag}, - prove::Proof, - verification::{Verifier, VerifyingParams}, + prove::generate_proof, + verification::Verifier, }; #[cfg(not(windows))] use pprof::criterion::{Output, PProfProfiler}; +use tempfile::tempdir; fn verifying(c: &mut Criterion) { let challenge = b"hello world, challenge me!!!!!!!"; - let metadata = ProofMetadata { - node_id: [0u8; 32], - commitment_atx_id: [0u8; 32], - challenge: *challenge, - num_units: 1, - labels_per_unit: 1024 * 1024 * 1024, - }; - let num_labels = metadata.num_units as u64 * metadata.labels_per_unit; - - let verifier = Verifier::new(Box::new( - PoW::new(RandomXFlag::get_recommended_flags()).unwrap(), - )); + let datadir = tempdir().unwrap(); - let (k2, k3) = (37, 37); - let proof = Proof::new( - 0, - (0..k2 as u64).collect::>().as_slice(), - num_labels, - 0, - ); - let params = VerifyingParams { - difficulty: u64::MAX, - k2, - k3, + let cfg = ProofConfig { + k1: 199, + k2: 37, + k3: 37, pow_difficulty: [0xFF; 32], + }; + let init_cfg = InitConfig { + min_num_units: 1, + max_num_units: 1, + labels_per_unit: 200, scrypt: ScryptParams::new(8192, 1, 1), }; + let metadata = CpuInitializer::new(init_cfg.scrypt) + .initialize( + datadir.path(), + &[0u8; 32], + &[0u8; 32], + init_cfg.labels_per_unit, + 1, + init_cfg.labels_per_unit, + None, + ) + .unwrap(); + + let pow_flags = RandomXFlag::get_recommended_flags(); + // Generate a proof + let stop = AtomicBool::new(false); + let proof = generate_proof(datadir.path(), challenge, cfg, 32, 1, pow_flags, stop).unwrap(); + let metadata = ProofMetadata::new(metadata, *challenge); + + // Bench verifying the proof + let verifier = Verifier::new(Box::new(PoW::new(pow_flags).unwrap())); c.bench_function("verify", |b| { b.iter(|| { verifier - .verify(&proof, &metadata, params) + .verify(&proof, &metadata, &cfg, &init_cfg) .expect("proof should be valid"); }); }); diff --git a/ffi/src/initialization.rs b/ffi/src/initialization.rs index 3b2b2853..32bcd8e5 100644 --- a/ffi/src/initialization.rs +++ b/ffi/src/initialization.rs @@ -416,22 +416,15 @@ mod tests { fn initialize_and_verify() { // Initialize some data let datadir = tempdir().unwrap(); + let scrypt = ScryptParams::new(2, 1, 1); - let cfg = post::config::Config { - k1: 23, - k2: 32, - k3: 10, - pow_difficulty: [0xFF; 32], - scrypt: ScryptParams::new(2, 1, 1), - }; - - CpuInitializer::new(cfg.scrypt) + CpuInitializer::new(scrypt) .initialize(datadir.path(), &[0u8; 32], &[0u8; 32], 256, 31, 700, None) .unwrap(); // Verify the data let datapath = CString::new(datadir.path().to_str().unwrap()).unwrap(); - let result = verify_pos(datapath.as_ptr(), null(), null(), 100.0, cfg.scrypt); + let result = verify_pos(datapath.as_ptr(), null(), null(), 100.0, scrypt); assert_eq!(VerifyResult::Ok, result); // verify with wrong scrypt params @@ -441,7 +434,7 @@ mod tests { // verify with non-existent path let path = CString::new("non-existent-path").unwrap(); - let result = verify_pos(path.as_ptr(), null(), null(), 100.0, cfg.scrypt); + let result = verify_pos(path.as_ptr(), null(), null(), 100.0, scrypt); assert_eq!(VerifyResult::Failed, result); } } diff --git a/ffi/src/post_impl.rs b/ffi/src/post_impl.rs index f6d461c5..7ed6e81e 100644 --- a/ffi/src/post_impl.rs +++ b/ffi/src/post_impl.rs @@ -9,11 +9,11 @@ use std::{ }; use post::{ - config::Config, + config::{InitConfig, ProofConfig}, metadata::ProofMetadata, pow::randomx::{PoW, RandomXFlag}, prove, - verification::{Verifier, VerifyingParams}, + verification::Verifier, }; use crate::ArrayU8; @@ -72,7 +72,7 @@ pub unsafe extern "C" fn free_proof(proof: *mut Proof) { pub extern "C" fn generate_proof( datadir: *const c_char, challenge: *const c_uchar, - cfg: Config, + cfg: ProofConfig, nonces: usize, threads: usize, pow_flags: RandomXFlag, @@ -90,7 +90,7 @@ pub extern "C" fn generate_proof( fn _generate_proof( datadir: *const c_char, challenge: *const c_uchar, - cfg: Config, + cfg: ProofConfig, nonces: usize, threads: usize, pow_flags: RandomXFlag, @@ -168,7 +168,8 @@ pub unsafe extern "C" fn verify_proof( verifier: *const Verifier, proof: Proof, metadata: *const ProofMetadata, - cfg: Config, + cfg: ProofConfig, + init_cfg: InitConfig, ) -> VerifyResult { let verifier = match verifier.as_ref() { Some(verifier) => verifier, @@ -191,12 +192,7 @@ pub unsafe extern "C" fn verify_proof( None => return VerifyResult::InvalidArgument, }; - let params = match VerifyingParams::new(metadata, &cfg) { - Ok(params) => params, - Err(_) => return VerifyResult::InvalidArgument, - }; - - match verifier.verify(&proof, metadata, params) { + match verifier.verify(&proof, metadata, &cfg, &init_cfg) { Ok(_) => VerifyResult::Ok, Err(err) => { log::error!("Proof is invalid: {err}"); @@ -215,12 +211,11 @@ mod tests { #[test] fn datadir_must_be_utf8() { let datadir = std::ffi::CString::new([159, 146, 150]).unwrap(); - let cfg = super::Config { + let cfg = super::ProofConfig { k1: 10, k2: 20, k3: 20, pow_difficulty: [0xFF; 32], - scrypt: ScryptParams::new(2, 1, 1), }; let result = super::_generate_proof( datadir.as_ptr(), @@ -253,11 +248,16 @@ mod tests { pow: 0, }, std::ptr::null(), - super::Config { + super::ProofConfig { k1: 1, k2: 2, k3: 2, pow_difficulty: [0xFF; 32], + }, + super::InitConfig { + min_num_units: 1, + max_num_units: 1, + labels_per_unit: 1, scrypt: ScryptParams::new(2, 1, 1), }, ) @@ -268,10 +268,9 @@ mod tests { #[test] fn test_end_to_end() { // Initialize some data first - let labels_per_unit = 200; let datadir = tempfile::tempdir().unwrap(); - let cfg = post::config::Config { + let cfg = post::config::ProofConfig { k1: 10, k2: 10, k3: 10, @@ -280,17 +279,23 @@ mod tests { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, ], + }; + + let init_cfg = post::config::InitConfig { + min_num_units: 1, + max_num_units: 2, + labels_per_unit: 200, scrypt: ScryptParams::new(2, 1, 1), }; - let meta = post::initialize::CpuInitializer::new(cfg.scrypt) + let meta = post::initialize::CpuInitializer::new(init_cfg.scrypt) .initialize( datadir.path(), &[77; 32], &[0u8; 32], - labels_per_unit, + init_cfg.labels_per_unit, 2, - labels_per_unit, + 100, None, ) .unwrap(); @@ -316,17 +321,11 @@ mod tests { pow_flags, ); - let proof_metadata = ProofMetadata { - node_id: meta.node_id, - commitment_atx_id: meta.commitment_atx_id, - challenge: *challenge, - num_units: meta.num_units, - labels_per_unit: meta.labels_per_unit, + let proof_metadata = ProofMetadata::new(meta, *challenge); + let result = unsafe { + crate::post_impl::verify_proof(verifier, *cproof, &proof_metadata, cfg, init_cfg) }; - let result = - unsafe { crate::post_impl::verify_proof(verifier, *cproof, &proof_metadata as _, cfg) }; - assert_eq!(result, super::VerifyResult::Ok); // Modify the proof to have different k2pow @@ -338,7 +337,7 @@ mod tests { }; let result = unsafe { - crate::post_impl::verify_proof(verifier, invalid_proof, &proof_metadata as _, cfg) + crate::post_impl::verify_proof(verifier, invalid_proof, &proof_metadata, cfg, init_cfg) }; assert_eq!(result, super::VerifyResult::Invalid); diff --git a/service/src/main.rs b/service/src/main.rs index c514f8be..7f59c41b 100644 --- a/service/src/main.rs +++ b/service/src/main.rs @@ -39,14 +39,23 @@ struct Cli { #[derive(Args, Debug)] /// POST configuration - network parameters struct PostConfig { + /// The minimal number of units that must be initialized. + #[arg(long, default_value_t = 4)] + pub min_num_units: u32, + /// The maximal number of units that can be initialized. + #[arg(long, default_value_t = u32::MAX)] + pub max_num_units: u32, + /// The number of labels per unit. + #[arg(long, default_value_t = 4294967296)] + pub labels_per_unit: u64, /// K1 specifies the difficulty for a label to be a candidate for a proof - #[arg(long, default_value = "26")] + #[arg(long, default_value_t = 26)] k1: u32, /// K2 is the number of labels below the required difficulty required for a proof - #[arg(long, default_value = "37")] + #[arg(long, default_value_t = 37)] k2: u32, /// K3 is the size of the subset of proof indices that is validated - #[arg(long, default_value = "37")] + #[arg(long, default_value_t = 37)] k3: u32, /// difficulty for the nonce proof of work (aka "k2pow") #[arg( @@ -164,18 +173,24 @@ async fn main() -> eyre::Result<()> { log::info!("POST network parameters: {:?}", args.post_config); log::info!("POST proving settings: {:?}", args.post_settings); + let scrypt = post::config::ScryptParams::new( + args.post_config.scrypt.n, + args.post_config.scrypt.r, + args.post_config.scrypt.p, + ); let service = post_service::service::PostService::new( args.dir, - post::config::Config { + post::config::ProofConfig { k1: args.post_config.k1, k2: args.post_config.k2, k3: args.post_config.k3, pow_difficulty: args.post_config.pow_difficulty, - scrypt: post::config::ScryptParams::new( - args.post_config.scrypt.n, - args.post_config.scrypt.r, - args.post_config.scrypt.p, - ), + }, + post::config::InitConfig { + min_num_units: args.post_config.min_num_units, + max_num_units: args.post_config.max_num_units, + labels_per_unit: args.post_config.labels_per_unit, + scrypt, }, args.post_settings.nonces, args.post_settings.threads, diff --git a/service/src/service.rs b/service/src/service.rs index f8062795..e27766f5 100644 --- a/service/src/service.rs +++ b/service/src/service.rs @@ -10,7 +10,7 @@ use post::{ metadata::{PostMetadata, ProofMetadata}, pow::randomx::{PoW, RandomXFlag}, prove::Proof, - verification::{Verifier, VerifyingParams}, + verification::Verifier, }; #[derive(Debug)] @@ -27,7 +27,8 @@ struct ProofGenProcess { pub struct PostService { datadir: PathBuf, - cfg: post::config::Config, + cfg: post::config::ProofConfig, + init_cfg: post::config::InitConfig, nonces: usize, threads: usize, pow_flags: RandomXFlag, @@ -40,7 +41,8 @@ pub struct PostService { impl PostService { pub fn new( datadir: PathBuf, - cfg: post::config::Config, + cfg: post::config::ProofConfig, + init_cfg: post::config::InitConfig, nonces: usize, threads: usize, pow_flags: RandomXFlag, @@ -49,6 +51,7 @@ impl PostService { proof_generation: Mutex::new(None), datadir, cfg, + init_cfg, nonces, threads, pow_flags, @@ -113,7 +116,7 @@ impl crate::client::PostService for PostService { fn verify_proof(&self, proof: &Proof, metadata: &ProofMetadata) -> eyre::Result<()> { self.verifier - .verify(proof, metadata, VerifyingParams::new(metadata, &self.cfg)?) + .verify(proof, metadata, &self.cfg, &self.init_cfg) .wrap_err("verifying proof") } diff --git a/service/tests/test_client.rs b/service/tests/test_client.rs index 80a1fdc4..eb8c659e 100644 --- a/service/tests/test_client.rs +++ b/service/tests/test_client.rs @@ -224,15 +224,20 @@ async fn test_broken_request_no_kind() { #[tokio::test] async fn test_get_metadata(#[case] vrf_difficulty: Option<[u8; 32]>) { let datadir = tempdir().unwrap(); - let cfg = post::config::Config { + let cfg = post::config::ProofConfig { k1: 23, k2: 32, k3: 10, pow_difficulty: [0xFF; 32], + }; + let init_cfg = post::config::InitConfig { + min_num_units: 1, + max_num_units: 1000, + labels_per_unit: 256 * 16, scrypt: post::config::ScryptParams::new(2, 1, 1), }; - let metadata = CpuInitializer::new(cfg.scrypt) + let metadata = CpuInitializer::new(init_cfg.scrypt) .initialize( datadir.path(), &[77; 32], @@ -249,6 +254,7 @@ async fn test_get_metadata(#[case] vrf_difficulty: Option<[u8; 32]>) { let service = post_service::service::PostService::new( datadir.path().into(), cfg, + init_cfg, 16, 1, post::pow::randomx::RandomXFlag::get_recommended_flags(), diff --git a/service/tests/test_service.rs b/service/tests/test_service.rs index 60ad31aa..65dbe832 100644 --- a/service/tests/test_service.rs +++ b/service/tests/test_service.rs @@ -1,7 +1,7 @@ use std::{thread::sleep, time::Duration}; use post::{ - config::ScryptParams, + config::{InitConfig, ProofConfig, ScryptParams}, initialize::{CpuInitializer, Initialize}, metadata::ProofMetadata, pow::randomx::RandomXFlag, @@ -11,25 +11,29 @@ use post_service::{client::PostService, service::ProofGenState}; #[test] fn test_generate_and_verify() { // Initialize some data - let labels_per_unit = 256; let datadir = tempfile::tempdir().unwrap(); - let cfg = post::config::Config { + let cfg = ProofConfig { k1: 8, k2: 4, k3: 4, pow_difficulty: [0xFF; 32], + }; + let init_cfg = InitConfig { + min_num_units: 1, + max_num_units: 1000, + labels_per_unit: 256, scrypt: ScryptParams::new(2, 1, 1), }; - let metadata = CpuInitializer::new(cfg.scrypt) + let metadata = CpuInitializer::new(init_cfg.scrypt) .initialize( datadir.path(), &[0xBE; 32], &[0xCE; 32], - labels_per_unit, + init_cfg.labels_per_unit, 4, - labels_per_unit, + init_cfg.labels_per_unit, None, ) .unwrap(); @@ -37,9 +41,15 @@ fn test_generate_and_verify() { let pow_flags = RandomXFlag::get_recommended_flags(); // Generate a proof - let service = - post_service::service::PostService::new(datadir.into_path(), cfg, 16, 1, pow_flags) - .unwrap(); + let service = post_service::service::PostService::new( + datadir.into_path(), + cfg, + init_cfg, + 16, + 1, + pow_flags, + ) + .unwrap(); let (proof, metadata) = loop { if let ProofGenState::Finished { proof } = service.gen_proof(vec![0xCA; 32]).unwrap() { @@ -57,25 +67,29 @@ fn test_generate_and_verify() { #[test] fn reject_invalid_challenge() { // Initialize some data - let labels_per_unit = 256; let datadir = tempfile::tempdir().unwrap(); - let cfg = post::config::Config { + let cfg = ProofConfig { k1: 8, k2: 4, k3: 4, pow_difficulty: [0xFF; 32], + }; + let init_cfg = InitConfig { + min_num_units: 1, + max_num_units: 1000, + labels_per_unit: 256, scrypt: ScryptParams::new(2, 1, 1), }; - CpuInitializer::new(cfg.scrypt) + CpuInitializer::new(init_cfg.scrypt) .initialize( datadir.path(), &[0xBE; 32], &[0xCE; 32], - labels_per_unit, + init_cfg.labels_per_unit, 4, - labels_per_unit, + init_cfg.labels_per_unit, None, ) .unwrap(); @@ -84,6 +98,7 @@ fn reject_invalid_challenge() { let service = post_service::service::PostService::new( datadir.into_path(), cfg, + init_cfg, 16, 1, RandomXFlag::get_recommended_flags(), @@ -95,25 +110,29 @@ fn reject_invalid_challenge() { #[test] fn cannot_run_parallel_proof_gens() { // Initialize some data - let labels_per_unit = 256; let datadir = tempfile::tempdir().unwrap(); - let cfg = post::config::Config { + let cfg = ProofConfig { k1: 8, k2: 4, k3: 4, pow_difficulty: [0xFF; 32], + }; + let init_cfg = InitConfig { + min_num_units: 1, + max_num_units: 1000, + labels_per_unit: 256, scrypt: ScryptParams::new(2, 1, 1), }; - CpuInitializer::new(cfg.scrypt) + CpuInitializer::new(init_cfg.scrypt) .initialize( datadir.path(), &[0xBE; 32], &[0xCE; 32], - labels_per_unit, + init_cfg.labels_per_unit, 4, - labels_per_unit, + init_cfg.labels_per_unit, None, ) .unwrap(); @@ -122,6 +141,7 @@ fn cannot_run_parallel_proof_gens() { let service = post_service::service::PostService::new( datadir.into_path(), cfg, + init_cfg, 16, 1, RandomXFlag::get_recommended_flags(), diff --git a/src/config.rs b/src/config.rs index b451a6dd..65519eaf 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,19 @@ #[repr(C)] #[derive(Debug, Clone, Copy)] -pub struct Config { +pub struct InitConfig { + /// The minimal number of units that must be initialized. + pub min_num_units: u32, + /// The maximal number of units that can be initialized. + pub max_num_units: u32, + /// The number of labels per unit. + pub labels_per_unit: u64, + /// Scrypt paramters for initilizing labels + pub scrypt: ScryptParams, +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct ProofConfig { /// K1 specifies the difficulty for a label to be a candidate for a proof. pub k1: u32, /// K2 is the number of labels below the required difficulty required for a proof. @@ -10,8 +23,6 @@ pub struct Config { /// Difficulty for the nonce proof of work. Lower values increase difficulty of finding /// `pow` for [Proof][crate::prove::Proof]. pub pow_difficulty: [u8; 32], - /// Scrypt paramters for initilizing labels - pub scrypt: ScryptParams, } #[repr(C)] diff --git a/src/difficulty.rs b/src/difficulty.rs index dd7aca2e..aabc91da 100644 --- a/src/difficulty.rs +++ b/src/difficulty.rs @@ -1,4 +1,4 @@ -use eyre::Context; +use primitive_types::U256; /// Calculate proving difficulty. /// @@ -7,14 +7,30 @@ use eyre::Context; /// /// The difficulty is calculated as: /// difficulty = 2^64 * K1 / num_labels -pub(crate) fn proving_difficulty(k1: u32, num_labels: u64) -> eyre::Result { - eyre::ensure!(num_labels > 0, "number of label blocks must be > 0"); - eyre::ensure!( - num_labels > k1 as u64, - format!("number of labels ({num_labels}) must be bigger than k1 ({k1})") - ); +pub(crate) fn proving_difficulty(k1: u32, num_labels: u64) -> Result { + if num_labels == 0 { + return Err("number of label blocks must be > 0".to_string()); + } + if num_labels <= k1 as u64 { + return Err(format!( + "number of labels ({num_labels}) must be bigger than k1 ({k1})" + )); + } let difficulty = (1u128 << 64) * k1 as u128 / num_labels as u128; - u64::try_from(difficulty).wrap_err("difficulty doesn't fit in u64") + u64::try_from(difficulty).or(Err("difficulty doesn't fit in u64".to_string())) +} + +/// Scale PoW difficulty by the number of units. +/// +/// The more units of data, the more difficult the PoW should be (linearly). +/// Because the PoW looks for values < difficulty, we need to scale the difficulty down. +/// The difficulty threshold is calculated as: +/// difficulty = difficulty / num_units +pub(crate) fn scale_pow_difficulty(difficulty: &[u8; 32], num_units: u32) -> [u8; 32] { + let difficulty_scaled = U256::from_big_endian(difficulty) / num_units; + let mut difficulty = [0u8; 32]; + difficulty_scaled.to_big_endian(&mut difficulty); + difficulty } #[test] @@ -34,3 +50,31 @@ fn difficulty_calculation() { assert_eq!(proving_difficulty(1, 4).unwrap(), 1u64 << (64 - 2)); assert_eq!(proving_difficulty(1, 128).unwrap(), 1u64 << (64 - 7)); } + +/// Test that PoW threshold is scaled with num_units. +#[test] +fn scaling_pow_thresholds() { + { + // don't scale when num_units is 1 + let difficulty = scale_pow_difficulty(&[0xFF; 32], 1); + assert_eq!(difficulty, [0xFF; 32]); + } + { + // scale with num_units + let difficulty = scale_pow_difficulty(&[0xFF; 32], 2); + assert!(difficulty < [0xFF; 32]); + assert_eq!( + difficulty.as_slice(), + [&[0x7F], [0xFF; 31].as_slice()].concat() + ); + } + { + // scale with num_units + let difficulty = scale_pow_difficulty(&[0xFF; 32], 2_u32.pow(5)); + assert!(difficulty < [0xFF; 32]); + assert_eq!( + difficulty.as_slice(), + [&[0xFF >> 5], [0xFF; 31].as_slice()].concat() + ); + } +} diff --git a/src/prove.rs b/src/prove.rs index b21ac060..d74b8eda 100644 --- a/src/prove.rs +++ b/src/prove.rs @@ -25,7 +25,7 @@ use rayon::prelude::{ParallelBridge, ParallelIterator}; use crate::{ cipher::AesCipher, compression::{compress_indices, required_bits}, - config::Config, + config::ProofConfig, difficulty::proving_difficulty, metadata::{self, PostMetadata}, pow, @@ -61,13 +61,13 @@ pub struct ProvingParams { } impl ProvingParams { - pub fn new(metadata: &PostMetadata, cfg: &Config) -> eyre::Result { + pub fn new(metadata: &PostMetadata, cfg: &ProofConfig) -> eyre::Result { let num_labels = metadata.num_units as u64 * metadata.labels_per_unit; let mut pow_difficulty = [0u8; 32]; let difficulty_scaled = U256::from_big_endian(&cfg.pow_difficulty) / metadata.num_units; difficulty_scaled.to_big_endian(&mut pow_difficulty); Ok(Self { - difficulty: proving_difficulty(cfg.k1, num_labels)?, + difficulty: proving_difficulty(cfg.k1, num_labels).map_err(|e| eyre::eyre!(e))?, pow_difficulty, }) } @@ -264,7 +264,7 @@ impl Prover for Prover8_56 { pub fn generate_proof( datadir: &Path, challenge: &[u8; 32], - cfg: Config, + cfg: ProofConfig, nonces: usize, threads: usize, pow_flags: RandomXFlag, @@ -354,9 +354,7 @@ where #[cfg(test)] mod tests { use super::*; - use crate::{ - compression::decompress_indexes, config::ScryptParams, difficulty::proving_difficulty, - }; + use crate::{compression::decompress_indexes, difficulty::proving_difficulty}; use mockall::predicate::{always, eq}; use rand::{thread_rng, RngCore}; use std::{collections::HashMap, iter::repeat}; @@ -384,12 +382,11 @@ mod tests { max_file_size: 1024, ..Default::default() }; - let cfg = Config { + let cfg = ProofConfig { k1: 279, k2: 300, k3: 65, pow_difficulty: [0xFF; 32], - scrypt: ScryptParams::new(2, 1, 1), }; let params = ProvingParams::new(&meta, &cfg).unwrap(); let mut pow_prover = pow::MockProver::new(); @@ -420,12 +417,11 @@ mod tests { max_file_size: 1024, ..Default::default() }; - let cfg = Config { + let cfg = ProofConfig { k1: 279, k2: 300, k3: 65, pow_difficulty: [0xFF; 32], - scrypt: ScryptParams::new(4, 1, 1), }; let mut pow_prover = pow::MockProver::new(); pow_prover @@ -439,12 +435,11 @@ mod tests { /// Test that PoW threshold is scaled with num_units. #[test] fn scaling_pows_thresholds() { - let cfg = Config { + let cfg = ProofConfig { k1: 32, k2: 32, k3: 10, pow_difficulty: [0x0F; 32], - scrypt: ScryptParams::new(2, 1, 1), }; let metadata = PostMetadata { num_units: 1, diff --git a/src/verification.rs b/src/verification.rs index 87d7d3c5..fbc4bd34 100644 --- a/src/verification.rs +++ b/src/verification.rs @@ -39,13 +39,12 @@ use std::cmp::Ordering; use cipher::BlockEncrypt; use itertools::Itertools; -use primitive_types::U256; use crate::{ cipher::AesCipher, compression::{decompress_indexes, required_bits}, - config::{Config, ScryptParams}, - difficulty::proving_difficulty, + config::{InitConfig, ProofConfig}, + difficulty::{proving_difficulty, scale_pow_difficulty}, initialize::{calc_commitment, generate_label}, metadata::ProofMetadata, pow::PowVerifier, @@ -55,35 +54,6 @@ use crate::{ const NONCES_PER_AES: u32 = Prover8_56::NONCES_PER_AES; -#[derive(Debug, Clone, Copy)] -pub struct VerifyingParams { - pub difficulty: u64, - pub k2: u32, - pub k3: u32, - pub pow_difficulty: [u8; 32], - pub scrypt: ScryptParams, -} - -impl VerifyingParams { - pub fn new(metadata: &ProofMetadata, cfg: &Config) -> eyre::Result { - let num_labels = metadata.num_units as u64 * metadata.labels_per_unit; - - // Scale PoW difficulty by number of units - eyre::ensure!(metadata.num_units > 0, "num_units must be > 0"); - let difficulty_scaled = U256::from_big_endian(&cfg.pow_difficulty) / metadata.num_units; - let mut pow_difficulty = [0u8; 32]; - difficulty_scaled.to_big_endian(&mut pow_difficulty); - - Ok(Self { - difficulty: proving_difficulty(cfg.k1, num_labels)?, - k2: cfg.k2, - k3: cfg.k3, - pow_difficulty, - scrypt: cfg.scrypt, - }) - } -} - pub struct Verifier { pow_verifier: Box, } @@ -110,6 +80,45 @@ pub enum Error { difficulty_lsb: u64, label: [u8; 16], }, + #[error(transparent)] + InvalidMetadata(#[from] MetadataValidationError), + #[error("invalid number of labels: (0)")] + InvalidNumLabels(String), +} + +#[derive(thiserror::Error, Debug)] +pub enum MetadataValidationError { + #[error("numunits too small: {got} < {min}")] + NumUnitsTooSmall { min: u32, got: u32 }, + #[error("numunits too large: {got} < {max}")] + NumUnitsTooLarge { max: u32, got: u32 }, + #[error("invalid labels_per_unit: {got} != {expected}")] + LabelsPerUnitInvalid { expected: u64, got: u64 }, +} + +pub fn verify_metadata( + metadata: &ProofMetadata, + init_cfg: &InitConfig, +) -> Result<(), MetadataValidationError> { + if metadata.num_units < init_cfg.min_num_units { + return Err(MetadataValidationError::NumUnitsTooSmall { + min: init_cfg.min_num_units, + got: metadata.num_units, + }); + } + if metadata.num_units > init_cfg.max_num_units { + return Err(MetadataValidationError::NumUnitsTooLarge { + max: init_cfg.max_num_units, + got: metadata.num_units, + }); + } + if metadata.labels_per_unit != init_cfg.labels_per_unit { + return Err(MetadataValidationError::LabelsPerUnitInvalid { + expected: init_cfg.labels_per_unit, + got: metadata.labels_per_unit, + }); + } + Ok(()) } impl Verifier { @@ -129,9 +138,13 @@ impl Verifier { &self, proof: &Proof, metadata: &ProofMetadata, - params: VerifyingParams, + cfg: &ProofConfig, + init_cfg: &InitConfig, ) -> Result<(), Error> { + verify_metadata(metadata, init_cfg)?; + let challenge = metadata.challenge; + let pow_difficulty = scale_pow_difficulty(&cfg.pow_difficulty, metadata.num_units); // Verify K2 PoW let nonce_group = proof.nonce / NONCES_PER_AES; @@ -141,14 +154,14 @@ impl Verifier { .try_into() .map_err(|_| Error::NonceGroupOutOfBounds(nonce_group))?, &challenge[..8].try_into().unwrap(), - ¶ms.pow_difficulty, + &pow_difficulty, &metadata.node_id, )?; // Verify the number of indices against K2 - let num_lables = metadata.num_units as u64 * metadata.labels_per_unit; - let bits_per_index = required_bits(num_lables); - let expected = expected_indices_bytes(bits_per_index, params.k2); + let num_labels = metadata.num_units as u64 * init_cfg.labels_per_unit; + let bits_per_index = required_bits(num_labels); + let expected = expected_indices_bytes(bits_per_index, cfg.k2); if proof.indices.len() != expected { return Err(Error::InvalidIndicesLen { expected, @@ -157,12 +170,14 @@ impl Verifier { } let indices_unpacked = decompress_indexes(&proof.indices, bits_per_index) - .take(params.k2 as usize) + .take(cfg.k2 as usize) .collect_vec(); let commitment = calc_commitment(&metadata.node_id, &metadata.commitment_atx_id); let cipher = AesCipher::new(&challenge, nonce_group, proof.pow); let lazy_cipher = AesCipher::new_lazy(&challenge, proof.nonce, nonce_group, proof.pow); - let (difficulty_msb, difficulty_lsb) = Prover8_56::split_difficulty(params.difficulty); + + let difficulty = proving_difficulty(cfg.k1, num_labels).map_err(Error::InvalidNumLabels)?; + let (difficulty_msb, difficulty_lsb) = Prover8_56::split_difficulty(difficulty); let output_index = (proof.nonce % NONCES_PER_AES) as usize; @@ -174,11 +189,11 @@ impl Verifier { &proof.pow.to_le_bytes(), ]; - let k3_indices = RandomValuesIterator::new(indices_unpacked, seed).take(params.k3 as usize); + let k3_indices = RandomValuesIterator::new(indices_unpacked, seed).take(cfg.k3 as usize); k3_indices.into_iter().try_for_each(|index| { let mut output = [0u8; 16]; - let label = generate_label(&commitment, params.scrypt, index); + let label = generate_label(&commitment, init_cfg.scrypt, index); cipher .aes .encrypt_block_b2b(&label.into(), (&mut output).into()); @@ -239,14 +254,14 @@ mod tests { use std::borrow::Cow; use crate::{ - config::{Config, ScryptParams}, + config::{InitConfig, ProofConfig, ScryptParams}, metadata::ProofMetadata, pow::MockPowVerifier, prove::Proof, verification::Error, }; - use super::{expected_indices_bytes, next_multiple_of, Verifier, VerifyingParams}; + use super::{expected_indices_bytes, next_multiple_of, Verifier}; #[test] fn test_next_mutliple_of() { @@ -263,11 +278,16 @@ mod tests { #[test] fn reject_invalid_pow() { - let params = VerifyingParams { - difficulty: u64::MAX, + let cfg = ProofConfig { + k1: 3, k2: 3, k3: 3, pow_difficulty: [0xFF; 32], + }; + let init_cfg = InitConfig { + min_num_units: 1, + max_num_units: 10, + labels_per_unit: 2048, scrypt: ScryptParams::new(2, 1, 1), }; @@ -290,18 +310,24 @@ mod tests { pow: 0, }, &fake_metadata, - params, + &cfg, + &init_cfg, ); assert!(matches!(result, Err(Error::InvalidPoW(_)))); } #[test] fn reject_invalid_proof() { - let params = VerifyingParams { - difficulty: u64::MAX, + let pcfg = ProofConfig { + k1: 10, k2: 10, k3: 10, pow_difficulty: [0xFF; 32], + }; + let icfg = InitConfig { + min_num_units: 1, + max_num_units: 10, + labels_per_unit: 2048, scrypt: ScryptParams::new(4, 1, 1), }; @@ -323,7 +349,7 @@ mod tests { indices: Cow::from(vec![]), pow: 0, }; - let result = verifier.verify(&empty_proof, &fake_metadata, params); + let result = verifier.verify(&empty_proof, &fake_metadata, &pcfg, &icfg); assert!(matches!( result, Err(Error::InvalidIndicesLen { @@ -338,7 +364,7 @@ mod tests { indices: Cow::from(vec![]), pow: 0, }; - let res = verifier.verify(&nonce_out_of_bounds_proof, &fake_metadata, params); + let res = verifier.verify(&nonce_out_of_bounds_proof, &fake_metadata, &pcfg, &icfg); assert!(matches!(res, Err(Error::NonceGroupOutOfBounds(256)))); } { @@ -347,7 +373,8 @@ mod tests { indices: Cow::from(vec![1, 2, 3]), pow: 0, }; - let result = verifier.verify(&proof_with_not_enough_indices, &fake_metadata, params); + let result = + verifier.verify(&proof_with_not_enough_indices, &fake_metadata, &pcfg, &icfg); assert!(matches!( result, Err(Error::InvalidIndicesLen { @@ -358,50 +385,42 @@ mod tests { } } - /// Test that PoW threshold is scaled with num_units. #[test] - fn scaling_pow_thresholds() { - let cfg = Config { - k1: 0, - k2: 0, - k3: 0, - pow_difficulty: [0xFF; 32], - scrypt: ScryptParams::new(2, 1, 1), - }; - let metadata = ProofMetadata { - node_id: [0u8; 32], - commitment_atx_id: [0u8; 32], - challenge: [0u8; 32], + fn verify_metadata() { + let valid_meta = ProofMetadata { + node_id: [0; 32], + commitment_atx_id: [0; 32], + challenge: [0; 32], num_units: 1, labels_per_unit: 100, }; + let init_cfg = InitConfig { + min_num_units: 1, + max_num_units: 10, + labels_per_unit: 100, + scrypt: ScryptParams::new(2, 1, 1), + }; + assert!(super::verify_metadata(&valid_meta, &init_cfg).is_ok()); { - // reject zero num_units - let params = VerifyingParams::new( - &ProofMetadata { - num_units: 0, - ..metadata - }, - &cfg, - ); - assert!(params.is_err()); + let num_units_small = ProofMetadata { + num_units: 0, + ..valid_meta + }; + assert!(super::verify_metadata(&num_units_small, &init_cfg).is_err()); } { - // don't scale when num_units is 1 - let params = VerifyingParams::new(&metadata, &cfg).unwrap(); - assert_eq!(params.pow_difficulty, cfg.pow_difficulty); + let num_units_large = ProofMetadata { + num_units: 99, + ..valid_meta + }; + assert!(super::verify_metadata(&num_units_large, &init_cfg).is_err()); } { - // scale with num_units - let params = VerifyingParams::new( - &ProofMetadata { - num_units: 10, - ..metadata - }, - &cfg, - ) - .unwrap(); - assert!(params.pow_difficulty < cfg.pow_difficulty); + let invalid_labels_per_unit = ProofMetadata { + labels_per_unit: 99, + ..valid_meta + }; + assert!(super::verify_metadata(&invalid_labels_per_unit, &init_cfg).is_err()); } } } diff --git a/tests/generate_and_verify.rs b/tests/generate_and_verify.rs index ffd7fe0a..425863e5 100644 --- a/tests/generate_and_verify.rs +++ b/tests/generate_and_verify.rs @@ -1,12 +1,12 @@ use std::sync::atomic::AtomicBool; use post::{ - config::ScryptParams, + config::{InitConfig, ScryptParams}, initialize::{CpuInitializer, Initialize}, metadata::ProofMetadata, pow::randomx::{PoW, RandomXFlag}, prove::generate_proof, - verification::{Verifier, VerifyingParams}, + verification::Verifier, }; use tempfile::tempdir; @@ -14,25 +14,29 @@ use tempfile::tempdir; fn test_generate_and_verify() { // Initialize some data let challenge = b"hello world, challenge me!!!!!!!"; - let labels_per_unit = 256 * 16; let datadir = tempdir().unwrap(); - let cfg = post::config::Config { + let cfg = post::config::ProofConfig { k1: 23, k2: 32, k3: 10, pow_difficulty: [0xFF; 32], + }; + let init_cfg = InitConfig { + min_num_units: 1, + max_num_units: 1000, + labels_per_unit: 256 * 16, scrypt: ScryptParams::new(2, 1, 1), }; - let metadata = CpuInitializer::new(cfg.scrypt) + let metadata = CpuInitializer::new(init_cfg.scrypt) .initialize( datadir.path(), &[77; 32], &[0u8; 32], - labels_per_unit, + init_cfg.labels_per_unit, 31, - labels_per_unit, + 1000, None, ) .unwrap(); @@ -46,22 +50,14 @@ fn test_generate_and_verify() { let metadata = ProofMetadata::new(metadata, *challenge); let verifier = Verifier::new(Box::new(PoW::new(pow_flags).unwrap())); verifier - .verify( - &proof, - &metadata, - VerifyingParams::new(&metadata, &cfg).unwrap(), - ) + .verify(&proof, &metadata, &cfg, &init_cfg) .expect("proof should be valid"); // Check that the proof is invalid if we modify one index let mut invalid_proof = proof; invalid_proof.pow -= 1; verifier - .verify( - &invalid_proof, - &metadata, - VerifyingParams::new(&metadata, &cfg).unwrap(), - ) + .verify(&invalid_proof, &metadata, &cfg, &init_cfg) .expect_err("proof should be invalid"); } @@ -71,25 +67,29 @@ fn test_generate_and_verify() { fn test_generate_and_verify_difficulty_msb_not_zero() { // Initialize some data let challenge = b"hello world, challenge me!!!!!!!"; - let labels_per_unit = 200; let datadir = tempdir().unwrap(); - let cfg = post::config::Config { + let cfg = post::config::ProofConfig { k1: 20, k2: 30, k3: 30, pow_difficulty: [0xFF; 32], + }; + let init_cfg = InitConfig { + min_num_units: 1, + max_num_units: 1000, + labels_per_unit: 200, scrypt: ScryptParams::new(2, 1, 1), }; - let metadata = CpuInitializer::new(cfg.scrypt) + let metadata = CpuInitializer::new(init_cfg.scrypt) .initialize( datadir.path(), &[0u8; 32], &[0u8; 32], - labels_per_unit, + init_cfg.labels_per_unit, 2, - labels_per_unit, + init_cfg.labels_per_unit, None, ) .unwrap(); @@ -100,30 +100,16 @@ fn test_generate_and_verify_difficulty_msb_not_zero() { let proof = generate_proof(datadir.path(), challenge, cfg, 32, 1, pow_flags, stop).unwrap(); // Verify the proof - let metadata = ProofMetadata { - node_id: metadata.node_id, - commitment_atx_id: metadata.commitment_atx_id, - challenge: *challenge, - num_units: metadata.num_units, - labels_per_unit: metadata.labels_per_unit, - }; + let metadata = ProofMetadata::new(metadata, *challenge); let verifier = Verifier::new(Box::new(PoW::new(pow_flags).unwrap())); verifier - .verify( - &proof, - &metadata, - VerifyingParams::new(&metadata, &cfg).unwrap(), - ) + .verify(&proof, &metadata, &cfg, &init_cfg) .expect("proof should be valid"); // Check that the proof is invalid if we modify one index let mut invalid_proof = proof; invalid_proof.indices.to_mut()[0] += 1; verifier - .verify( - &invalid_proof, - &metadata, - VerifyingParams::new(&metadata, &cfg).unwrap(), - ) + .verify(&invalid_proof, &metadata, &cfg, &init_cfg) .expect_err("proof should be invalid"); } diff --git a/tests/initialize_and_verify.rs b/tests/initialize_and_verify.rs index 9d0a5de9..3103543c 100644 --- a/tests/initialize_and_verify.rs +++ b/tests/initialize_and_verify.rs @@ -12,23 +12,16 @@ use tempfile::tempdir; fn test_generate_and_verify() { // Initialize some data let datadir = tempdir().unwrap(); + let scrypt = ScryptParams::new(2, 1, 1); - let cfg = post::config::Config { - k1: 23, - k2: 32, - k3: 10, - pow_difficulty: [0xFF; 32], - scrypt: ScryptParams::new(2, 1, 1), - }; - - CpuInitializer::new(cfg.scrypt) + CpuInitializer::new(scrypt) .initialize(datadir.path(), &[0u8; 32], &[0u8; 32], 256, 31, 700, None) .unwrap(); // Verify the data - verify_files(datadir.path(), 100.0, None, None, cfg.scrypt).unwrap(); - verify_files(datadir.path(), 1.0, None, None, cfg.scrypt).unwrap(); - verify_files(datadir.path(), 1.0, Some(0), Some(1), cfg.scrypt).unwrap(); + verify_files(datadir.path(), 100.0, None, None, scrypt).unwrap(); + verify_files(datadir.path(), 1.0, None, None, scrypt).unwrap(); + verify_files(datadir.path(), 1.0, Some(0), Some(1), scrypt).unwrap(); // Try verification with wrong scrypt params let wrong_scrypt = ScryptParams::new(4, 1, 1); @@ -44,12 +37,12 @@ fn test_generate_and_verify() { file.write_all(&[0u8; 16]).unwrap(); - assert!(verify_files(datadir.path(), 100.0, None, None, cfg.scrypt).is_err()); - assert!(verify_files(datadir.path(), 100.0, Some(1), Some(1), cfg.scrypt).is_err()); - assert!(verify_files(datadir.path(), 100.0, None, Some(1), cfg.scrypt).is_err()); - assert!(verify_files(datadir.path(), 100.0, Some(1), None, cfg.scrypt).is_err()); + assert!(verify_files(datadir.path(), 100.0, None, None, scrypt).is_err()); + assert!(verify_files(datadir.path(), 100.0, Some(1), Some(1), scrypt).is_err()); + assert!(verify_files(datadir.path(), 100.0, None, Some(1), scrypt).is_err()); + assert!(verify_files(datadir.path(), 100.0, Some(1), None, scrypt).is_err()); // skip corrupted files - pass - verify_files(datadir.path(), 100.0, None, Some(0), cfg.scrypt).unwrap(); - verify_files(datadir.path(), 100.0, Some(2), None, cfg.scrypt).unwrap(); + verify_files(datadir.path(), 100.0, None, Some(0), scrypt).unwrap(); + verify_files(datadir.path(), 100.0, Some(2), None, scrypt).unwrap(); }