1#![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#[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 pub const fn as_inner(&self) -> (&Point, &Scalar) {
32 (&self.R, &self.s)
33 }
34}
35
36pub 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, &(*generator * secret_key), message, );
72
73 let s = (challenge * secret_key) + nonce;
74
75 Signature { R, s }
76}
77
78pub 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, public_key, message, );
104
105 (*public_key * challenge + R - *generator * s)
106 .clear_cofactor()
107 .is_identity()
108 .into()
109}
110
111#[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, public_key, message, );
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 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}