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
10#[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
18pub 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, &(*generator * secret_key), message, );
53
54 let s = (challenge * secret_key) + nonce;
55
56 Signature { R, s }
57}
58
59pub 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, public_key, message, );
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}