diff --git a/.gitignore b/.gitignore index 196e176..30fcd1c 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ Cargo.lock # Added by cargo /target + +# Ignore macos specific files +.DS_Store \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index c548205..d5e95ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,14 @@ resolver = "2" [workspace.package] version = "0.1.1" edition = "2021" +authors = ["@AbdelStark"] +homepage = "https://github.com/AbdelStark/askeladd" +repository = "https://github.com/AbdelStark/askeladd.git" +license = "MIT" + [workspace.dependencies] stwo-prover = { git = "https://github.com/starkware-libs/stwo.git" } +tokio = { version = "1", default-features = false } +tracing = { version = "0.1", default-features = false } +tracing-subscriber = "0.3" diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 5fd7f4e..42c2923 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -7,3 +7,20 @@ edition.workspace = true [dependencies] askeladd-core = { path = "../core" } +nostr-sdk = "0.32.0" +tokio = { version = "1", default-features = false } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +uuid = { version = "1.3", features = ["v4"] } + +[[bin]] +name = "user_cli" +path = "src/user_cli.rs" + +[[bin]] +name = "prover_agent" +path = "src/prover_agent.rs" + +[dev-dependencies] +tokio = { workspace = true, features = ["macros"] } +tracing-subscriber = { workspace = true, features = ["env-filter"] } diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs deleted file mode 100644 index e710519..0000000 --- a/crates/cli/src/main.rs +++ /dev/null @@ -1,19 +0,0 @@ -use askeladd_core::prover_service::ProverService; -use askeladd_core::types::FibonnacciProvingRequest; -use askeladd_core::verifier_service::VerifierService; - -fn main() { - let proving_service: ProverService = Default::default(); - let log_size = 5; - let claim = 443693538; - let request = FibonnacciProvingRequest { log_size, claim }; - println!("Generating proof..."); - let response = proving_service.generate_proof(request).unwrap(); - println!("Proof successfully generated."); - let verifier_service: VerifierService = Default::default(); - println!("Verifying proof..."); - match verifier_service.verify_proof(response) { - Ok(_) => println!("Proof successfully verified"), - Err(e) => println!("Proof verification failed: {}", e), - } -} diff --git a/crates/cli/src/prover_agent.rs b/crates/cli/src/prover_agent.rs new file mode 100644 index 0000000..e46f867 --- /dev/null +++ b/crates/cli/src/prover_agent.rs @@ -0,0 +1,69 @@ +use askeladd_core::prover_service::ProverService; +use askeladd_core::types::FibonnacciProvingRequest; +use nostr_sdk::prelude::*; + +const SUBSCRIBED_RELAYS: &[&str] = &[ + "wss://nostr.oxtr.dev", + "wss://relay.damus.io", + "wss://nostr.openchain.fr", +]; +const PROVING_REQ_SUB_ID: &str = "askeladd_proving_request"; + +#[tokio::main] +async fn main() -> Result<()> { + let opts = Options::new().wait_for_send(false); + let client = Client::builder().opts(opts).build(); + + for relay in SUBSCRIBED_RELAYS { + client.add_relay(Url::parse(relay).unwrap()).await?; + } + + client.connect().await; + + let proving_req_sub_id = SubscriptionId::new(PROVING_REQ_SUB_ID); + let filter = Filter::new().kind(Kind::TextNote); + + client + .subscribe_with_id(proving_req_sub_id.clone(), vec![filter], None) + .await; + + let proving_service: ProverService = Default::default(); + + client + .handle_notifications(|notification| async { + if let RelayPoolNotification::Event { + subscription_id, + event, + .. + } = notification + { + if subscription_id == proving_req_sub_id { + println!("Proving request received: {:?}", event); + + // Deserialize the request + if let Ok(request) = + serde_json::from_str::(&event.content) + { + // Generate the proof + match proving_service.generate_proof(request) { + Ok(response) => { + // Serialize the response to JSON + let response_json = serde_json::to_string(&response)?; + + // Publish the proving response + let tags = vec![]; + let event_id = + client.publish_text_note(response_json, tags).await?; + println!("Proving response published with event ID: {}", event_id); + } + Err(e) => println!("Proof generation failed: {}", e), + } + } + } + } + Ok(false) + }) + .await?; + + Ok(()) +} diff --git a/crates/cli/src/user_cli.rs b/crates/cli/src/user_cli.rs new file mode 100644 index 0000000..1dad098 --- /dev/null +++ b/crates/cli/src/user_cli.rs @@ -0,0 +1,82 @@ +use askeladd_core::types::{FibonnacciProvingRequest, FibonnacciProvingResponse}; +use askeladd_core::verifier_service::VerifierService; +use nostr_sdk::prelude::*; +use uuid::Uuid; + +const SUBSCRIBED_RELAYS: &[&str] = &[ + "wss://nostr.oxtr.dev", + "wss://relay.damus.io", + "wss://nostr.openchain.fr", +]; +const PROVING_RESP_SUB_ID: &str = "askeladd_proving_response"; + +#[tokio::main] +async fn main() -> Result<()> { + let opts = Options::new().wait_for_send(false); + let client = Client::builder().opts(opts).build(); + + for relay in SUBSCRIBED_RELAYS { + client.add_relay(Url::parse(relay).unwrap()).await?; + } + + client.connect().await; + + // Generate a unique request ID + let request_id = Uuid::new_v4().to_string(); + + // Create a proving request + let proving_request = FibonnacciProvingRequest { + request_id: request_id.clone(), + log_size: 5, + claim: 443693538, + }; + + // Serialize the request to JSON + let request_json = serde_json::to_string(&proving_request)?; + + // Publish the proving request + let event_id = client.publish_text_note(request_json, []).await?; + + println!("Proving request published with event ID: {}", event_id); + + // Subscribe to proving responses + let proving_resp_sub_id = SubscriptionId::new(PROVING_RESP_SUB_ID); + let filter = Filter::new().kind(Kind::TextNote).since(Timestamp::now()); + + client + .subscribe_with_id(proving_resp_sub_id.clone(), vec![filter], None) + .await; + + // Handle subscription notifications + client + .handle_notifications(|notification| async { + if let RelayPoolNotification::Event { + subscription_id, + event, + .. + } = notification + { + if subscription_id == proving_resp_sub_id { + println!("Proving response received: {:?}", event); + + // Deserialize the response + if let Ok(response) = + serde_json::from_str::(&event.content) + { + // Verify the proof + let verifier_service: VerifierService = Default::default(); + match verifier_service.verify_proof(response) { + Ok(_) => println!("Proof successfully verified"), + Err(e) => println!("Proof verification failed: {}", e), + } + return Ok(true); // Stop listening after receiving and verifying the + // response + } + } + } + Ok(false) + }) + .await?; + + Ok(()) +} diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 20ae18c..1363d53 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -5,3 +5,6 @@ edition.workspace = true [dependencies] stwo-prover.workspace = true +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +thiserror = "1.0.62" diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 4185887..634e0fb 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -1,3 +1,4 @@ pub mod prover_service; pub mod types; pub mod verifier_service; +pub mod wrappers; diff --git a/crates/core/src/prover_service.rs b/crates/core/src/prover_service.rs index d7d5baa..650c5a3 100644 --- a/crates/core/src/prover_service.rs +++ b/crates/core/src/prover_service.rs @@ -14,11 +14,12 @@ impl ProverService { ) -> Result { let fib = Fibonacci::new(request.log_size, BaseField::from(request.claim)); match fib.prove() { - Ok(proof) => Ok(FibonnacciProvingResponse { + Ok(proof) => Ok(FibonnacciProvingResponse::new( + request.request_id, + request.log_size, + request.claim, proof, - log_size: request.log_size, - claim: request.claim, - }), + )), Err(e) => Err(e), } } diff --git a/crates/core/src/types.rs b/crates/core/src/types.rs index a8345da..d9ca92a 100644 --- a/crates/core/src/types.rs +++ b/crates/core/src/types.rs @@ -1,18 +1,35 @@ +use serde::{Deserialize, Serialize}; use stwo_prover::core::prover::StarkProof; -/// A request to generate a proof for a Fibonnacci sequence. +use crate::wrappers::{ConversionError, StarkProofWrapper}; + +#[derive(Debug, Serialize, Deserialize)] pub struct FibonnacciProvingRequest { - /// The size of the log to generate. + pub request_id: String, pub log_size: u32, - /// The claim to be proved. pub claim: u32, } +#[derive(Debug, Serialize, Deserialize)] pub struct FibonnacciProvingResponse { - /// The size of the log to generate. + pub request_id: String, pub log_size: u32, - /// The claim to be proved. pub claim: u32, - /// The proof generated for the request. - pub proof: StarkProof, + pub proof: StarkProofWrapper, +} + +impl FibonnacciProvingResponse { + pub fn new(request_id: String, log_size: u32, claim: u32, proof: StarkProof) -> Self { + Self { + request_id, + log_size, + claim, + proof: proof.into(), + } + } + + pub fn into_stark_proof(self) -> Result<(String, u32, u32, StarkProof), ConversionError> { + let proof = self.proof.try_into().unwrap(); + Ok((self.request_id, self.log_size, self.claim, proof)) + } } diff --git a/crates/core/src/verifier_service.rs b/crates/core/src/verifier_service.rs index 7b4e007..44f3032 100644 --- a/crates/core/src/verifier_service.rs +++ b/crates/core/src/verifier_service.rs @@ -3,16 +3,23 @@ use stwo_prover::core::prover::VerificationError; use stwo_prover::examples::fibonacci::Fibonacci; use crate::types::FibonnacciProvingResponse; +use crate::wrappers::ConversionError; + +#[derive(Debug, thiserror::Error)] +pub enum VerifierError { + #[error(transparent)] + Verification(#[from] VerificationError), + #[error(transparent)] + Conversion(#[from] ConversionError), +} #[derive(Debug, Default)] pub struct VerifierService {} impl VerifierService { - pub fn verify_proof( - &self, - response: FibonnacciProvingResponse, - ) -> Result<(), VerificationError> { - let fib = Fibonacci::new(response.log_size, BaseField::from(response.claim)); - fib.verify(response.proof) + pub fn verify_proof(&self, response: FibonnacciProvingResponse) -> Result<(), VerifierError> { + let (_, log_size, claim, proof) = response.into_stark_proof()?; + let fib = Fibonacci::new(log_size, BaseField::from(claim)); + fib.verify(proof).map_err(VerifierError::Verification) } } diff --git a/crates/core/src/wrappers.rs b/crates/core/src/wrappers.rs new file mode 100644 index 0000000..2289786 --- /dev/null +++ b/crates/core/src/wrappers.rs @@ -0,0 +1,50 @@ +use std::mem; + +use serde::{Deserialize, Serialize}; +use stwo_prover::core::prover::StarkProof; + +#[derive(Debug, Serialize, Deserialize)] +pub struct StarkProofWrapper(Vec); + +/// Warning: This is a hack to get around the fact that StarkProof is not serializable. +/// TODO: Remove this when StarkProof is serializable. +impl From for StarkProofWrapper { + fn from(proof: StarkProof) -> Self { + let bytes = unsafe { + let ptr = &proof as *const StarkProof as *const u8; + std::slice::from_raw_parts(ptr, mem::size_of::()) + }; + StarkProofWrapper(bytes.to_vec()) + } +} + +/// Warning: This is a hack to get around the fact that StarkProof is not serializable. +/// TODO: Remove this when StarkProof is serializable. +impl TryFrom for StarkProof { + type Error = &'static str; + + fn try_from(wrapper: StarkProofWrapper) -> Result { + if wrapper.0.len() != mem::size_of::() { + return Err("Invalid byte length for StarkProof"); + } + + let proof = unsafe { + let ptr = wrapper.0.as_ptr() as *const StarkProof; + ptr.read() + }; + + Ok(proof) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum ConversionError { + #[error("Invalid hash")] + InvalidHash, + #[error("Invalid lookup value")] + InvalidLookupValue, + #[error("Invalid sampled value")] + InvalidSampledValue, + #[error("Invalid Merkle proof")] + InvalidMerkleProof, +}