mithril_stm/signature_scheme/unique_schnorr_signature/
signing_key.rs

1use anyhow::{Context, anyhow};
2use rand_core::{CryptoRng, RngCore};
3use serde::{Deserialize, Serialize};
4
5use crate::StmResult;
6
7use super::{
8    BaseFieldElement, PrimeOrderProjectivePoint, ProjectivePoint, ScalarFieldElement,
9    SchnorrVerificationKey, UniqueSchnorrSignature, UniqueSchnorrSignatureError,
10    compute_poseidon_digest,
11};
12
13/// Schnorr Signing key, it is essentially a random scalar of the Jubjub scalar field
14#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
15pub struct SchnorrSigningKey(pub(crate) ScalarFieldElement);
16
17impl SchnorrSigningKey {
18    /// Generate a random scalar value to use as signing key
19    pub fn generate<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
20        SchnorrSigningKey(ScalarFieldElement::new_random_scalar(rng))
21    }
22
23    /// This function is an adapted version of the Schnorr signature scheme that includes
24    /// the computation of a deterministic value (called commitment_point) based on the message and the signing key
25    /// and works with the Jubjub elliptic curve and the Poseidon hash function.
26    ///
27    /// Input:
28    ///     - a message: some BaseFieldElements
29    ///     - a signing key: an element of the scalar field of the Jubjub curve
30    /// Output:
31    ///     - a unique signature of the form (commitment_point, response, challenge):
32    ///         - commitment_point is deterministic depending only on the message and signing key
33    ///         - the response and challenge depends on a random value generated during the signature
34    ///
35    /// The protocol computes:
36    ///     - commitment_point = signing_key * H(msg)
37    ///     - random_scalar, a random value
38    ///     - random_point_1 = random_scalar * H(msg)
39    ///     - random_point_2 = random_scalar * prime_order_generator_point, where generator is a generator of the prime-order subgroup of Jubjub
40    ///     - challenge = Poseidon(DST || H(msg) || verification_key || commitment_point || random_point_1 || random_point_2)
41    ///     - response = random_scalar - challenge * signing_key
42    ///
43    /// Output the signature (`commitment_point`, `response`, `challenge`)
44    ///
45    pub fn sign<R: RngCore + CryptoRng>(
46        &self,
47        msg: &[BaseFieldElement],
48        rng: &mut R,
49    ) -> StmResult<UniqueSchnorrSignature> {
50        // Use the subgroup generator to compute the curve points
51        let prime_order_generator_point = PrimeOrderProjectivePoint::create_generator();
52        let verification_key = SchnorrVerificationKey::new_from_signing_key(self.clone());
53
54        // First hashing the message to a scalar then hashing it to a curve point
55        let msg_hash_point = ProjectivePoint::hash_to_projective_point(msg)?;
56
57        let commitment_point = self.0 * msg_hash_point;
58
59        let random_scalar = ScalarFieldElement::new_random_nonzero_scalar(rng)
60            .with_context(|| "Random scalar generation failed during signing.")?;
61
62        let random_point_1 = random_scalar * msg_hash_point;
63        let random_point_2 = random_scalar * prime_order_generator_point;
64
65        // Since the hash function takes as input scalar elements
66        // We need to convert the EC points to their coordinates
67        // The order must be preserved
68        let points_coordinates: Vec<BaseFieldElement> = [
69            msg_hash_point,
70            ProjectivePoint::from(verification_key.0),
71            commitment_point,
72            random_point_1,
73            ProjectivePoint::from(random_point_2),
74        ]
75        .iter()
76        .flat_map(|point| {
77            let (u, v) = point.get_coordinates();
78            [u, v]
79        })
80        .collect();
81
82        let challenge = compute_poseidon_digest(&points_coordinates);
83        let challenge_times_sk = ScalarFieldElement::from_base_field(&challenge)? * self.0;
84        let response = random_scalar - challenge_times_sk;
85
86        Ok(UniqueSchnorrSignature {
87            commitment_point,
88            response,
89            challenge,
90        })
91    }
92
93    /// Convert a `SchnorrSigningKey` into bytes.
94    pub fn to_bytes(&self) -> [u8; 32] {
95        self.0.to_bytes()
96    }
97
98    /// Convert bytes into a `SchnorrSigningKey`.
99    ///
100    /// The bytes must represent a Jubjub scalar or the conversion will fail
101    pub fn from_bytes(bytes: &[u8]) -> StmResult<Self> {
102        if bytes.len() < 32 {
103            return Err(anyhow!(UniqueSchnorrSignatureError::Serialization)).with_context(
104                || "Not enough bytes provided to re-construct a Schnorr signing key.",
105            );
106        }
107        let scalar_field_element = ScalarFieldElement::from_bytes(bytes)
108            .with_context(|| "Could not construct Schnorr signing key from given bytes.")?;
109        Ok(SchnorrSigningKey(scalar_field_element))
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use rand_chacha::ChaCha20Rng;
116    use rand_core::SeedableRng;
117
118    use crate::signature_scheme::SchnorrSigningKey;
119
120    #[test]
121    fn generate_different_keys() {
122        let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
123        let sk1 = SchnorrSigningKey::generate(&mut rng);
124        let sk2 = SchnorrSigningKey::generate(&mut rng);
125
126        // Keys should be different
127        assert_ne!(sk1, sk2, "Different keys should be generated");
128    }
129
130    #[test]
131    fn from_bytes_not_enough_bytes() {
132        let bytes = vec![0u8; 31];
133        let result = SchnorrSigningKey::from_bytes(&bytes);
134
135        result.expect_err("Should fail with insufficient bytes");
136    }
137
138    #[test]
139    fn from_bytes_exact_size() {
140        let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
141        let sk = SchnorrSigningKey::generate(&mut rng);
142        let sk_bytes = sk.to_bytes();
143
144        let sk_restored = SchnorrSigningKey::from_bytes(&sk_bytes).unwrap();
145
146        assert_eq!(sk, sk_restored);
147    }
148
149    #[test]
150    fn from_bytes_extra_bytes() {
151        let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
152        let sk = SchnorrSigningKey::generate(&mut rng);
153        let sk_bytes = sk.to_bytes();
154
155        let mut extended_bytes = sk_bytes.to_vec();
156        extended_bytes.extend_from_slice(&[0xFF; 10]);
157
158        let sk_restored = SchnorrSigningKey::from_bytes(&extended_bytes).unwrap();
159
160        assert_eq!(sk, sk_restored);
161    }
162
163    #[test]
164    fn to_bytes_is_deterministic() {
165        let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
166        let sk = SchnorrSigningKey::generate(&mut rng);
167
168        let bytes1 = sk.to_bytes();
169        let bytes2 = sk.to_bytes();
170
171        assert_eq!(bytes1, bytes2, "to_bytes should be deterministic");
172    }
173
174    mod golden {
175        use super::*;
176
177        const GOLDEN_BYTES: &[u8; 32] = &[
178            126, 191, 239, 197, 88, 151, 248, 254, 187, 143, 86, 35, 29, 62, 90, 13, 196, 71, 234,
179            5, 90, 124, 205, 194, 51, 192, 228, 133, 25, 140, 157, 7,
180        ];
181
182        fn golden_value() -> SchnorrSigningKey {
183            let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
184            SchnorrSigningKey::generate(&mut rng)
185        }
186
187        #[test]
188        fn golden_conversions() {
189            let value = SchnorrSigningKey::from_bytes(GOLDEN_BYTES)
190                .expect("This from bytes should not fail");
191            assert_eq!(golden_value().0, value.0);
192
193            let serialized = SchnorrSigningKey::to_bytes(&value);
194            let golden_serialized = SchnorrSigningKey::to_bytes(&golden_value());
195            assert_eq!(golden_serialized, serialized);
196        }
197    }
198
199    mod golden_json {
200        use super::*;
201
202        const GOLDEN_JSON: &str = r#"[126, 191, 239, 197, 88, 151, 248, 254, 187, 143, 86, 35, 29, 62, 90, 13, 196, 71, 234, 5, 90, 124, 205, 194, 51, 192, 228, 133, 25, 140, 157, 7]"#;
203
204        fn golden_value() -> SchnorrSigningKey {
205            let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
206            SchnorrSigningKey::generate(&mut rng)
207        }
208
209        #[test]
210        fn golden_conversions() {
211            let value = serde_json::from_str(GOLDEN_JSON)
212                .expect("This JSON deserialization should not fail");
213            assert_eq!(golden_value(), value);
214
215            let serialized =
216                serde_json::to_string(&value).expect("This JSON serialization should not fail");
217            let golden_serialized = serde_json::to_string(&golden_value())
218                .expect("This JSON serialization should not fail");
219            assert_eq!(golden_serialized, serialized);
220        }
221    }
222}