seedelf_cli/
schnorr.rs

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
use crate::{hashing::blake2b_224, register::Register};
use blstrs::{G1Affine, G1Projective, Scalar};

use ff::Field;
use hex;
use rand_core::OsRng;

/// Applies the Fiat-Shamir heuristic using the BLAKE2b-224 hash function.
///
/// This function takes four inputs as hex strings, concatenates them, and hashes
/// the resulting string using the BLAKE2b-224 hash function.
///
/// # Arguments
///
/// * `g_b` - A string representing the generator in its hex-encoded form.
/// * `g_r_b` - A string representing the randomized generator value in hex form.
/// * `u_b` - A string representing the public value in hex form.
/// * `b` - A string representing an additional value or input in hex form.
///
/// # Returns
///
/// * `String` - A hex-encoded BLAKE2b-224 hash of the concatenated input strings.
pub fn fiat_shamir_heuristic(g_b: String, g_r_b: String, u_b: String, b: String) -> String {
    // Concatenate the strings
    let concatenated: String = format!("{}{}{}{}", g_b, g_r_b, u_b, b);

    // Convert to bytes and hash
    blake2b_224(&concatenated)
}

/// Generates a cryptographically secure random scalar.
///
/// This function uses a secure random number generator (`OsRng`) to produce
/// a random `Scalar` suitable for cryptographic operations.
///
/// # Returns
///
/// * `Scalar` - A randomly generated scalar.
pub fn random_scalar() -> Scalar {
    Scalar::random(&mut OsRng)
}

/// Creates a non-interactive Schnorr proof using the Fiat-Shamir heuristic.
///
/// This function generates a proof of knowledge for a secret scalar `sk` associated
/// with a `Register`. It uses a random scalar `r` and applies the Fiat-Shamir heuristic
/// to produce a challenge, which is then used to compute the response.
///
/// # Arguments
///
/// * `datum` - A `Register` containing the generator and public value as hex-encoded strings.
/// * `sk` - A secret scalar representing the private key.
/// * `bound` - A string representing an additional input for the Fiat-Shamir heuristic.
///
/// # Returns
///
/// * `(String, String)` - A tuple containing:
///     - `z` - The response scalar as a hex-encoded string.
///     - `g_r` - The blinded generator (`g^r`) as a hex-encoded compressed point.
pub fn create_proof(datum: Register, sk: Scalar, bound: String) -> (String, String) {
    let r: Scalar = random_scalar();
    let g1: G1Affine = G1Affine::from_compressed(
        &hex::decode(&datum.generator)
            .expect("Failed to decode generator hex")
            .try_into()
            .expect("Invalid generator length"),
    )
    .expect("Failed to decompress generator");

    let g_r: G1Projective = G1Projective::from(g1) * r;

    let c_hex: String = fiat_shamir_heuristic(
        datum.generator,
        hex::encode(g_r.to_compressed()),
        datum.public_value,
        bound,
    );
    let c_bytes: Vec<u8> = hex::decode(&c_hex).expect("Failed to decode Fiat-Shamir output");
    let mut c_array: [u8; 32] = [0u8; 32];
    c_array[(32 - c_bytes.len())..].copy_from_slice(&c_bytes);
    let c: Scalar = Scalar::from_bytes_be(&c_array).unwrap();

    let z: Scalar = r + c * sk;
    (
        hex::encode(z.to_bytes_be()),
        hex::encode(g_r.to_compressed()),
    )
}

/// Used for testing
pub fn prove(generator: &str, public_value: &str, z_b: &str, g_r_b: &str, bound: &str) -> bool {
    // Decode and decompress generator
    let g1: G1Affine = G1Affine::from_compressed(
        &hex::decode(generator)
            .expect("Failed to decode generator hex")
            .try_into()
            .expect("Invalid generator length"),
    )
    .expect("Failed to decompress generator");

    // Decode and decompress public_value
    let u: G1Affine = G1Affine::from_compressed(
        &hex::decode(public_value)
            .expect("Failed to decode public value hex")
            .try_into()
            .expect("Invalid public value length"),
    )
    .expect("Failed to decompress public value");

    // Decode and decompress g_r_b
    let g_r: G1Affine = G1Affine::from_compressed(
        &hex::decode(g_r_b)
            .expect("Failed to decode g_r_b hex")
            .try_into()
            .expect("Invalid g_r_b length"),
    )
    .expect("Failed to decompress g_r_b");

    // Convert z_b to Scalar
    let z_bytes: Vec<u8> = hex::decode(z_b).expect("Failed to decode z_b hex");
    let mut z_array: [u8; 32] = [0u8; 32];
    z_array[(32 - z_bytes.len())..].copy_from_slice(&z_bytes);
    let z: Scalar = Scalar::from_bytes_be(&z_array).unwrap();

    // Compute g^z = g1 * z
    let g_z: G1Projective = g1 * z; // Convert to G1Affine for comparison

    // Calculate challenge `c` using the Fiat-Shamir heuristic
    let c_hex: String = fiat_shamir_heuristic(
        generator.to_string(),
        g_r_b.to_string(),
        public_value.to_string(),
        bound.to_string(),
    );
    let c_bytes: Vec<u8> = hex::decode(&c_hex).expect("Failed to decode Fiat-Shamir output");
    let mut c_array = [0u8; 32];
    c_array[(32 - c_bytes.len())..].copy_from_slice(&c_bytes);
    let c = Scalar::from_bytes_be(&c_array).unwrap();

    // Compute u^c = (g^x)^c = g1^(x * c)
    let u_c: G1Projective = u * c;

    // Verify g^z = g^r * u^c
    g_z == (G1Projective::from(g_r) + u_c)
}