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
5#[cfg(feature = "verify-batch")]
6extern crate alloc;
7
8#[cfg(feature = "verify-batch")]
9use alloc::vec::Vec;
10
11use group::Group;
12use group::cofactor::CofactorGroup;
13use group::ff::PrimeField;
14#[cfg(feature = "verify-batch")]
15use group::ff::PrimeFieldBits;
16use rand_core::{CryptoRng, RngCore};
17#[cfg(feature = "serde")]
18use serde::{Deserialize, Serialize};
19
20/// Signature produced by [`sign`].
21#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
22#[allow(non_snake_case)]
23#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
24pub struct Signature<Point, Scalar> {
25    pub R: Point,
26    pub s: Scalar,
27}
28
29impl<Point, Scalar> Signature<Point, Scalar> {
30    /// Get the inner `R` and `s` parameters of the signature.
31    pub const fn as_inner(&self) -> (&Point, &Scalar) {
32        (&self.R, &self.s)
33    }
34}
35
36/// Produce a [`Signature`] over `message`.
37///
38/// The group arithmetic uses `generator` to produce
39/// public keys.
40///
41/// ## Safety
42///
43/// The user must not provide a random number generator
44/// instance that is likely to produce nonce values that
45/// have already been instantiated for previous signatures.
46/// This poses the risk of exposing the underlying
47/// secret key.
48pub fn sign<Msg, Rng, HashToField, Point, Scalar>(
49    generator: &Point,
50    message: &Msg,
51    secret_key: &Scalar,
52    rng: Rng,
53    hash_to_field: HashToField,
54) -> Signature<Point, Scalar>
55where
56    Msg: ?Sized,
57    HashToField: FnOnce(&Point, &Point, &Msg) -> Scalar,
58    Rng: RngCore + CryptoRng,
59    Point: CofactorGroup + Group<Scalar = Scalar>,
60    Scalar: PrimeField,
61{
62    let nonce = Scalar::random(rng);
63
64    #[allow(non_snake_case)]
65    let R = *generator * nonce;
66
67    let challenge = hash_to_field(
68        &R,                         // commit to nonce
69        &(*generator * secret_key), // commit to public key
70        message,                    // commit to message
71    );
72
73    let s = (challenge * secret_key) + nonce;
74
75    Signature { R, s }
76}
77
78/// Verify a signature.
79///
80/// The group arithmetic uses `generator` to produce
81/// public keys.
82pub fn verify<Msg, HashToField, Point, Scalar>(
83    generator: &Point,
84    message: &Msg,
85    public_key: &Point,
86    Signature { R, s }: &Signature<Point, Scalar>,
87    hash_to_field: HashToField,
88) -> bool
89where
90    Msg: ?Sized,
91    HashToField: FnOnce(&Point, &Point, &Msg) -> Scalar,
92    Point: CofactorGroup + Group<Scalar = Scalar>,
93    Scalar: PrimeField,
94{
95    if public_key.is_small_order().into() || R.is_small_order().into() {
96        return false;
97    }
98
99    let challenge = hash_to_field(
100        R,          // commit to nonce
101        public_key, // commit to public key
102        message,    // commit to message
103    );
104
105    (*public_key * challenge + R - *generator * s)
106        .clear_cofactor()
107        .is_identity()
108        .into()
109}
110
111/// Verify a bach of signatures over the same message,
112/// from different signing keys.
113///
114/// The group arithmetic uses `generators[i]` to produce
115/// public keys. Each `usize` value `i` in `auths` indexes
116/// `generators[i]` (i.e. `generators[i] * sk == auths[i].1`).
117///
118/// Generators passed in are assumed to be trusted (not small order).
119#[cfg(feature = "verify-batch")]
120pub fn verify_batch<Msg, HashToField, Point, Scalar, Rng>(
121    generators: &[Point],
122    message: &Msg,
123    auths: &[(usize, Point, Signature<Point, Scalar>)],
124    mut hash_to_field: HashToField,
125    rng: Rng,
126) -> bool
127where
128    Msg: ?Sized,
129    Rng: RngCore + CryptoRng,
130    HashToField: FnMut(&Point, &Point, &Msg) -> Scalar,
131    Point: CofactorGroup + Group<Scalar = Scalar>,
132    Scalar: PrimeFieldBits,
133{
134    if auths.is_empty() {
135        return true;
136    }
137
138    let mut s_coeffs = alloc::vec![Scalar::ZERO; generators.len()];
139    let mut msm_buf = Vec::with_capacity(2 * auths.len() + generators.len());
140
141    let z_seed = Scalar::random(rng);
142    let mut z_pow = Scalar::ONE;
143
144    for (generator_index, public_key, Signature { R, s }) in auths {
145        if public_key.is_small_order().into() || R.is_small_order().into() {
146            return false;
147        }
148
149        let challenge = hash_to_field(
150            R,          // commit to nonce
151            public_key, // commit to public key
152            message,    // commit to message
153        );
154
155        let z = z_pow;
156        let neg_z = -z_pow;
157
158        z_pow *= z_seed;
159
160        let s_coeff = z * s;
161        s_coeffs[*generator_index] += s_coeff;
162
163        let r_term = (neg_z, *R);
164        let pk_term = (neg_z * challenge, *public_key);
165
166        msm_buf.push(r_term);
167        msm_buf.push(pk_term);
168    }
169
170    for (s_coeff, generator) in s_coeffs.into_iter().zip(generators) {
171        let s_term = (s_coeff, *generator);
172        msm_buf.push(s_term);
173    }
174
175    // verify: h * (\sum [s_acc]G - r_acc - pk_acc) == 0
176    multiexp::multiexp_vartime(&msm_buf)
177        .clear_cofactor()
178        .is_identity()
179        .into()
180}
181
182#[cfg(test)]
183mod tests {
184    use group::GroupEncoding;
185    use group::ff::{Field, FromUniformBytes};
186    use pasta_curves::pallas;
187    use rand_chacha::ChaCha20Rng;
188    use rand_core::SeedableRng;
189
190    use super::*;
191
192    #[test]
193    fn test_sign_verify() {
194        let mut csprng = test_csprng();
195
196        let message = b"eat shit";
197        let generator = pallas::Point::generator();
198        let secret_key = pallas::Scalar::random(&mut csprng);
199
200        let signature = sign(
201            &generator,
202            &message[..],
203            &secret_key,
204            &mut csprng,
205            hash_to_field,
206        );
207
208        assert!(verify(
209            &generator,
210            &message[..],
211            &(generator * secret_key),
212            &signature,
213            hash_to_field,
214        ));
215    }
216
217    #[test]
218    fn test_sign_verify_batch() {
219        let mut csprng = test_csprng();
220
221        let message = b"eat shit";
222        let generators = [pallas::Point::random(&mut csprng); 4];
223
224        let auths = (0..8)
225            .map(|i| {
226                let secret_key = pallas::Scalar::random(&mut csprng);
227                let public_key = generators[i >> 1] * secret_key;
228
229                let signature = sign(
230                    &generators[i >> 1],
231                    &message[..],
232                    &secret_key,
233                    &mut csprng,
234                    hash_to_field,
235                );
236
237                (i >> 1, public_key, signature)
238            })
239            .collect::<Vec<_>>();
240
241        assert!(verify_batch(
242            &generators,
243            &message[..],
244            &auths,
245            hash_to_field,
246            csprng,
247        ));
248    }
249
250    fn test_csprng() -> ChaCha20Rng {
251        ChaCha20Rng::from_seed([0xbe; 32])
252    }
253
254    fn hash_to_field(
255        nonce: &pallas::Point,
256        public_key: &pallas::Point,
257        message: &[u8],
258    ) -> pallas::Scalar {
259        let mut xof_stream = {
260            let mut hasher = blake3::Hasher::new();
261            hasher.update(&nonce.to_bytes());
262            hasher.update(&public_key.to_bytes());
263            hasher.update(message);
264            hasher.finalize_xof()
265        };
266
267        let mut output = [0u8; 64];
268        xof_stream.fill(&mut output);
269
270        pallas::Scalar::from_uniform_bytes(&output)
271    }
272}