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::cofactor::CofactorGroup;
7use group::ff::PrimeField;
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<Msg, Rng, HashToField, Point, Scalar>(
41    generator: &Point,
42    message: &Msg,
43    secret_key: &Scalar,
44    rng: Rng,
45    hash_to_field: HashToField,
46) -> Signature<Point, Scalar>
47where
48    Msg: ?Sized,
49    HashToField: FnOnce(&Point, &Point, &Msg) -> Scalar,
50    Rng: RngCore + CryptoRng,
51    Point: CofactorGroup + Group<Scalar = Scalar>,
52    Scalar: PrimeField,
53{
54    let nonce = Scalar::random(rng);
55
56    #[allow(non_snake_case)]
57    let R = *generator * nonce;
58
59    let challenge = hash_to_field(
60        &R,                         // commit to nonce
61        &(*generator * secret_key), // commit to public key
62        message,                    // commit to message,
63    );
64
65    let s = (challenge * secret_key) + nonce;
66
67    Signature { R, s }
68}
69
70/// Verify a signature.
71///
72/// The group arithmetic uses `generator` to produce
73/// public keys.
74pub fn verify<Msg, HashToField, Point, Scalar>(
75    generator: &Point,
76    message: &Msg,
77    public_key: &Point,
78    Signature { R, s }: &Signature<Point, Scalar>,
79    hash_to_field: HashToField,
80) -> bool
81where
82    Msg: ?Sized,
83    HashToField: FnOnce(&Point, &Point, &Msg) -> Scalar,
84    Point: CofactorGroup + Group<Scalar = Scalar>,
85    Scalar: PrimeField,
86{
87    if (public_key.is_small_order() | R.is_small_order()).into() {
88        return false;
89    }
90
91    let challenge = hash_to_field(
92        R,          // commit to nonce
93        public_key, // commit to public key
94        message,    // commit to message,
95    );
96
97    (*public_key * challenge + R - *generator * s)
98        .clear_cofactor()
99        .is_identity()
100        .into()
101}
102
103#[cfg(test)]
104mod tests {
105    use group::GroupEncoding;
106    use group::ff::{Field, FromUniformBytes};
107    use pasta_curves::pallas;
108    use rand_chacha::ChaCha20Rng;
109    use rand_core::SeedableRng;
110
111    use super::*;
112
113    #[test]
114    fn test_sign_verify() {
115        let mut csprng = test_csprng();
116
117        let message = b"eat shit";
118        let generator = pallas::Point::generator();
119        let secret_key = pallas::Scalar::random(&mut csprng);
120
121        let signature = sign(
122            &generator,
123            &message[..],
124            &secret_key,
125            &mut csprng,
126            hash_to_field,
127        );
128
129        assert!(verify(
130            &generator,
131            &message[..],
132            &(generator * secret_key),
133            &signature,
134            hash_to_field,
135        ));
136    }
137
138    fn test_csprng() -> ChaCha20Rng {
139        ChaCha20Rng::from_seed([0xbe; 32])
140    }
141
142    fn hash_to_field(
143        nonce: &pallas::Point,
144        public_key: &pallas::Point,
145        message: &[u8],
146    ) -> pallas::Scalar {
147        let mut xof_stream = {
148            let mut hasher = blake3::Hasher::new();
149            hasher.update(&nonce.to_bytes());
150            hasher.update(&public_key.to_bytes());
151            hasher.update(message);
152            hasher.finalize_xof()
153        };
154
155        let mut output = [0u8; 64];
156        xof_stream.fill(&mut output);
157
158        pallas::Scalar::from_uniform_bytes(&output)
159    }
160}