seedelf_cli/
schnorr.rs

1use crate::{hashing::blake2b_224, register::Register};
2use blstrs::{G1Affine, G1Projective, Scalar};
3
4use ff::Field;
5use hex;
6use rand_core::OsRng;
7
8/// Applies the Fiat-Shamir heuristic using the BLAKE2b-224 hash function.
9///
10/// This function takes four inputs as hex strings, concatenates them, and hashes
11/// the resulting string using the BLAKE2b-224 hash function.
12///
13/// # Arguments
14///
15/// * `g_b` - A string representing the generator in its hex-encoded form.
16/// * `g_r_b` - A string representing the randomized generator value in hex form.
17/// * `u_b` - A string representing the public value in hex form.
18/// * `b` - A string representing an additional value or input in hex form.
19///
20/// # Returns
21///
22/// * `String` - A hex-encoded BLAKE2b-224 hash of the concatenated input strings.
23pub fn fiat_shamir_heuristic(g_b: String, g_r_b: String, u_b: String, b: String) -> String {
24    // Concatenate the strings
25    let concatenated: String = format!("{}{}{}{}", g_b, g_r_b, u_b, b);
26
27    // Convert to bytes and hash
28    blake2b_224(&concatenated)
29}
30
31/// Generates a cryptographically secure random scalar.
32///
33/// This function uses a secure random number generator (`OsRng`) to produce
34/// a random `Scalar` suitable for cryptographic operations.
35///
36/// # Returns
37///
38/// * `Scalar` - A randomly generated scalar.
39pub fn random_scalar() -> Scalar {
40    Scalar::random(&mut OsRng)
41}
42
43/// Creates a non-interactive Schnorr proof using the Fiat-Shamir heuristic.
44///
45/// This function generates a proof of knowledge for a secret scalar `sk` associated
46/// with a `Register`. It uses a random scalar `r` and applies the Fiat-Shamir heuristic
47/// to produce a challenge, which is then used to compute the response.
48///
49/// # Arguments
50///
51/// * `datum` - A `Register` containing the generator and public value as hex-encoded strings.
52/// * `sk` - A secret scalar representing the private key.
53/// * `bound` - A string representing an additional input for the Fiat-Shamir heuristic.
54///
55/// # Returns
56///
57/// * `(String, String)` - A tuple containing:
58///     - `z` - The response scalar as a hex-encoded string.
59///     - `g_r` - The blinded generator (`g^r`) as a hex-encoded compressed point.
60pub fn create_proof(datum: Register, sk: Scalar, bound: String) -> (String, String) {
61    let r: Scalar = random_scalar();
62    let g1: G1Affine = G1Affine::from_compressed(
63        &hex::decode(&datum.generator)
64            .expect("Failed to decode generator hex")
65            .try_into()
66            .expect("Invalid generator length"),
67    )
68    .expect("Failed to decompress generator");
69
70    let g_r: G1Projective = G1Projective::from(g1) * r;
71
72    let c_hex: String = fiat_shamir_heuristic(
73        datum.generator,
74        hex::encode(g_r.to_compressed()),
75        datum.public_value,
76        bound,
77    );
78    let c_bytes: Vec<u8> = hex::decode(&c_hex).expect("Failed to decode Fiat-Shamir output");
79    let mut c_array: [u8; 32] = [0u8; 32];
80    c_array[(32 - c_bytes.len())..].copy_from_slice(&c_bytes);
81    let c: Scalar = Scalar::from_bytes_be(&c_array).unwrap();
82
83    let z: Scalar = r + c * sk;
84    (
85        hex::encode(z.to_bytes_be()),
86        hex::encode(g_r.to_compressed()),
87    )
88}
89
90/// Used for testing
91pub fn prove(generator: &str, public_value: &str, z_b: &str, g_r_b: &str, bound: &str) -> bool {
92    // Decode and decompress generator
93    let g1: G1Affine = G1Affine::from_compressed(
94        &hex::decode(generator)
95            .expect("Failed to decode generator hex")
96            .try_into()
97            .expect("Invalid generator length"),
98    )
99    .expect("Failed to decompress generator");
100
101    // Decode and decompress public_value
102    let u: G1Affine = G1Affine::from_compressed(
103        &hex::decode(public_value)
104            .expect("Failed to decode public value hex")
105            .try_into()
106            .expect("Invalid public value length"),
107    )
108    .expect("Failed to decompress public value");
109
110    // Decode and decompress g_r_b
111    let g_r: G1Affine = G1Affine::from_compressed(
112        &hex::decode(g_r_b)
113            .expect("Failed to decode g_r_b hex")
114            .try_into()
115            .expect("Invalid g_r_b length"),
116    )
117    .expect("Failed to decompress g_r_b");
118
119    // Convert z_b to Scalar
120    let z_bytes: Vec<u8> = hex::decode(z_b).expect("Failed to decode z_b hex");
121    let mut z_array: [u8; 32] = [0u8; 32];
122    z_array[(32 - z_bytes.len())..].copy_from_slice(&z_bytes);
123    let z: Scalar = Scalar::from_bytes_be(&z_array).unwrap();
124
125    // Compute g^z = g1 * z
126    let g_z: G1Projective = g1 * z; // Convert to G1Affine for comparison
127
128    // Calculate challenge `c` using the Fiat-Shamir heuristic
129    let c_hex: String = fiat_shamir_heuristic(
130        generator.to_string(),
131        g_r_b.to_string(),
132        public_value.to_string(),
133        bound.to_string(),
134    );
135    let c_bytes: Vec<u8> = hex::decode(&c_hex).expect("Failed to decode Fiat-Shamir output");
136    let mut c_array = [0u8; 32];
137    c_array[(32 - c_bytes.len())..].copy_from_slice(&c_bytes);
138    let c = Scalar::from_bytes_be(&c_array).unwrap();
139
140    // Compute u^c = (g^x)^c = g1^(x * c)
141    let u_c: G1Projective = u * c;
142
143    // Verify g^z = g^r * u^c
144    g_z == (G1Projective::from(g_r) + u_c)
145}