Skip to content

Commit

Permalink
job signature
Browse files Browse the repository at this point in the history
  • Loading branch information
Okm165 committed Apr 23, 2024
1 parent 3d70a0d commit f484091
Show file tree
Hide file tree
Showing 11 changed files with 154 additions and 70 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
7 changes: 5 additions & 2 deletions crates/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
138 changes: 97 additions & 41 deletions crates/common/src/job.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand All @@ -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<u8>, // The task bytecode in compressed zip format, to conserve memory
pub registry_address: Vec<u8>, // The address of the registry contract where the delegator expects the proof to be verified
pub public_key: Vec<u8>, // The public key of the delegator, used in the bootloader stage to confirm authenticity of the Job<->Delegator relationship
pub signature: Vec<u8>, // 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<FieldElement> = 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<FieldElement> = 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<u8>,
pub registry_address: L1Address,
}

impl JobData {
pub fn new(
reward: u32,
num_of_steps: u32,
cairo_pie_compressed: Vec<u8>,
registry_address: L1Address,
secret_key: SecretKey,
) -> Self {
Self { reward, num_of_steps, cairo_pie_compressed, registry_address }
}
}

impl TryFrom<JobData> for Vec<FieldElement> {
type Error = FromByteSliceError;
fn try_from(value: JobData) -> Result<Self, Self::Error> {
let mut felts: Vec<FieldElement> =
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<H: Hasher>(&self, state: &mut H) {
self.reward.hash(state);
self.num_of_steps.hash(state);
Expand All @@ -84,8 +99,49 @@ impl Hash for Job {
}
}

impl Hash for Job {
fn hash<H: Hasher>(&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::<u32>(),
num_of_steps in any::<u32>(),
cairo_pie_compressed in any::<Vec<u8>>(),
secret_key in any::<[u8; 32]>()
) -> (u32, u32, Vec<u8>, [u8; 32]) {
(reward, num_of_steps, cairo_pie_compressed, secret_key)
}
}

impl Arbitrary for Job {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
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()
}
}
11 changes: 7 additions & 4 deletions crates/common/src/tests/job.rs
Original file line number Diff line number Diff line change
@@ -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::<Job>()) {
assert!(job.verify_signature());
}
}
2 changes: 2 additions & 0 deletions crates/compiler/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
28 changes: 17 additions & 11 deletions crates/compiler/src/cairo_compiler/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 }
}
}

Expand Down Expand Up @@ -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))
Expand Down
10 changes: 7 additions & 3 deletions crates/compiler/src/cairo_compiler/tests/single_job.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
use rand::thread_rng;

use crate::{
cairo_compiler::{tests::models::fixture, CairoCompiler},
traits::CompilerController,
};

#[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();
}
2 changes: 1 addition & 1 deletion crates/runner/src/cairo_runner/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
14 changes: 8 additions & 6 deletions crates/runner/src/cairo_runner/tests/models.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand All @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions crates/tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
7 changes: 5 additions & 2 deletions crates/tests/src/tests/compiler_runner_flow.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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
Expand All @@ -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();

Expand Down

0 comments on commit f484091

Please sign in to comment.