diff --git a/Cargo.toml b/Cargo.toml index cb949fb..7ece45c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,9 +47,12 @@ libp2p = { version = "0.53.2", features = [ ] } libsecp256k1 = "0.7.1" num-bigint = "0.4.4" +proptest = "1.4.0" +proptest-derive = "0.4.0" rand = "0.8.5" serde = "1.0.197" serde_json = "1.0.115" +serde_with = "3.7.0" starknet = "0.10.0" starknet-crypto = "0.6.2" strum = { version = "0.26", features = ["derive"] } diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 6514dc0..3f78034 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -15,11 +15,14 @@ hex.workspace = true libp2p.workspace = true libsecp256k1.workspace = true num-bigint.workspace = true +proptest-derive.workspace = true +proptest.workspace = true serde_json.workspace = true +serde_with.workspace = true serde.workspace = true -starknet.workspace = true starknet-crypto.workspace = true +starknet.workspace = true strum.workspace = true tempfile.workspace = true thiserror.workspace = true -tokio.workspace = true +tokio.workspace = true \ No newline at end of file diff --git a/crates/common/src/job.rs b/crates/common/src/job.rs index 9f877c8..fb59d0c 100644 --- a/crates/common/src/job.rs +++ b/crates/common/src/job.rs @@ -1,6 +1,12 @@ use crate::hash; -use libsecp256k1::{curve::Scalar, sign, Message, PublicKey, SecretKey}; +use libsecp256k1::{Message, PublicKey, SecretKey, Signature}; +use proptest::arbitrary::any; +use proptest::prop_compose; +use proptest::strategy::BoxedStrategy; +use proptest::{arbitrary::Arbitrary, strategy::Strategy}; use serde::Serialize; +use serde_with::serde_as; +use starknet::core::types::FromByteSliceError; use starknet::providers::sequencer::models::L1Address; use starknet_crypto::{poseidon_hash_many, FieldElement}; use std::{ @@ -17,65 +23,74 @@ use std::{ The Job object also includes the target registry where the delegator expects this proof to be verified. */ +#[serde_as] #[derive(Debug, PartialEq, Eq, Clone, Serialize)] pub struct Job { - pub reward: u32, // The reward offered for completing the task - pub num_of_steps: u32, // The number of steps expected to complete the task (executor ensures that this number is greater than or equal to the actual steps; in the future, the executor may charge a fee to the delegator if not met) - pub cairo_pie_compressed: Vec, // The task bytecode in compressed zip format, to conserve memory - pub registry_address: Vec, // The address of the registry contract where the delegator expects the proof to be verified - pub public_key: Vec, // The public key of the delegator, used in the bootloader stage to confirm authenticity of the Job<->Delegator relationship - pub signature: Vec, // The signature of the delegator, used in the bootloader stage to confirm authenticity of the Job<->Delegator relationship + pub job_data: JobData, + #[serde_as(as = "[_; 65]")] + pub public_key: [u8; 65], // The public key of the delegator, used in the bootloader stage to confirm authenticity of the Job<->Delegator relationship + #[serde_as(as = "[_; 64]")] + pub signature: [u8; 64], // The signature of the delegator, used in the bootloader stage to confirm authenticity of the Job<->Delegator relationship } impl Job { + pub fn from_job_data(job_data: JobData, secret_key: SecretKey) -> Self { + let felts: Vec = job_data.to_owned().try_into().unwrap(); + let message = Message::parse(&poseidon_hash_many(&felts).to_bytes_be()); + let (signature, _recovery) = libsecp256k1::sign(&message, &secret_key); + + Self { + job_data, + public_key: PublicKey::from_secret_key(&secret_key).serialize(), + signature: signature.serialize(), + } + } + + pub fn verify_signature(&self) -> bool { + let felts: Vec = self.job_data.to_owned().try_into().unwrap(); + let message = Message::parse(&poseidon_hash_many(&felts).to_bytes_be()); + let signature = Signature::parse_overflowing(&self.signature); + let pubkey = PublicKey::parse(&self.public_key).unwrap(); + libsecp256k1::verify(&message, &signature, &pubkey) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct JobData { + pub reward: u32, + pub num_of_steps: u32, + pub cairo_pie_compressed: Vec, + pub registry_address: L1Address, +} + +impl JobData { pub fn new( reward: u32, num_of_steps: u32, cairo_pie_compressed: Vec, registry_address: L1Address, - secret_key: SecretKey, ) -> Self { + Self { reward, num_of_steps, cairo_pie_compressed, registry_address } + } +} + +impl TryFrom for Vec { + type Error = FromByteSliceError; + fn try_from(value: JobData) -> Result { let mut felts: Vec = - vec![FieldElement::from(reward), FieldElement::from(num_of_steps)]; + vec![FieldElement::from(value.reward), FieldElement::from(value.num_of_steps)]; felts.extend( - cairo_pie_compressed + value + .cairo_pie_compressed .chunks(31) .map(|chunk| FieldElement::from_byte_slice_be(chunk).unwrap()), ); - felts.push(FieldElement::from_byte_slice_be(registry_address.as_bytes()).unwrap()); - - let message = Message::parse(&poseidon_hash_many(&felts).to_bytes_be()); - let (signature, _recovery) = libsecp256k1::sign(&message, &secret_key); - - Self { - reward, - num_of_steps, - cairo_pie_compressed, - registry_address: registry_address.to_fixed_bytes().to_vec(), - public_key: PublicKey::from_secret_key(&secret_key).serialize().to_vec(), - signature: signature.serialize().to_vec(), - } - } -} - -impl Default for Job { - fn default() -> Self { - let secret_key = &SecretKey::default(); - let public_key = PublicKey::from_secret_key(secret_key); - let (signature, _recovery_id) = - sign(&libsecp256k1::Message(Scalar([0, 0, 0, 0, 0, 0, 0, 0])), secret_key); - Self { - reward: 0, - num_of_steps: 0, - cairo_pie_compressed: vec![], - registry_address: L1Address::zero().to_fixed_bytes().to_vec(), - public_key: public_key.serialize().to_vec(), - signature: signature.serialize().to_vec(), - } + felts.push(FieldElement::from_byte_slice_be(&value.registry_address.to_fixed_bytes())?); + Ok(felts) } } -impl Hash for Job { +impl Hash for JobData { fn hash(&self, state: &mut H) { self.reward.hash(state); self.num_of_steps.hash(state); @@ -84,8 +99,49 @@ impl Hash for Job { } } +impl Hash for Job { + fn hash(&self, state: &mut H) { + self.job_data.hash(state) + } +} + impl Display for Job { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", hex::encode(hash!(self).to_be_bytes())) } } + +prop_compose! { + fn arb_state()( + reward in any::(), + num_of_steps in any::(), + cairo_pie_compressed in any::>(), + secret_key in any::<[u8; 32]>() + ) -> (u32, u32, Vec, [u8; 32]) { + (reward, num_of_steps, cairo_pie_compressed, secret_key) + } +} + +impl Arbitrary for Job { + type Parameters = (); + type Strategy = BoxedStrategy; + fn arbitrary() -> Self::Strategy { + let abs_state = arb_state(); + abs_state + .prop_map(|(reward, num_of_steps, cairo_pie_compressed, secret_key)| { + Job::from_job_data( + JobData { + reward, + num_of_steps, + cairo_pie_compressed, + registry_address: L1Address::random(), + }, + libsecp256k1::SecretKey::parse(&secret_key).unwrap(), + ) + }) + .boxed() + } + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + Self::arbitrary() + } +} diff --git a/crates/common/src/tests/job.rs b/crates/common/src/tests/job.rs index 1242de1..f42dfd3 100644 --- a/crates/common/src/tests/job.rs +++ b/crates/common/src/tests/job.rs @@ -1,7 +1,10 @@ use crate::job::Job; +use proptest::prelude::*; -#[test] -fn job_serialize() { - let random_job = Job::default(); - println!("{}", serde_json::to_string(&random_job).unwrap()); +proptest! { + #![proptest_config(ProptestConfig::with_cases(1000))] + #[test] + fn job_verify_signature(job in any::()) { + assert!(job.verify_signature()); + } } diff --git a/crates/compiler/Cargo.toml b/crates/compiler/Cargo.toml index 346dddd..eead4a4 100644 --- a/crates/compiler/Cargo.toml +++ b/crates/compiler/Cargo.toml @@ -18,6 +18,8 @@ rand.workspace = true serde_json.workspace = true serde.workspace = true sharp-p2p-common.workspace = true +starknet-crypto.workspace = true +starknet.workspace = true strum.workspace = true tempfile.workspace = true thiserror.workspace = true diff --git a/crates/compiler/src/cairo_compiler/mod.rs b/crates/compiler/src/cairo_compiler/mod.rs index 862254f..70aa6a3 100644 --- a/crates/compiler/src/cairo_compiler/mod.rs +++ b/crates/compiler/src/cairo_compiler/mod.rs @@ -1,8 +1,11 @@ use crate::{errors::CompilerControllerError, traits::CompilerController}; use async_process::Stdio; use futures::Future; +use libsecp256k1::SecretKey; +use sharp_p2p_common::job::JobData; use sharp_p2p_common::layout::Layout; use sharp_p2p_common::{job::Job, process::Process}; +use starknet::providers::sequencer::models::L1Address; use std::path::PathBuf; use std::{io::Read, pin::Pin}; use tempfile::NamedTempFile; @@ -11,17 +14,13 @@ use tracing::debug; pub mod tests; -pub struct CairoCompiler {} - -impl CairoCompiler { - pub fn new() -> Self { - Self {} - } +pub struct CairoCompiler { + signing_key: SecretKey, } -impl Default for CairoCompiler { - fn default() -> Self { - Self::new() +impl CairoCompiler { + pub fn new(signing_key: SecretKey) -> Self { + Self { signing_key } } } @@ -104,8 +103,15 @@ impl CompilerController for CairoCompiler { let mut cairo_pie_bytes = Vec::new(); cairo_pie.read_to_end(&mut cairo_pie_bytes)?; - // TODO: calculate details - Ok(Job { cairo_pie_compressed: cairo_pie_bytes, ..Default::default() }) + Ok(Job::from_job_data( + JobData { + reward: 0, // TODO: calculate this properly + num_of_steps: 0, // TODO: calculate this properly + cairo_pie_compressed: cairo_pie_bytes, + registry_address: L1Address::random(), + }, + self.signing_key, + )) }); Ok(Process::new(future, terminate_tx)) diff --git a/crates/compiler/src/cairo_compiler/tests/single_job.rs b/crates/compiler/src/cairo_compiler/tests/single_job.rs index 06a4c55..38b32f3 100644 --- a/crates/compiler/src/cairo_compiler/tests/single_job.rs +++ b/crates/compiler/src/cairo_compiler/tests/single_job.rs @@ -1,3 +1,5 @@ +use rand::thread_rng; + use crate::{ cairo_compiler::{tests::models::fixture, CairoCompiler}, traits::CompilerController, @@ -5,18 +7,20 @@ use crate::{ #[tokio::test] async fn run_single_job() { + let mut rng = thread_rng(); let fixture = fixture(); - let compiler = CairoCompiler::new(); + let compiler = CairoCompiler::new(libsecp256k1::SecretKey::random(&mut rng)); compiler.run(fixture.program_path, fixture.program_input_path).unwrap().await.unwrap(); } #[tokio::test] async fn abort_single_jobs() { + let mut rng = thread_rng(); let fixture = fixture(); - let runner = CairoCompiler::new(); - let job = runner.run(fixture.program_path, fixture.program_input_path).unwrap(); + let compiler = CairoCompiler::new(libsecp256k1::SecretKey::random(&mut rng)); + let job = compiler.run(fixture.program_path, fixture.program_input_path).unwrap(); job.abort().await.unwrap(); job.await.unwrap_err(); } diff --git a/crates/runner/src/cairo_runner/mod.rs b/crates/runner/src/cairo_runner/mod.rs index 934acf8..47767b3 100644 --- a/crates/runner/src/cairo_runner/mod.rs +++ b/crates/runner/src/cairo_runner/mod.rs @@ -36,7 +36,7 @@ impl RunnerController for CairoRunner { let layout: &str = Layout::RecursiveWithPoseidon.into(); let mut cairo_pie = NamedTempFile::new()?; - cairo_pie.write_all(&job.cairo_pie_compressed)?; + cairo_pie.write_all(&job.job_data.cairo_pie_compressed)?; let input = BootloaderInput { tasks: vec![BootloaderTask { diff --git a/crates/runner/src/cairo_runner/tests/models.rs b/crates/runner/src/cairo_runner/tests/models.rs index fb6f9e9..fbd3bca 100644 --- a/crates/runner/src/cairo_runner/tests/models.rs +++ b/crates/runner/src/cairo_runner/tests/models.rs @@ -1,5 +1,5 @@ use rand::{thread_rng, Rng}; -use sharp_p2p_common::job::Job; +use sharp_p2p_common::job::{Job, JobData}; use starknet::providers::sequencer::models::L1Address; use std::{env, fs, path::PathBuf}; @@ -17,11 +17,13 @@ pub fn fixture() -> TestFixture { let program_path = ws_root.join("target/bootloader.json"); TestFixture { - job: Job::new( - rng.gen(), - rng.gen(), - fs::read(cairo_pie_path).unwrap(), - L1Address::random(), + job: Job::from_job_data( + JobData::new( + rng.gen(), + rng.gen(), + fs::read(cairo_pie_path).unwrap(), + L1Address::random(), + ), libsecp256k1::SecretKey::random(&mut rng), ), program_path, diff --git a/crates/tests/Cargo.toml b/crates/tests/Cargo.toml index 06bb165..a78ae9f 100644 --- a/crates/tests/Cargo.toml +++ b/crates/tests/Cargo.toml @@ -13,6 +13,8 @@ sharp-p2p-prover.workspace = true sharp-p2p-runner.workspace = true sharp-p2p-compiler.workspace = true tokio.workspace = true +libsecp256k1.workspace = true +rand.workspace = true [features] full_test = [] diff --git a/crates/tests/src/tests/compiler_runner_flow.rs b/crates/tests/src/tests/compiler_runner_flow.rs index 2186768..9cb496f 100644 --- a/crates/tests/src/tests/compiler_runner_flow.rs +++ b/crates/tests/src/tests/compiler_runner_flow.rs @@ -1,5 +1,6 @@ use futures::stream::FuturesUnordered; use futures::{FutureExt, StreamExt}; +use rand::thread_rng; use sharp_p2p_compiler::cairo_compiler::tests::models::fixture as compiler_fixture; use sharp_p2p_compiler::cairo_compiler::CairoCompiler; use sharp_p2p_compiler::traits::CompilerController; @@ -9,10 +10,11 @@ use sharp_p2p_runner::traits::RunnerController; #[tokio::test] async fn run_single_job() { + let mut rng = thread_rng(); let compiler_fixture = compiler_fixture(); let runner_fixture = runner_fixture(); - let compiler = CairoCompiler::new(); + let compiler = CairoCompiler::new(libsecp256k1::SecretKey::random(&mut rng)); let runner = CairoRunner::new(runner_fixture.program_path); compiler @@ -29,11 +31,12 @@ async fn run_single_job() { #[tokio::test] async fn run_multiple_job() { + let mut rng = thread_rng(); let compiler_fixture1 = compiler_fixture(); let compiler_fixture2 = compiler_fixture(); let runner_fixture1 = runner_fixture(); - let compiler = CairoCompiler::new(); + let compiler = CairoCompiler::new(libsecp256k1::SecretKey::random(&mut rng)); let runner = CairoRunner::new(runner_fixture1.program_path); let mut futures = FuturesUnordered::new();