mithril_stm/signature_scheme/unique_schnorr_signature/
verification_key.rs

1use std::{
2    cmp::Ordering,
3    hash::{Hash, Hasher},
4};
5
6use anyhow::{Context, Ok, anyhow};
7use serde::{Deserialize, Serialize};
8
9use crate::{StmResult, signature_scheme::BaseFieldElement};
10
11use super::{
12    PrimeOrderProjectivePoint, ProjectivePoint, SchnorrSigningKey, UniqueSchnorrSignatureError,
13};
14
15/// Schnorr verification key, it consists of a point on the Jubjub curve
16/// vk = g * sk, where g is a generator
17#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
18pub struct SchnorrVerificationKey(pub(crate) PrimeOrderProjectivePoint);
19
20impl SchnorrVerificationKey {
21    /// Convert a Schnorr signing key into a verification key
22    ///
23    /// This is done by computing `vk = g * sk` where g is the generator
24    /// of the subgroup and sk is the schnorr signing key
25    pub fn new_from_signing_key(signing_key: SchnorrSigningKey) -> StmResult<Self> {
26        if signing_key.0.is_zero() | signing_key.0.is_one() {
27            return Err(anyhow!(UniqueSchnorrSignatureError::InvalidSigningKey))
28                .with_context(|| "Verification key generation failed.");
29        }
30        let generator = PrimeOrderProjectivePoint::create_generator();
31
32        Ok(SchnorrVerificationKey(signing_key.0 * generator))
33    }
34
35    pub fn is_valid(&self) -> StmResult<Self> {
36        let projective_point = ProjectivePoint::from(self.0);
37        if !projective_point.is_prime_order() {
38            return Err(anyhow!(UniqueSchnorrSignatureError::PointIsNotPrimeOrder(
39                Box::new(self.0)
40            )));
41        }
42        self.0.is_on_curve()?;
43
44        Ok(*self)
45    }
46
47    /// Convert a `SchnorrVerificationKey` into bytes by decomposing it into
48    /// its coordinates first.
49    pub fn to_bytes(self) -> [u8; 64] {
50        let (x, y) = self.0.get_coordinates();
51        let mut output = [0; 64];
52        output[0..32].copy_from_slice(&x.to_bytes());
53        output[32..64].copy_from_slice(&y.to_bytes());
54        output
55    }
56
57    /// Convert bytes into a `SchnorrVerificationKey`.
58    ///
59    /// The bytes must represent two Jubjub Base field elements or the conversion will fail
60    pub fn from_bytes(bytes: &[u8]) -> StmResult<Self> {
61        if bytes.len() < 64 {
62            return Err(anyhow!(UniqueSchnorrSignatureError::Serialization)).with_context(
63                || "Not enough bytes provided to construct a Schnorr verification key.",
64            );
65        }
66        let x = BaseFieldElement::from_bytes(&bytes[0..32])?;
67        let y = BaseFieldElement::from_bytes(&bytes[32..64])?;
68        let prime_order_projective_point = PrimeOrderProjectivePoint::from_coordinates(x, y)
69            .with_context(|| "Cannot construct Schnorr verification key from given bytes.")?;
70
71        Ok(SchnorrVerificationKey(prime_order_projective_point))
72    }
73}
74
75impl Hash for SchnorrVerificationKey {
76    fn hash<H: Hasher>(&self, state: &mut H) {
77        Hash::hash_slice(&self.to_bytes(), state)
78    }
79}
80
81impl PartialOrd for SchnorrVerificationKey {
82    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
83        Some(std::cmp::Ord::cmp(self, other))
84    }
85}
86
87impl Ord for SchnorrVerificationKey {
88    fn cmp(&self, other: &Self) -> Ordering {
89        self.to_bytes().cmp(&other.to_bytes())
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use rand_chacha::ChaCha20Rng;
96    use rand_core::SeedableRng;
97
98    use crate::signature_scheme::{SchnorrSigningKey, SchnorrVerificationKey};
99
100    #[test]
101    fn create_verification_key_from_signing_key() {
102        let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
103        let sk = SchnorrSigningKey::generate(&mut rng).unwrap();
104
105        let vk = SchnorrVerificationKey::new_from_signing_key(sk);
106
107        assert!(
108            vk.is_ok(),
109            "Verification key creation should succeed for valid signing key"
110        );
111    }
112
113    #[test]
114    fn different_signing_keys_produce_different_verification_keys() {
115        let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
116        let sk1 = SchnorrSigningKey::generate(&mut rng).unwrap();
117        let sk2 = SchnorrSigningKey::generate(&mut rng).unwrap();
118
119        let vk1 = SchnorrVerificationKey::new_from_signing_key(sk1).unwrap();
120        let vk2 = SchnorrVerificationKey::new_from_signing_key(sk2).unwrap();
121
122        assert_ne!(
123            vk1, vk2,
124            "Different signing keys should produce different verification keys"
125        );
126    }
127
128    #[test]
129    fn same_signing_key_produces_same_verification_key() {
130        let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
131        let sk = SchnorrSigningKey::generate(&mut rng).unwrap();
132
133        let vk1 = SchnorrVerificationKey::new_from_signing_key(sk.clone()).unwrap();
134        let vk2 = SchnorrVerificationKey::new_from_signing_key(sk).unwrap();
135
136        assert_eq!(
137            vk1, vk2,
138            "Same signing key should produce same verification key"
139        );
140    }
141
142    #[test]
143    fn valid_verification_key_passes_validation() {
144        let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
145        let sk = SchnorrSigningKey::generate(&mut rng).unwrap();
146        let vk = SchnorrVerificationKey::new_from_signing_key(sk).unwrap();
147
148        let result = vk.is_valid();
149
150        assert!(
151            result.is_ok(),
152            "Valid verification key should pass validation"
153        );
154    }
155
156    #[test]
157    fn from_bytes_not_enough_bytes() {
158        let bytes = vec![0u8; 31];
159        let result = SchnorrVerificationKey::from_bytes(&bytes);
160
161        result.expect_err("Should fail with insufficient bytes");
162    }
163
164    #[test]
165    fn from_bytes_exact_size() {
166        let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
167        let sk = SchnorrSigningKey::generate(&mut rng).unwrap();
168        let vk = SchnorrVerificationKey::new_from_signing_key(sk).unwrap();
169        let vk_bytes = vk.to_bytes();
170
171        let vk_restored = SchnorrVerificationKey::from_bytes(&vk_bytes).unwrap();
172
173        assert_eq!(vk, vk_restored);
174    }
175
176    #[test]
177    fn from_bytes_extra_bytes() {
178        let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
179        let sk = SchnorrSigningKey::generate(&mut rng).unwrap();
180        let vk = SchnorrVerificationKey::new_from_signing_key(sk).unwrap();
181        let vk_bytes = vk.to_bytes();
182
183        let mut extended_bytes = vk_bytes.to_vec();
184        extended_bytes.extend_from_slice(&[0xFF; 10]);
185
186        let vk_restored = SchnorrVerificationKey::from_bytes(&extended_bytes).unwrap();
187
188        assert_eq!(vk, vk_restored);
189    }
190
191    #[test]
192    fn to_bytes_is_deterministic() {
193        let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
194        let sk = SchnorrSigningKey::generate(&mut rng).unwrap();
195        let vk = SchnorrVerificationKey::new_from_signing_key(sk).unwrap();
196
197        let bytes1 = vk.to_bytes();
198        let bytes2 = vk.to_bytes();
199
200        assert_eq!(bytes1, bytes2, "to_bytes should be deterministic");
201    }
202
203    mod golden {
204        use super::*;
205
206        const GOLDEN_BYTES: &[u8; 64] = &[
207            186, 22, 69, 162, 1, 67, 125, 160, 104, 197, 105, 109, 200, 34, 186, 196, 171, 155,
208            191, 178, 11, 116, 108, 8, 111, 249, 47, 39, 137, 55, 62, 62, 144, 52, 95, 161, 127,
209            253, 49, 32, 140, 217, 231, 207, 32, 238, 244, 196, 97, 241, 47, 95, 101, 9, 70, 136,
210            194, 66, 187, 253, 200, 32, 218, 43,
211        ];
212
213        fn golden_value() -> SchnorrVerificationKey {
214            let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
215            let sk = SchnorrSigningKey::generate(&mut rng).unwrap();
216            SchnorrVerificationKey::new_from_signing_key(sk).unwrap()
217        }
218
219        #[test]
220        fn golden_conversions() {
221            let value = SchnorrVerificationKey::from_bytes(GOLDEN_BYTES)
222                .expect("This from bytes should not fail");
223            assert_eq!(golden_value(), value);
224
225            let serialized = SchnorrVerificationKey::to_bytes(value);
226            let golden_serialized = SchnorrVerificationKey::to_bytes(golden_value());
227            assert_eq!(golden_serialized, serialized);
228        }
229    }
230
231    mod golden_json {
232        use super::*;
233
234        const GOLDEN_JSON: &str = r#"[144, 52, 95, 161, 127, 253, 49, 32, 140, 217, 231, 207, 32, 238, 244, 196, 97, 241, 47, 95, 101, 9, 70, 136, 194, 66, 187, 253, 200, 32, 218, 43]"#;
235
236        fn golden_value() -> SchnorrVerificationKey {
237            let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
238            let sk = SchnorrSigningKey::generate(&mut rng).unwrap();
239            SchnorrVerificationKey::new_from_signing_key(sk).unwrap()
240        }
241
242        #[test]
243        fn golden_conversions() {
244            let value = serde_json::from_str(GOLDEN_JSON)
245                .expect("This JSON deserialization should not fail");
246            assert_eq!(golden_value(), value);
247
248            let serialized =
249                serde_json::to_string(&value).expect("This JSON serialization should not fail");
250            let golden_serialized = serde_json::to_string(&golden_value())
251                .expect("This JSON serialization should not fail");
252            assert_eq!(golden_serialized, serialized);
253        }
254    }
255}