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<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: PrimeGroup + 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: PrimeGroup + Group<Scalar = Scalar>,
85    Scalar: PrimeField,
86{
87    let challenge = hash_to_field(
88        R,          // commit to nonce
89        public_key, // commit to public key
90        message,    // commit to message,
91    );
92
93    (*public_key * challenge + R - *generator * s)
94        .is_identity()
95        .into()
96}
97
98#[cfg(test)]
99mod tests {
100    use group::GroupEncoding;
101    use group::ff::{Field, FromUniformBytes};
102    use pasta_curves::pallas;
103    use rand_chacha::ChaCha20Rng;
104    use rand_core::SeedableRng;
105
106    use super::*;
107
108    #[test]
109    fn test_sign_verify() {
110        let mut csprng = test_csprng();
111
112        let message = b"eat shit";
113        let generator = pallas::Point::generator();
114        let secret_key = pallas::Scalar::random(&mut csprng);
115
116        let signature = sign(
117            &generator,
118            &message[..],
119            &secret_key,
120            &mut csprng,
121            hash_to_field,
122        );
123
124        assert!(verify(
125            &generator,
126            &message[..],
127            &(generator * secret_key),
128            &signature,
129            hash_to_field,
130        ));
131    }
132
133    fn test_csprng() -> ChaCha20Rng {
134        ChaCha20Rng::from_seed([0xbe; 32])
135    }
136
137    fn hash_to_field(
138        nonce: &pallas::Point,
139        public_key: &pallas::Point,
140        message: &[u8],
141    ) -> pallas::Scalar {
142        let mut xof_stream = {
143            let mut hasher = blake3::Hasher::new();
144            hasher.update(&nonce.to_bytes());
145            hasher.update(&public_key.to_bytes());
146            hasher.update(message);
147            hasher.finalize_xof()
148        };
149
150        let mut output = [0u8; 64];
151        xof_stream.fill(&mut output);
152
153        pallas::Scalar::from_uniform_bytes(&output)
154    }
155}