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