From ab4b34a5749e3bf44dc9b31af7a67379a384b272 Mon Sep 17 00:00:00 2001 From: John Basrai Date: Mon, 2 Oct 2023 22:06:23 -0700 Subject: [PATCH] Adding zkp sources --- .gitignore | 1 + Cargo.toml | 25 +++++ README.md | 37 ++++++- build.rs | 11 ++ proto/zkp_auth.proto | 51 ++++++++++ rust-toolchain.toml | 2 + rustfmt.toml | 19 ++++ src/client.rs | 95 +++++++++++++++++ src/lib.rs | 237 +++++++++++++++++++++++++++++++++++++++++++ src/server.rs | 183 +++++++++++++++++++++++++++++++++ 10 files changed, 659 insertions(+), 2 deletions(-) create mode 100644 Cargo.toml create mode 100644 build.rs create mode 100644 proto/zkp_auth.proto create mode 100644 rust-toolchain.toml create mode 100644 rustfmt.toml create mode 100644 src/client.rs create mode 100644 src/lib.rs create mode 100644 src/server.rs diff --git a/.gitignore b/.gitignore index 6985cf1..ca4899e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ # will have compiled files and executables debug/ target/ +src/zkp_auth.rs # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..47ac4bb --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "zkp-chaum-pedersen" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rand = "0.8" +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 + +[build-dependencies] +tonic-build = "0.9" + +[[bin]] +name = "server" +path = "./src/server.rs" + +[[bin]] +name = "client" +path = "./src/client.rs" \ No newline at end of file diff --git a/README.md b/README.md index 8d96ef3..97c0b94 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,35 @@ -# zkp-cp -Chaum-Pedersen Zero Knowledge Proof +# Chaum-Pedersen Zero Knowledge Proof +# gRPC client/server for authentication + +Rust crates directly used + +- tokio, Rusts asynchronous runtime. + + Tokio is a tool that provides the building blocks for writing network + applications without compromising speed. It includes a stack of + various components, such as Hyper, Tonic, Tower, and Mio, for + different needs and scenarios. + +- tonic - gRPC, Rust implementation of gRPC + +## Building + +``` +cargo build --release --bin client --bin server +``` + +## Running + +In one shell window run command +``` +cargo run --release --bin server +``` +In a second shell window run command +``` +cargo run --release --bin client +``` + +## Containerization + +Work in progress. + diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..6f3b293 --- /dev/null +++ b/build.rs @@ -0,0 +1,11 @@ +fn main() +{ + tonic_build::configure() + .build_server(true) + .out_dir("src/") // you can change the generated code's location + .compile( + &["proto/zkp_auth.proto"], + &["proto/"], // specify the root location to search proto dependencies + ) + .unwrap(); +} diff --git a/proto/zkp_auth.proto b/proto/zkp_auth.proto new file mode 100644 index 0000000..e7a50bf --- /dev/null +++ b/proto/zkp_auth.proto @@ -0,0 +1,51 @@ +syntax = "proto3"; +package zkp_auth; + +/* + * Prover registers in the server sending: + * y1 = alpha^x mod p + * y2 = beta^x mod p + */ +message RegisterRequest { + string user = 1; + bytes y1 = 2; + bytes y2 = 3; +} + +message RegisterResponse {} + +/* + * Prover ask for challenge in the server sending + * r1 = alpha^k mod p + * r2 = beta^k mod p + * Verifier sends the challenge "c" back + */ +message AuthenticationChallengeRequest { + string user = 1; + bytes r1 = 2; + bytes r2 = 3; +} + +message AuthenticationChallengeResponse { + string auth_id = 1; + bytes c = 2; +} + +/* + * Prover sends solution "s = k - c * x mod q" to the challenge + * Verifier sends the session ID if the solution is correct + */ +message AuthenticationAnswerRequest { + string auth_id = 1; + bytes s = 2; +} + +message AuthenticationAnswerResponse { + string session_id = 1; +} + +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 new file mode 100644 index 0000000..271800c --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..7953691 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,19 @@ +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 new file mode 100644 index 0000000..5f672a4 --- /dev/null +++ b/src/client.rs @@ -0,0 +1,95 @@ +use num_bigint::BigUint; +use std::io::stdin; + +pub mod zkp_auth +{ + include!("./zkp_auth.rs"); +} + +use zkp_auth::{ + auth_client::AuthClient, AuthenticationAnswerRequest, AuthenticationChallengeRequest, + RegisterRequest, +}; +use zkp_chaum_pedersen::ZKP; + +#[tokio::main] +async fn main() +{ + let mut buf = String::new(); + let (alpha, beta, p, q) = ZKP::get_constants(); + let zkp = ZKP::new(&alpha, &beta, &p, &q); + + let mut client = AuthClient::connect("http://127.0.0.1:50051") + .await + .expect("could not connect to the server"); + println!("✅ Connected to the server"); + + println!("Please provide the username:"); + stdin() + .read_line(&mut buf) + .expect("Could not get the username from stdin"); + let username = buf.trim().to_string(); + buf.clear(); + + println!("Please provide the password:"); + stdin() + .read_line(&mut buf) + .expect("Could not get the username from stdin"); + let password = BigUint::from_bytes_be(buf.trim().as_bytes()); + buf.clear(); + + let (y1, y2) = zkp.compute_pair(&password); + + let request = RegisterRequest { + user: username.clone(), + y1: y1.to_bytes_be(), + y2: y2.to_bytes_be(), + }; + + let _response = client + .register(request) + .await + .expect("Could not register in server"); + + println!("✅ Registration was successful"); + + println!("Please provide the password (to login):"); + stdin() + .read_line(&mut buf) + .expect("Could not get the username from stdin"); + let password = BigUint::from_bytes_be(buf.trim().as_bytes()); + buf.clear(); + + 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(), + }; + + let response = client + .create_authentication_challenge(request) + .await + .expect("Could not request challenge to server") + .into_inner(); + + let auth_id = response.auth_id; + let c = BigUint::from_bytes_be(&response.c); + + let s = zkp.solve(&k, &c, &password); + + let request = AuthenticationAnswerRequest { + auth_id, + s: s.to_bytes_be(), + }; + + let response = client + .verify_authentication(request) + .await + .expect("Could not verify authentication in server") + .into_inner(); + + println!("✅Logging successful! session_id: {}", response.session_id); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..d390c9a --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,237 @@ +use num_bigint::{BigUint, RandBigInt}; +use rand::Rng; + +pub struct ZKP +{ + pub alpha: BigUint, + pub beta: BigUint, + pub p: BigUint, + pub q: BigUint, +} + +impl ZKP +{ + pub fn new(alpha: &BigUint, beta: &BigUint, p: &BigUint, q: &BigUint) -> Self + { + Self { + alpha: alpha.clone(), + beta: beta.clone(), + p: p.clone(), + q: q.clone(), + } + } + + /// output = (alpha^exp mod p, beta^exp mod p) + 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 + { + (k - c * x).modpow(&BigUint::from(1u32), &self.q) + } + else + { + &self.q - (c * x - k).modpow(&BigUint::from(1u32), &self.q) + } + } + + /// cond1: r1 = alpha^s * y1^c + /// cond2: r2 = beta^s * y2^c + pub fn verify( + &self, r1: &BigUint, r2: &BigUint, y1: &BigUint, y2: &BigUint, c: &BigUint, s: &BigUint, + ) -> bool + { + let cond1 = *r1 + == (&self.alpha.modpow(s, &self.p) * y1.modpow(c, &self.p)) + .modpow(&BigUint::from(1u32), &self.p); + + let cond2 = *r2 + == (&self.beta.modpow(s, &self.p) * y2.modpow(c, &self.p)) + .modpow(&BigUint::from(1u32), &self.p); + + cond1 && cond2 + } + + pub fn generate_random_number_below(bound: &BigUint) -> BigUint + { + rand::thread_rng().gen_biguint_below(bound) + } + + pub fn generate_random_string(size: usize) -> String + { + rand::thread_rng() + .sample_iter(rand::distributions::Alphanumeric) + .take(size) + .map(char::from) + .collect() + } + + #[rustfmt::skip] + pub fn get_constants() -> (BigUint, BigUint, BigUint, BigUint) + { + // Reference: https://www.rfc-editor.org/rfc/rfc5114#page-15 + // + let p = BigUint::from_bytes_be(&hex::decode( + "B10B8F96A080E01DDE92DE5EAE5D54EC52C99FBCFB06A3C69A6A9DCA52D23B61607\ + 3E28675A23D189838EF1E2EE652C013ECB4AEA906112324975C3CD49B83BFACCBDD\ + 7D90C4BD7098488E9C219A73724EFFD6FAE5644738FAA31A4FF55BCCC0A151AF5F0\ + DC8B4BD45BF37DF365C1A65E68CFDA76D4DA708DF1FB2BC2E4A4371").unwrap()); + + let q = BigUint::from_bytes_be( + &hex::decode("F518AA8781A8DF278ABA4E7D64B7CB9D49462353").unwrap()); + + let alpha = BigUint::from_bytes_be(&hex::decode( + "A4D1CBD5C3FD34126765A442EFB99905F8104DD258AC507FD6406CFF14266D3\ + 1266FEA1E5C41564B777E690F5504F213160217B4B01B886A5E91547F9E2749\ + F4D7FBD7D3B9A92EE1909D0D2263F80A76A6A24C087A091F531DBF0A0169B6A\ + 28AD662A4D18E73AFA32D779D5918D08BC8858F4DCEF97C2A24855E6EEB22B3B2E5").unwrap()); + + // beta = alpha^i is also a generator + let beta = alpha.modpow(&ZKP::generate_random_number_below(&q), &p); + + (alpha, beta, p, q) + } +} + +#[cfg(test)] +mod test +{ + use super::*; + + #[test] + fn test_toy_example() + { + let alpha = BigUint::from(4_u32); + let beta = BigUint::from(9_u32); + let p = BigUint::from(23_u32); + let q = BigUint::from(11_u32); + let zkp = ZKP::new(&alpha, &beta, &p, &q); + + let x = BigUint::from(6_u32); + let k = BigUint::from(7_u32); + + let c = BigUint::from(4_u32); + + let (y1, y2) = zkp.compute_pair(&x); + assert_eq!(y1, BigUint::from(2_u32)); + assert_eq!(y2, BigUint::from(3_u32)); + + let (r1, r2) = zkp.compute_pair(&k); + assert_eq!(r1, BigUint::from(8_u32)); + assert_eq!(r2, BigUint::from(4_u32)); + + let s = zkp.solve(&k, &c, &x); + assert_eq!(s, BigUint::from(5_u32)); + + let result = zkp.verify(&r1, &r2, &y1, &y2, &c, &s); + assert!(result); + + // fake secret + let x_fake = BigUint::from(7_u32); + let s_fake = zkp.solve(&k, &c, &x_fake); + + let result = zkp.verify(&r1, &r2, &y1, &y2, &c, &s_fake); + assert!(!result); + } + + #[test] + fn test_toy_example_with_random_numbers() + { + let alpha = BigUint::from(4_u32); + let beta = BigUint::from(9_u32); + let p = BigUint::from(23_u32); + let q = BigUint::from(11_u32); + let zkp = ZKP::new(&alpha, &beta, &p, &q); + + let x = BigUint::from(6_u32); + let k = ZKP::generate_random_number_below(&q); + let c = ZKP::generate_random_number_below(&q); + + let (y1, y2) = zkp.compute_pair(&x); + assert_eq!(y1, BigUint::from(2_u32)); + assert_eq!(y2, BigUint::from(3_u32)); + + let (r1, r2) = zkp.compute_pair(&k); + + let s = zkp.solve(&k, &c, &x); + + let result = zkp.verify(&r1, &r2, &y1, &y2, &c, &s); + assert!(result); + } + + #[test] + fn test_1024_bits_constants() + { + let (alpha, beta, p, q) = ZKP::get_constants(); + let zkp = ZKP::new(&alpha, &beta, &p, &q); + + let x = ZKP::generate_random_number_below(&q); + let k = ZKP::generate_random_number_below(&q); + let c = ZKP::generate_random_number_below(&q); + + let (y1, y2) = zkp.compute_pair(&x); + + let (r1, r2) = zkp.compute_pair(&k); + + let s = zkp.solve(&k, &c, &x); + + let result = zkp.verify(&r1, &r2, &y1, &y2, &c, &s); + assert!(result); + } + + #[test] + fn test_2048_bits_constants() + { + // + // Reference: https://www.rfc-editor.org/rfc/rfc5114#page-15 + + #[rustfmt::skip] + let p = BigUint::from_bytes_be(&hex::decode( + "AD107E1E9123A9D0D660FAA79559C51FA20D64E5683B9FD1B54B1597B61D0A75E6F\ + A141DF95A56DBAF9A3C407BA1DF15EB3D688A309C180E1DE6B85A1274A0A66D3F81\ + 52AD6AC2129037C9EDEFDA4DF8D91E8FEF55B7394B7AD5B7D0B6C12207C9F98D11E\ + D34DBF6C6BA0B2C8BBC27BE6A00E0A0B9C49708B3BF8A317091883681286130BC89\ + 85DB1602E714415D9330278273C7DE31EFDC7310F7121FD5A07415987D9ADC0A486\ + DCDF93ACC44328387315D75E198C641A480CD86A1B9E587E8BE60E69CC928B2B9C5\ + 2172E413042E9B23F10B0E16E79763C9B53DCF4BA80A29E3FB73C16B8E75B97EF36\ + 3E2FFA31F71CF9DE5384E71B81C0AC4DFFE0C10E64F").unwrap()); + let q = BigUint::from_bytes_be(&hex::decode( + "801C0D34C58D93FE997177101F80535A4738CEBCBF389A99B36371EB").unwrap(), + ); + + let alpha = BigUint::from_bytes_be(&hex::decode( + "AC4032EF4F2D9AE39DF30B5C8FFDAC506CDEBE7B89998CAF74866A08CFE4FFE3A68\ + 24A4E10B9A6F0DD921F01A70C4AFAAB739D7700C29F52C57DB17C620A8652BE5E90\ + 01A8D66AD7C17669101999024AF4D027275AC1348BB8A762D0521BC98AE24715042\ + 2EA1ED409939D54DA7460CDB5F6C6B250717CBEF180EB34118E98D119529A45D6F8\ + 34566E3025E316A330EFBB77A86F0C1AB15B051AE3D428C8F8ACB70A8137150B8EE\ + B10E183EDD19963DDD9E263E4770589EF6AA21E7F5F2FF381B539CCE3409D13CD56\ + 6AFBB48D6C019181E1BCFE94B30269EDFE72FE9B6AA4BD7B5A0F1C71CFFF4C19C41\ + 8E1F6EC017981BC087F2A7065B384B890D3191F2BFA").unwrap(), + ); + + // beta = alpha^i is also a generator + let beta = alpha.modpow(&ZKP::generate_random_number_below(&q), &p); + + let zkp = ZKP::new(&alpha, &beta, &p, &q); + + let x = ZKP::generate_random_number_below(&q); + let k = ZKP::generate_random_number_below(&q); + let c = ZKP::generate_random_number_below(&q); + + let (y1, y2) = zkp.compute_pair(&x); + let (r1, r2) = zkp.compute_pair(&k); + + let s = zkp.solve(&k, &c, &x); + + let result = zkp.verify(&r1, &r2, &y1, &y2, &c, &s); + assert!(result); + } +} diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 0000000..21ac01a --- /dev/null +++ b/src/server.rs @@ -0,0 +1,183 @@ +use std::{collections::HashMap, sync::Mutex}; + +use num_bigint::BigUint; +use tonic::{transport::Server, Code, Request, Response, Status}; + +use zkp_chaum_pedersen::ZKP; + +pub mod zkp_auth +{ + include!("./zkp_auth.rs"); +} + +use zkp_auth::{ + auth_server::{Auth, AuthServer}, + AuthenticationAnswerRequest, AuthenticationAnswerResponse, AuthenticationChallengeRequest, + AuthenticationChallengeResponse, RegisterRequest, RegisterResponse, +}; + +#[derive(Debug, Default)] +pub struct AuthImpl +{ + pub user_info: Mutex>, + pub auth_id_to_user: Mutex>, +} + +#[derive(Debug, Default)] +pub struct UserInfo +{ + // registration + pub user_name: String, + pub y1: BigUint, + pub y2: BigUint, + // authorization + pub r1: BigUint, + pub r2: BigUint, + // verification + pub c: BigUint, + pub s: BigUint, + pub session_id: String, +} + +#[tonic::async_trait] +impl Auth for AuthImpl +{ + async fn register( + &self, request: Request, + ) -> Result, Status> + { + let request = request.into_inner(); + + let user_name = request.user; + println!("Processing Registration username: {:?}", user_name); + + let user_info = UserInfo { + user_name: user_name.clone(), + y1: BigUint::from_bytes_be(&request.y1), + y2: BigUint::from_bytes_be(&request.y2), + ..Default::default() + }; + + 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 {})) + } + + async fn create_authentication_challenge( + &self, request: Request, + ) -> Result, Status> + { + let request = request.into_inner(); + + let user_name = request.user; + println!("Processing Challenge Request username: {:?}", user_name); + + let user_info_hashmap = &mut self.user_info.lock().unwrap(); + + 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.r1 = BigUint::from_bytes_be(&request.r1); + user_info.r2 = BigUint::from_bytes_be(&request.r2); + + let auth_id_to_user = &mut self.auth_id_to_user.lock().unwrap(); + auth_id_to_user.insert(auth_id.clone(), user_name.clone()); + + println!("✅ Successful Challenge Request username: {:?}", user_name); + + Ok(Response::new(AuthenticationChallengeResponse { + auth_id, + c: c.to_bytes_be(), + })) + } + else + { + Err(Status::new( + Code::NotFound, + format!("User: {} not found in database", user_name), + )) + } + } + + async fn verify_authentication( + &self, request: Request, + ) -> Result, Status> + { + let request = request.into_inner(); + + let auth_id = request.auth_id; + println!("Processing Challenge Solution auth_id: {:?}", auth_id); + + 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) + { + 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 s = BigUint::from_bytes_be(&request.s); + user_info.s = s; + + let (alpha, beta, p, q) = ZKP::get_constants(); + let zkp = ZKP { alpha, beta, p, q }; + + let verification = zkp.verify( + &user_info.r1, + &user_info.r2, + &user_info.y1, + &user_info.y2, + &user_info.c, + &user_info.s, + ); + + if verification + { + let session_id = ZKP::generate_random_string(12); + + println!("✅ Correct Challenge Solution username: {:?}", user_name); + + Ok(Response::new(AuthenticationAnswerResponse { session_id })) + } + else + { + println!("❌ Wrong Challenge Solution username: {:?}", user_name); + + Err(Status::new( + Code::PermissionDenied, + format!("AuthId: {} bad solution to the challenge", auth_id), + )) + } + } + else + { + Err(Status::new( + Code::NotFound, + format!("AuthId: {} not found in database", auth_id), + )) + } + } +} + +#[tokio::main] +async fn main() +{ + let addr = "127.0.0.1:50051".to_string(); + + println!("✅ Running the server in {}", addr); + + let auth_impl = AuthImpl::default(); + + Server::builder() + .add_service(AuthServer::new(auth_impl)) + .serve(addr.parse().expect("could not convert address")) + .await + .unwrap(); +}