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<Rng, HashToField, Point, Scalar>(
41 generator: &Point,
42 message: &[u8],
43 secret_key: &Scalar,
44 rng: Rng,
45 hash_to_field: HashToField,
46) -> Signature<Point, Scalar>
47where
48 HashToField: FnOnce(&Point, &Point, &[u8]) -> Scalar,
49 Rng: RngCore + CryptoRng,
50 Point: PrimeGroup + Group<Scalar = Scalar>,
51 Scalar: PrimeField,
52{
53 let nonce = Scalar::random(rng);
54
55 #[allow(non_snake_case)]
56 let R = *generator * nonce;
57
58 let challenge = hash_to_field(
59 &R, &(*generator * secret_key), message, );
63
64 let s = (challenge * secret_key) + nonce;
65
66 Signature { R, s }
67}
68
69pub fn verify<HashToField, Point, Scalar>(
74 generator: &Point,
75 message: &[u8],
76 public_key: &Point,
77 Signature { R, s }: &Signature<Point, Scalar>,
78 hash_to_field: HashToField,
79) -> bool
80where
81 HashToField: FnOnce(&Point, &Point, &[u8]) -> Scalar,
82 Point: PrimeGroup + Group<Scalar = Scalar>,
83 Scalar: PrimeField,
84{
85 let challenge = hash_to_field(
86 R, public_key, message, );
90
91 (*public_key * challenge + R - *generator * s)
92 .is_identity()
93 .into()
94}
95
96#[cfg(test)]
97mod tests {
98 use group::GroupEncoding;
99 use group::ff::{Field, FromUniformBytes};
100 use pasta_curves::pallas;
101 use rand_chacha::ChaCha20Rng;
102 use rand_core::SeedableRng;
103
104 use super::*;
105
106 #[test]
107 fn test_sign_verify() {
108 let mut csprng = test_csprng();
109
110 let message = b"eat shit";
111 let generator = pallas::Point::generator();
112 let secret_key = pallas::Scalar::random(&mut csprng);
113
114 let signature = sign(&generator, message, &secret_key, &mut csprng, hash_to_field);
115
116 assert!(verify(
117 &generator,
118 message,
119 &(generator * secret_key),
120 &signature,
121 hash_to_field,
122 ));
123 }
124
125 fn test_csprng() -> ChaCha20Rng {
126 ChaCha20Rng::from_seed([0xbe; 32])
127 }
128
129 fn hash_to_field(
130 nonce: &pallas::Point,
131 public_key: &pallas::Point,
132 message: &[u8],
133 ) -> pallas::Scalar {
134 let mut xof_stream = {
135 let mut hasher = blake3::Hasher::new();
136 hasher.update(&nonce.to_bytes());
137 hasher.update(&public_key.to_bytes());
138 hasher.update(message);
139 hasher.finalize_xof()
140 };
141
142 let mut output = [0u8; 64];
143 xof_stream.fill(&mut output);
144
145 pallas::Scalar::from_uniform_bytes(&output)
146 }
147}