From 16c9e8e64fdf13f5967cc9811aeebb241a09f5a2 Mon Sep 17 00:00:00 2001 From: John Basrai Date: Thu, 26 Sep 2024 19:39:15 -0700 Subject: [PATCH] Reformatted with rustfmt - Converted from nightly build stable - Deleted some now unsupported settings in rustfmt.toml - Cleaned up some clippy warings and other code issues - Added documentation comments to the code --- Cargo.toml | 15 ++-- build.rs | 5 +- proto/zkp_auth.proto | 2 +- rust-toolchain.toml | 2 +- rustfmt.toml | 13 ---- src/client.rs | 65 +++++++--------- src/lib.rs | 166 ++++++++++++++++++++++++++++++----------- src/server.rs | 171 ++++++++++++++++++++++++++++--------------- 8 files changed, 275 insertions(+), 164 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b252418..1912e72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,17 +6,18 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -rand = "0.8" +rand = "0" num-bigint = { version = "0.4", features = ["rand"] } -hex = "0.4.3" -tonic = "0.9" -prost = "0.11" -tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } # async rust runtime +hex = "0" +tonic = "0" +prost = "0" +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } # async rust runtime clap = { version = "4", features = ["derive","cargo"] } anyhow = "1" +rpassword = "7" [build-dependencies] -tonic-build = "0.9" +tonic-build = "0" [[bin]] name = "server" @@ -24,4 +25,4 @@ path = "./src/server.rs" [[bin]] name = "client" -path = "./src/client.rs" \ No newline at end of file +path = "./src/client.rs" diff --git a/build.rs b/build.rs index 6f3b293..839ff85 100644 --- a/build.rs +++ b/build.rs @@ -1,9 +1,8 @@ -fn main() -{ +fn main() { tonic_build::configure() .build_server(true) .out_dir("src/") // you can change the generated code's location - .compile( + .compile_protos( &["proto/zkp_auth.proto"], &["proto/"], // specify the root location to search proto dependencies ) diff --git a/proto/zkp_auth.proto b/proto/zkp_auth.proto index e7a50bf..04711f2 100644 --- a/proto/zkp_auth.proto +++ b/proto/zkp_auth.proto @@ -48,4 +48,4 @@ service Auth { rpc Register(RegisterRequest) returns (RegisterResponse) {} rpc CreateAuthenticationChallenge(AuthenticationChallengeRequest) returns (AuthenticationChallengeResponse) {} rpc VerifyAuthentication(AuthenticationAnswerRequest) returns (AuthenticationAnswerResponse) {} -} \ No newline at end of file +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 271800c..31578d3 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "nightly" \ No newline at end of file +channel = "stable" \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml index 7953691..113d848 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -3,17 +3,4 @@ edition = "2021" array_width = 70 fn_params_layout = "Compressed" fn_call_width = 80 -fn_single_line = true -space_before_colon = false -spaces_around_ranges = true -unstable_features = true - -wrap_comments = true max_width = 100 -comment_width = 90 - -# These work only for nightly builds (of rust toolchain), as of rust v 1.71 -control_brace_style = "AlwaysNextLine" -brace_style = "AlwaysNextLine" -struct_field_align_threshold = 40 -enum_discrim_align_threshold = 40 diff --git a/src/client.rs b/src/client.rs index 393eaf1..f1747cc 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,9 +1,9 @@ -use anyhow::Result; +use anyhow::{anyhow, Result}; use clap::Parser; use num_bigint::BigUint; +use rpassword::prompt_password; -pub mod zkp_auth -{ +pub mod zkp_auth { include!("./zkp_auth.rs"); } @@ -16,16 +16,11 @@ use zkp_chaum_pedersen::ZKP; #[derive(Debug, Parser)] #[command(author, version, about, long_about = None)] /// Chaum-Pedersen Zero Knowledge Proof (client) -struct Args -{ +struct Args { /// User name #[arg(short, long, required = true)] user_name: String, - /// Password - #[arg(short, long, required = true)] - password: String, - /// Server endpoint #[arg( short, @@ -37,62 +32,56 @@ struct Args } #[tokio::main] -async fn main() -> Result<(), String> -{ - let args = &Args::parse(); +async fn main() -> Result<()> { + let args = Args::parse(); let (alpha, beta, p, q) = ZKP::get_constants(); let zkp = ZKP::new(&p, &q, &alpha, &beta); - let mut client = match AuthClient::connect(args.server.clone()).await - { + let mut client = match AuthClient::connect(args.server.clone()).await { Ok(x) => x, - Err(e) => - { - return Err(format!("Connect failed to {}: {:?}", args.server, e)); + Err(e) => { + return Err(anyhow!("Failed to register user on server {}: {:?}", args.server, e)); } }; println!("✅ Connected to the server"); - let password = BigUint::from_bytes_be(args.password.trim().as_bytes()); - + let password = prompt_password("Password: ")?; + let password = BigUint::from_bytes_be(password.trim().as_bytes()); let (y1, y2) = zkp.compute_pair(&password); let username = args.user_name.clone(); let request = RegisterRequest { user: username.clone(), - y1: y1.to_bytes_be(), - y2: y2.to_bytes_be(), + y1: y1.to_bytes_be(), + y2: y2.to_bytes_be(), }; - let _ = match client.register(request).await - { + let _ = match client.register(request).await { Ok(x) => x, - Err(e) => - { - return Err(format!("Could not register user name with server {:?}", e)); + Err(e) => { + return Err(anyhow!("Could not register user name with server {:?}", e)); } }; println!("✅ Registration was successful"); println!("Please provide the password (to login):"); - let password = BigUint::from_bytes_be(args.password.trim().as_bytes()); + let password = prompt_password("Password (to login): ")?; + let password = BigUint::from_bytes_be(password.trim().as_bytes()); let k = ZKP::generate_random_number_below(&q); let (r1, r2) = zkp.compute_pair(&k); let request = AuthenticationChallengeRequest { user: username, - r1: r1.to_bytes_be(), - r2: r2.to_bytes_be(), + r1: r1.to_bytes_be(), + r2: r2.to_bytes_be(), }; - let response = match client.create_authentication_challenge(request).await - { + let response = match client.create_authentication_challenge(request).await { Ok(x) => x, - Err(e) => - { - return Err(format!("Could not request challenge to server {:?}", e)); + Err(e) => { + return Err(anyhow!("Could not request challenge to server {:?}", e)); } } .into_inner(); @@ -106,12 +95,10 @@ async fn main() -> Result<(), String> s: s.to_bytes_be(), }; - let response = match client.verify_authentication(request).await - { + let response = match client.verify_authentication(request).await { Ok(x) => x, - Err(e) => - { - return Err(format!("Could not verify authentication in server {:?}", e)); + Err(e) => { + return Err(anyhow!("Could not verify authentication in server {:?}", e)); } } .into_inner(); diff --git a/src/lib.rs b/src/lib.rs index 81e240b..2e9bc8f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,50 +1,97 @@ use num_bigint::{BigUint, RandBigInt}; use rand::Rng; -pub struct ZKP -{ - pub p: BigUint, - pub q: BigUint, +/// A struct representing the Zero-Knowledge Proof (ZKP) parameters. +/// +/// This struct holds the parameters required for performing Zero-Knowledge Proofs, +/// including the prime numbers `p` and `q`, and the generators `alpha` and `beta`. +pub struct ZKP { + pub p: BigUint, + pub q: BigUint, pub alpha: BigUint, - pub beta: BigUint, + pub beta: BigUint, } -impl ZKP -{ - pub fn new(p: &BigUint, q: &BigUint, alpha: &BigUint, beta: &BigUint) -> Self - { +impl ZKP { + /// Creates a new `ZKP` instance with the specified parameters. + /// + /// # Arguments + /// + /// * `p` - A reference to a `BigUint` representing the prime number `p`. + /// * `q` - A reference to a `BigUint` representing the prime number `q`. + /// * `alpha` - A reference to a `BigUint` representing the generator `alpha`. + /// * `beta` - A reference to a `BigUint` representing the generator `beta`. + /// + /// # Returns + /// + /// A new `ZKP` instance initialized with the provided parameters. + pub fn new(p: &BigUint, q: &BigUint, alpha: &BigUint, beta: &BigUint) -> Self { Self { - p: p.clone(), - q: q.clone(), + p: p.clone(), + q: q.clone(), alpha: alpha.clone(), - beta: beta.clone(), + beta: beta.clone(), } } - /// output = (alpha^exp mod p, beta^exp mod p) - pub fn compute_pair(&self, exp: &BigUint) -> (BigUint, BigUint) - { + /// Computes a pair of values based on the ZKP parameters and an exponent. + /// + /// This method calculates the pair of outputs as `(alpha^exp mod p, beta^exp mod p)`. + /// + /// # Arguments + /// + /// * `exp` - A reference to a `BigUint` representing the exponent. + /// + /// # Returns + /// + /// A tuple containing the computed values as `BigUint`. + pub fn compute_pair(&self, exp: &BigUint) -> (BigUint, BigUint) { let p1 = self.alpha.modpow(exp, &self.p); let p2 = self.beta.modpow(exp, &self.p); (p1, p2) } - /// output = s = k - c * x mod q - pub fn solve(&self, k: &BigUint, c: &BigUint, x: &BigUint) -> BigUint - { - if *k >= c * x - { + /// Solves for the value `s` based on the provided parameters. + /// + /// The solution is computed using the formula `s = k - c * x mod q`. + /// + /// # Arguments + /// + /// * `k` - A reference to a `BigUint` representing `k`. + /// * `c` - A reference to a `BigUint` representing `c`. + /// * `x` - A reference to a `BigUint` representing `x`. + /// + /// # Returns + /// + /// A `BigUint` representing the computed value `s`. + pub fn solve(&self, k: &BigUint, c: &BigUint, x: &BigUint) -> BigUint { + if *k >= c * x { return (k - c * x).modpow(&BigUint::from(1u32), &self.q); } &self.q - (c * x - k).modpow(&BigUint::from(1u32), &self.q) } - /// cond1: r1 = alpha^s * y1^c - /// cond2: r2 = beta^s * y2^c + /// Verifies the conditions for the ZKP. + /// + /// The verification checks the conditions: + /// 1. `r1 = alpha^s * y1^c` + /// 2. `r2 = beta^s * y2^c` + /// + /// # Arguments + /// + /// * `r1` - A reference to a `BigUint` representing the first response. + /// * `r2` - A reference to a `BigUint` representing the second response. + /// * `y1` - A reference to a `BigUint` representing the first witness. + /// * `y2` - A reference to a `BigUint` representing the second witness. + /// * `c` - A reference to a `BigUint` representing the challenge value. + /// * `s` - A reference to a `BigUint` representing the response value. + /// + /// # Returns + /// + /// A boolean indicating whether the verification conditions are met. pub fn verify( &self, r1: &BigUint, r2: &BigUint, y1: &BigUint, y2: &BigUint, c: &BigUint, s: &BigUint, - ) -> bool - { + ) -> bool { let cond1 = *r1 == (&self.alpha.modpow(s, &self.p) * y1.modpow(c, &self.p)) .modpow(&BigUint::from(1u32), &self.p); @@ -56,15 +103,23 @@ impl ZKP cond1 && cond2 } - pub fn generate_random_number_below(bound: &BigUint) -> BigUint - { + /// Generates a random alphanumeric string of the specified size. + /// + /// This method uses a secure random number generator to produce a random string containing + /// alphanumeric characters. + /// + /// # Arguments + /// * `size` - The length of the string to be generated. + /// + /// # Returns + /// A `String` representing the generated random alphanumeric string. + pub fn generate_random_number_below(bound: &BigUint) -> BigUint { let mut rng = rand::thread_rng(); rng.gen_biguint_below(bound) } - pub fn generate_random_string(size: usize) -> String - { + pub fn generate_random_string(size: usize) -> String { rand::thread_rng() .sample_iter(rand::distributions::Alphanumeric) .take(size) @@ -72,6 +127,14 @@ impl ZKP .collect() } + /// Retrieves the ZKP constants used in the Zero-Knowledge Proof protocol. + /// + /// This method returns the constants `alpha`, `beta`, `p`, and `q` which are + /// fundamental to the ZKP computations. + /// + /// # Returns + /// + /// A tuple containing the constants `(alpha, beta, p, q)` as `BigUint`. #[rustfmt::skip] pub fn get_constants() -> (BigUint, BigUint, BigUint, BigUint) { @@ -97,13 +160,11 @@ impl ZKP } #[cfg(test)] -mod test -{ +mod test { use super::*; #[test] - fn test_toy_example() - { + fn test_toy_example() { let alpha = BigUint::from(4u32); let beta = BigUint::from(9u32); let p = BigUint::from(23u32); @@ -138,17 +199,16 @@ mod test } #[test] - fn test_toy_example_with_random_numbers() - { + fn test_toy_example_with_random_numbers() { let alpha = BigUint::from(4u32); let beta = BigUint::from(9u32); let p = BigUint::from(23u32); let q = BigUint::from(11u32); let zkp = ZKP { - p: p.clone(), - q: q.clone(), + p: p.clone(), + q: q.clone(), alpha: alpha.clone(), - beta: beta.clone(), + beta: beta.clone(), }; let x = BigUint::from(6u32); @@ -211,8 +271,7 @@ mod test } #[test] - fn test_2048_bits_constants() - { + fn test_2048_bits_constants() { // // Reference: https://www.rfc-editor.org/rfc/rfc5114#page-15 // @@ -263,10 +322,10 @@ mod test let beta = alpha.modpow(&ZKP::generate_random_number_below(&q), &p); let zkp = ZKP { - p: p.clone(), - q: q.clone(), + p: p.clone(), + q: q.clone(), alpha: alpha.clone(), - beta: beta.clone(), + beta: beta.clone(), }; let x = ZKP::generate_random_number_below(&q); @@ -282,4 +341,27 @@ mod test let result = zkp.verify(&r1, &r2, &y1, &y2, &c, &s); assert!(result); } + + #[test] + fn test_invalid_inputs() { + let alpha = BigUint::from(4u32); + let beta = BigUint::from(9u32); + let p = BigUint::from(23u32); + let q = BigUint::from(11u32); + let zkp = ZKP::new(&p, &q, &alpha, &beta); + + let x = BigUint::from(6u32); + let k = BigUint::from(7u32); + let c = BigUint::from(4u32); + + let (y1, y2) = zkp.compute_pair(&x); + let (r1, r2) = zkp.compute_pair(&k); + let s = zkp.solve(&k, &c, &x); + + // Verify with an invalid c value + let invalid_c = BigUint::from(999u32); // A value that's expected to fail verification + let result = zkp.verify(&r1, &r2, &y1, &y2, &invalid_c, &s); + assert!(!result); // Expect the verification to fail + } } + diff --git a/src/server.rs b/src/server.rs index 40d0944..5176b14 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use anyhow::{anyhow, Result}; use clap::Parser; use std::{collections::HashMap, sync::Mutex}; @@ -7,8 +7,7 @@ use tonic::{transport::Server, Code, Request, Response, Status}; use zkp_chaum_pedersen::ZKP; -pub mod zkp_auth -{ +pub mod zkp_auth { include!("./zkp_auth.rs"); } @@ -21,27 +20,23 @@ use zkp_auth::{ #[derive(Debug, Parser)] #[command(author, version, about, long_about = None)] /// Chaum-Pedersen Zero Knowledge Proof (server) -struct Args -{ +struct Args { /// Server listening endpoint #[arg(short, long, required = false, default_value = "127.0.0.1:50051")] endpoint: String, } #[tokio::main] -async fn main() -> Result<(), String> -{ - let args = &Args::parse(); +async fn main() -> Result<()> { + let args = Args::parse(); let addr = args.endpoint.clone(); println!("✅ Server listening at {}", addr); - let addr = match addr.parse() - { + let addr = match addr.parse() { Ok(a) => a, - Err(e) => - { - return Err(format!("could not convert address:{} error:{:?}", addr, e)); + Err(e) => { + return Err(anyhow!("could not convert address:{} error:{:?}", addr, e)); } }; @@ -51,45 +46,85 @@ async fn main() -> Result<(), String> .await { Ok(x) => x, - Err(e) => - { - return Err(format!("Server create failed endpoint:{} error:{:?}", args.endpoint, e)); + Err(e) => { + return Err(anyhow!("Server create failed endpoint:{} error:{:?}", args.endpoint, e)); } }; Ok(()) } +/// An implementation of the `Auth` trait for handling user registration, authentication challenge creation, +/// and verification in the Chaum-Pedersen Zero Knowledge Proof (ZKP) protocol. +/// +/// `AuthImpl` manages two key data structures that are protected by mutexes for concurrent access: +/// +/// - `user_info`: A `Mutex`-protected `HashMap` that stores user registration and authentication details, +/// with the username as the key and the associated `UserInfo` as the value. It holds the ZKP data for each user. +/// +/// - `auth_id_to_user`: A `Mutex`-protected `HashMap` that maps an authentication ID (generated during +/// challenge creation) to the corresponding username. This is used to match a challenge response with the correct user. +/// +/// This struct is the main server-side component responsible for processing requests for registration, +/// challenge creation, and authentication verification in the ZKP protocol. #[derive(Debug, Default)] -pub struct AuthImpl -{ - pub user_info: Mutex>, +pub struct AuthImpl { + pub user_info: Mutex>, pub auth_id_to_user: Mutex>, } +/// A struct representing the information needed for the authentication and verification process +/// using the Chaum-Pedersen Zero Knowledge Proof (ZKP) protocol. +/// +/// `UserInfo` contains data for registration, authorization, and verification steps, including: +/// +/// - **Registration**: +/// - `user_name`: The username of the user. +/// - `y1`, `y2`: The values used for registration, representing the user's computed public keys. +/// +/// - **Authorization**: +/// - `r1`, `r2`: The values sent by the user during the authentication challenge, computed as part of the ZKP protocol. +/// +/// - **Verification**: +/// - `c`: The challenge value generated by the server during the authentication process. +/// - `s`: The solution provided by the user to prove they know the correct password. +/// - `session_id`: A unique identifier for the authenticated session, generated after successful verification. #[derive(Debug, Default)] -pub struct UserInfo -{ +pub struct UserInfo { + // // registration - pub user_name: String, - pub y1: BigUint, - pub y2: BigUint, + pub user_name: String, + pub y1: BigUint, + pub y2: BigUint, + // // authorization - pub r1: BigUint, - pub r2: BigUint, + pub r1: BigUint, + pub r2: BigUint, + // // verification - pub c: BigUint, - pub s: BigUint, + pub c: BigUint, + pub s: BigUint, pub session_id: String, } #[tonic::async_trait] -impl Auth for AuthImpl -{ +impl Auth for AuthImpl { + /// Registers a new user by storing their information in the `user_info` hashmap. + /// + /// This method takes a `RegisterRequest` containing the user's name and two big integers + /// (y1 and y2) that are part of the Chaum-Pedersen ZKP protocol. If registration is successful, + /// it logs a success message and returns an empty `RegisterResponse`. + /// + /// # Arguments + /// + /// * `request` - A `Request` containing the user's registration details. + /// + /// # Returns + /// + /// A `Result` containing either a `Response` on success or a `Status` error. async fn register( &self, request: Request, - ) -> Result, Status> - { + ) -> Result, Status> { let request = request.into_inner(); let user_name = request.user; @@ -102,17 +137,33 @@ impl Auth for AuthImpl ..Default::default() }; - let user_info_hashmap = &mut self.user_info.lock().unwrap(); - user_info_hashmap.insert(user_name.clone(), user_info); + { + let user_info_hashmap = &mut self.user_info.lock().unwrap(); + user_info_hashmap.insert(user_name.clone(), user_info); + } println!("✅ Successful Registration username: {:?}", user_name); Ok(Response::new(RegisterResponse {})) } + /// Creates an authentication challenge for a registered user. + /// + /// This method generates a random challenge value and an authentication ID for the user specified + /// in the `AuthenticationChallengeRequest`. It updates the user's information with the received + /// values and logs a success message upon completion. + /// + /// # Arguments + /// + /// * `request` - A `Request` containing the user's name and + /// the values r1 and r2 for the challenge. + /// + /// # Returns + /// + /// A `Result` containing either a `Response` with the challenge details + /// or a `Status` error if the user is not found. async fn create_authentication_challenge( &self, request: Request, - ) -> Result, Status> - { + ) -> Result, Status> { let request = request.into_inner(); let user_name = request.user; @@ -120,13 +171,12 @@ impl Auth for AuthImpl let user_info_hashmap = &mut self.user_info.lock().unwrap(); - if let Some(user_info) = user_info_hashmap.get_mut(&user_name) - { + if let Some(user_info) = user_info_hashmap.get_mut(&user_name) { let (_, _, _, q) = ZKP::get_constants(); let c = ZKP::generate_random_number_below(&q); let auth_id = ZKP::generate_random_string(12); - user_info.c = c.clone(); + user_info.c.clone_from(&c); user_info.r1 = BigUint::from_bytes_be(&request.r1); user_info.r2 = BigUint::from_bytes_be(&request.r2); @@ -139,9 +189,7 @@ impl Auth for AuthImpl auth_id, c: c.to_bytes_be(), })) - } - else - { + } else { Err(Status::new( Code::NotFound, format!("User: {} not found in database", user_name), @@ -149,10 +197,23 @@ impl Auth for AuthImpl } } - async fn verify_authentication( + /// Verifies the solution to an authentication challenge. + /// + /// This method checks the provided solution against the stored user information using the + /// ZKP verification process. If successful, it generates a session ID and returns it in the + /// `AuthenticationAnswerResponse`. If the verification fails, an error status is returned. + /// + /// # Arguments + /// + /// * `request` - A `Request` containing the authentication ID and the solution. + /// + /// # Returns + /// + /// A `Result` containing either a `Response` with the session ID + /// or a `Status` error if the authentication ID is not found or the solution is incorrect. + async fn verify_authentication( &self, request: Request, - ) -> Result, Status> - { + ) -> Result, Status> { let request = request.into_inner(); let auth_id = request.auth_id; @@ -160,12 +221,11 @@ impl Auth for AuthImpl let auth_id_to_user_hashmap = &mut self.auth_id_to_user.lock().unwrap(); - if let Some(user_name) = auth_id_to_user_hashmap.get(&auth_id) - { + if let Some(user_name) = auth_id_to_user_hashmap.get(&auth_id) { let user_info_hashmap = &mut self.user_info.lock().unwrap(); - let user_info = user_info_hashmap - .get_mut(user_name) - .expect("AuthId not found on hashmap"); + let user_info = user_info_hashmap.get_mut(user_name).ok_or_else(|| { + Status::new(Code::NotFound, format!("AuthId {} not found in database", auth_id)) + })?; let s = BigUint::from_bytes_be(&request.s); user_info.s = s; @@ -182,16 +242,13 @@ impl Auth for AuthImpl &user_info.s, ); - if verification - { + if verification { let session_id = ZKP::generate_random_string(12); println!("✅ Correct Challenge Solution username: {:?}", user_name); Ok(Response::new(AuthenticationAnswerResponse { session_id })) - } - else - { + } else { println!("❌ Wrong Challenge Solution username: {:?}", user_name); Err(Status::new( @@ -199,9 +256,7 @@ impl Auth for AuthImpl format!("AuthId: {} bad solution to the challenge", auth_id), )) } - } - else - { + } else { Err(Status::new( Code::NotFound, format!("AuthId: {} not found in database", auth_id),