Skip to main content

generic_schnorr/
lib.rs

1//! Schnorr signature implementation generic over the underlying group.
2
3#![cfg_attr(not(test), no_std)]
4
5use group::Group;
6use group::ff::PrimeField;
7use group::prime::PrimeGroup;
8use rand_core::{CryptoRng, RngCore};
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12/// Signature produced by [`sign`].
13#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
14#[allow(non_snake_case)]
15#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
16pub struct Signature<Point, Scalar> {
17    pub R: Point,
18    pub s: Scalar,
19}
20
21impl<Point, Scalar> Signature<Point, Scalar> {
22    /// Get the inner `R` and `s` parameters of the signature.
23    pub const fn as_inner(&self) -> (&Point, &Scalar) {
24        (&self.R, &self.s)
25    }
26}
27
28/// Produce a [`Signature`] over `message`.
29///
30/// The group arithmetic uses `generator` to produce
31/// public keys.
32///
33/// ## Safety
34///
35/// The user must not provide a random number generator
36/// instance that is likely to produce nonce values that
37/// have already been instantiated for previous signatures.
38/// This poses the risk of exposing the underlying
39/// secret key.
40pub fn sign<Rng, HashToField, Point, Scalar>(
41    generator: &Point,
42    message: &[u8],
43    secret_key: &Scalar,
44    rng: Rng,
45    hash_to_field: HashToField,
46) -> Signature<Point, Scalar>
47where
48    HashToField: FnOnce(&Point, &Point, &[u8]) -> Scalar,
49    Rng: RngCore + CryptoRng,
50    Point: PrimeGroup + Group<Scalar = Scalar>,
51    Scalar: PrimeField,
52{
53    let nonce = Scalar::random(rng);
54
55    #[allow(non_snake_case)]
56    let R = *generator * nonce;
57
58    let challenge = hash_to_field(
59        &R,                         // commit to nonce
60        &(*generator * secret_key), // commit to public key
61        message,                    // commit to message,
62    );
63
64    let s = (challenge * secret_key) + nonce;
65
66    Signature { R, s }
67}
68
69/// Verify a signature.
70///
71/// The group arithmetic uses `generator` to produce
72/// public keys.
73pub fn verify<HashToField, Point, Scalar>(
74    generator: &Point,
75    message: &[u8],
76    public_key: &Point,
77    Signature { R, s }: &Signature<Point, Scalar>,
78    hash_to_field: HashToField,
79) -> bool
80where
81    HashToField: FnOnce(&Point, &Point, &[u8]) -> Scalar,
82    Point: PrimeGroup + Group<Scalar = Scalar>,
83    Scalar: PrimeField,
84{
85    let challenge = hash_to_field(
86        R,          // commit to nonce
87        public_key, // commit to public key
88        message,    // commit to message,
89    );
90
91    (*public_key * challenge + R - *generator * s)
92        .is_identity()
93        .into()
94}
95
96#[cfg(test)]
97mod tests {
98    use group::GroupEncoding;
99    use group::ff::{Field, FromUniformBytes};
100    use pasta_curves::pallas;
101    use rand_chacha::ChaCha20Rng;
102    use rand_core::SeedableRng;
103
104    use super::*;
105
106    #[test]
107    fn test_sign_verify() {
108        let mut csprng = test_csprng();
109
110        let message = b"eat shit";
111        let generator = pallas::Point::generator();
112        let secret_key = pallas::Scalar::random(&mut csprng);
113
114        let signature = sign(&generator, message, &secret_key, &mut csprng, hash_to_field);
115
116        assert!(verify(
117            &generator,
118            message,
119            &(generator * secret_key),
120            &signature,
121            hash_to_field,
122        ));
123    }
124
125    fn test_csprng() -> ChaCha20Rng {
126        ChaCha20Rng::from_seed([0xbe; 32])
127    }
128
129    fn hash_to_field(
130        nonce: &pallas::Point,
131        public_key: &pallas::Point,
132        message: &[u8],
133    ) -> pallas::Scalar {
134        let mut xof_stream = {
135            let mut hasher = blake3::Hasher::new();
136            hasher.update(&nonce.to_bytes());
137            hasher.update(&public_key.to_bytes());
138            hasher.update(message);
139            hasher.finalize_xof()
140        };
141
142        let mut output = [0u8; 64];
143        xof_stream.fill(&mut output);
144
145        pallas::Scalar::from_uniform_bytes(&output)
146    }
147}