1#![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#[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 pub const fn as_inner(&self) -> (&Point, &Scalar) {
24 (&self.R, &self.s)
25 }
26}
27
28pub 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, &(*generator * secret_key), message, );
64
65 let s = (challenge * secret_key) + nonce;
66
67 Signature { R, s }
68}
69
70pub 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, public_key, message, );
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}