1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
use curve25519_dalek::RistrettoPoint;
use num_bigint::BigUint;
use sha2::{Digest, Sha512};
use std::str::FromStr;
use structopt::StructOpt;
use strum::VariantNames;
use zk_pass::conversion::ByteConvertible;

use pasta_curves::pallas::Point as PallasPoint;
use pasta_curves::vesta::Point as VestaPoint;
use std::error::Error;
use zk_pass::chaum_pedersen::{
    curve25519::Curve25519ChaumPedersen, discretelog::DiscreteLogChaumPedersen,
    pallas::PallasCurveChaumPedersen, vesta::VestaCurveChaumPedersen, GroupParams,
};
use zk_pass::client::execute_protocol;
use zk_pass::client::AuthClientLib;
use zk_pass::cmdutil::{ChaumPedersenType, EllipticCurveType, RfcModpType};
use zk_pass::rand::RandomGenerator;

/// Command-line options structure for the ZKPass client.
#[derive(Debug, StructOpt)]
#[structopt(name = "client", about = "A client for the ZKPass server")]
struct Opt {
    /// The host address of the ZKPass server.
    #[structopt(short, long, default_value = "[::1]")]
    host: String,

    /// The port number to connect to the ZKPass server.
    #[structopt(short, long, default_value = "50051")]
    port: i32,

    /// Optional secret passcode for authentication.
    #[structopt(short, long)]
    secret: Option<String>,

    /// Username for identification.
    #[structopt(short, long, default_value = "foo")]
    user: String,

    /// Type of RFC log group to use for the Discrete Log implementation of Chaum-Pedersen.
    #[structopt(short, long, possible_values = RfcModpType::VARIANTS, default_value = "rfc5114_modp_1024_160", required_if("stereotype", "discrete_log"))]
    modp: RfcModpType,

    /// Underlying type of the Chaum-Pedersen protocol to use.
    #[structopt(short, long, possible_values = ChaumPedersenType::VARIANTS, default_value = "discrete_log")]
    r#type: ChaumPedersenType,

    /// Elliptic curve type for the Elliptic Curve implementation of Chaum-Pedersen.
    #[structopt(short, long, possible_values = EllipticCurveType::VARIANTS, default_value = "ec25519", required_if("stereotype", "elliptic_curve"))]
    curve: EllipticCurveType,
}

/// Hashes the provided secret string or generates a random value.
///
/// This function takes an optional secret string and performs one of two actions:
/// - If a secret string is provided, it hashes the string using SHA-512 and then
///   converts the hash to the specified type `T`.
/// - If no secret is provided (i.e., `None`), it generates a random value of type `T`.
///
/// # Type Parameters
/// * `T`: The target type for the hashed or random value. The type must implement
///   `ByteConvertible` to allow conversion from bytes to `T`, and `RandomGenerator`
///   to allow generation of a random value of type `T`.
///
/// # Parameters
/// * `secret`: An `Option<&String>` representing the secret string to hash.
///   - `Some(&String)`: The string to hash.
///   - `None`: Indicates that a random value should be generated instead of hashing.
///
/// # Returns
/// Returns a value of type `T`. The value is either:
/// - The hash of the provided secret string, converted to type `T`, or
/// - A randomly generated value of type `T`, if no secret string was provided.
///
/// # Panics
/// This function may panic in the following cases:
/// - If conversion from the hash bytes to the target type `T` fails.
/// - If random value generation for the target type `T` fails.
///
/// # Examples
/// ```
/// let secret = Some(String::from("my_secret"));
/// let hashed_secret: [u8; 64] = hash_or_randomize_secret(secret.as_ref());
/// // hashed_secret is now the SHA-512 hash of "my_secret", as an array of bytes.
///
/// let random_secret: [u8; 64] = hash_or_randomize_secret(None);
/// // random_secret is now a randomly generated array of bytes.
/// ```
fn hash_or_randomize_secret<T: ByteConvertible<T> + RandomGenerator<T>>(
    secret: Option<&String>,
) -> T {
    match secret {
        Some(s) => {
            let mut hasher = Sha512::new();
            hasher.update(s);
            let result = hasher.finalize();
            T::convert_from(&result).expect("Failed to convert hash to target type")
        }
        None => T::generate_random().expect("Failed to generate random value"),
    }
}

/// Main entry point for the ZKPass client.
///
/// ## Usage
/// This program starts a client to interact with a server implementing the ZKPass Chaum-Pedersen protocol.
/// It requires command-line arguments to specify its configuration and to perform authentication.
///
/// ### Starting the Client from the Command Line
///
/// 1. **Open Terminal or Command Prompt:**
///    Ensure you have a terminal or command prompt window open.
///
/// 2. **Navigate to the Project Directory:**
///    Use the `cd` command to navigate to the directory where the client code is located.
///    ```bash
///    cd path/to/project_directory
///    ```
///
/// 3. **Build the Project (if needed):**
///    If you haven't already built your Rust project, build it using the `cargo build` command.
///    ```bash
///    cargo build
///    ```
///
/// 4. **Run the Client:**
///    Start the client using the `cargo run` command followed by the necessary options.
///    ```bash
///    cargo run -- --host <host_address> --port <port_number> --secret <secret_passcode> --user <user_name> --modp <modp_type> --type <protocol_type> --curve <elliptic_curve_type>
///    ```
///
///    Replace `<host_address>`, `<port_number>`, `<secret_passcode>`, `<user_name>`, `<modp_type>`, `<protocol_type>`, and `<elliptic_curve_type>` with appropriate values.
///
/// ### Command Line Options
///
/// - `--host` or `-h`: Sets the host address of the ZKPass server. Defaults to "[::1]" if not specified.
/// - `--port` or `-p`: Sets the port number of the ZKPass server. Defaults to 50051 if not specified.
/// - `--secret` or `-s`: Sets the secret passcode for authentication. Optional.
/// - `--user` or `-u`: Sets the username for authentication. Defaults to "foo" if not specified.
/// - `--modp` or `-m`: Sets the type of the RFC log group to use. Required if `--type` is "discrete_log".
/// - `--type` or `-t`: Sets the type of the Chaum-Pedersen protocol to use. Possible values: "discrete_log", "elliptic_curve".
/// - `--curve` or `-c`: Sets the elliptic curve type. Required if `--type` is "elliptic_curve".
///
/// ### Example Usage
///
/// To connect to a server on localhost, port 50051, using the elliptic curve protocol with user "alice":
/// ```bash
/// cargo run -- --host [::1] --port 50051 --user alice --type elliptic_curve --curve ec25519
/// ```
///
/// Remember to replace the values in the command with those suitable for your setup, and that the server must be serving the same protocol (type, modp, curve) as the client.
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let opt = Opt::from_args(); // Parses command-line arguments.

    // Displays initial client information.
    println!("🔥 Starting ZK_PASS client 🔥");
    println!("      🤖 host: {}", opt.host);
    println!("      🔌 port: {}", opt.port);
    println!("      💥 stereotype: {}", opt.r#type);
    if opt.r#type == ChaumPedersenType::EllipticCurve {
        println!("      📈 elliptic curve: {}", opt.curve)
    } else {
        println!("      🔢 modp group: {}", opt.modp)
    }
    println!("      🔑 user: {}", opt.user);

    // Establishes a connection to the ZKPass server.
    let mut client = AuthClientLib::connect(format!("http://{}:{}", opt.host, opt.port)).await?;
    execute_selected_protocol(opt, &mut client).await?;
    Ok(())
}

async fn execute_selected_protocol(
    opt: Opt, client: &mut AuthClientLib,
) -> Result<(), Box<dyn Error>> {
    // Executes the selected Chaum-Pedersen protocol.
    match opt.r#type {
        ChaumPedersenType::DiscreteLog => {
            let dl_params =
                GroupParams::<BigUint>::from_str(&opt.modp.to_string()).map_err(|_| {
                    "Invalid discrete log group parameters provided in command-line arguments"
                        .to_string()
                })?;
            // Executes the discrete log version of the protocol
            execute_protocol::<DiscreteLogChaumPedersen, _, _>(
                &dl_params,
                &hash_or_randomize_secret(opt.secret.as_ref()),
                &opt.user,
                client,
            )
            .await
        }
        ChaumPedersenType::EllipticCurve => {
            match opt.curve {
                EllipticCurveType::Ec25519 => {
                    let ec_params = GroupParams::<RistrettoPoint>::from_str(&opt.curve.to_string())
                    .map_err(|_| {
                        "Invalid elliptic curve group parameters provided in command-line arguments"
                            .to_string()
                    })?;
                    // Executes the elliptic curve version of the protocol
                    execute_protocol::<Curve25519ChaumPedersen, _, _>(
                        &ec_params,
                        &hash_or_randomize_secret(opt.secret.as_ref()),
                        &opt.user,
                        client,
                    )
                    .await
                }
                EllipticCurveType::Pallas => {
                    let ec_params = GroupParams::<PallasPoint>::from_str(&opt.curve.to_string())
                    .map_err(|_| {
                        "Invalid elliptic curve group parameters provided in command-line arguments"
                            .to_string()
                    })?;
                    // Executes the elliptic curve version of the protocol
                    execute_protocol::<PallasCurveChaumPedersen, _, _>(
                        &ec_params,
                        &hash_or_randomize_secret(opt.secret.as_ref()),
                        &opt.user,
                        client,
                    )
                    .await
                }

                EllipticCurveType::Vesta => {
                    let ec_params = GroupParams::<VestaPoint>::from_str(&opt.curve.to_string())
                    .map_err(|_| {
                        "Invalid elliptic curve group parameters provided in command-line arguments"
                            .to_string()
                    })?;
                    // Executes the elliptic curve version of the protocol
                    execute_protocol::<VestaCurveChaumPedersen, _, _>(
                        &ec_params,
                        &hash_or_randomize_secret(opt.secret.as_ref()),
                        &opt.user,
                        client,
                    )
                    .await
                }
            }
        }
    }
}