1#![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#[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: 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, &(*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: PrimeGroup + Group<Scalar = Scalar>,
85 Scalar: PrimeField,
86{
87 let challenge = hash_to_field(
88 R, public_key, message, );
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}