1use getrandom::SysRng;
8use p256::elliptic_curve::PrimeField;
9use p256::elliptic_curve::rand_core::{Rng, UnwrapErr};
10use p256::elliptic_curve::sec1::{FromSec1Point, ToSec1Point};
11use p256::{AffinePoint, ProjectivePoint, Scalar};
12use serde::{Deserialize, Serialize};
13use sha2::{Digest, Sha256};
14
15#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
17pub struct SchnorrProof {
18 pub r_hex: String,
20 pub z_hex: String,
22}
23
24#[derive(Debug, thiserror::Error)]
26pub enum SchnorrError {
27 #[error("point decoding failed")]
29 InvalidPoint,
30 #[error("scalar decoding failed")]
32 InvalidScalar,
33 #[error("hex decoding failed: {0}")]
35 HexError(String),
36}
37
38pub fn prove(secret: &Scalar, public: &AffinePoint, message: &[u8]) -> SchnorrProof {
40 use p256::elliptic_curve::Field;
41 let mut k_bytes = [0u8; 32];
42 UnwrapErr(SysRng).fill_bytes(&mut k_bytes);
43 let k_fb = p256::FieldBytes::from(k_bytes);
44 let k_ct = Scalar::from_repr(k_fb);
45 let k = Option::<Scalar>::from(k_ct).unwrap_or_else(|| Scalar::random(&mut UnwrapErr(SysRng)));
46
47 let r_point = ProjectivePoint::GENERATOR * k;
48 let r_affine = r_point.to_affine();
49
50 let c = fiat_shamir_challenge(public, &r_affine, message);
51 let z = k + secret * &c;
52
53 let r_encoded = r_affine.to_sec1_point(true);
54 let z_bytes: [u8; 32] = z.to_repr().into();
55
56 SchnorrProof {
57 r_hex: hex::encode(r_encoded.as_bytes()),
58 z_hex: hex::encode(z_bytes),
59 }
60}
61
62pub fn verify(
64 proof: &SchnorrProof,
65 public: &AffinePoint,
66 message: &[u8],
67) -> Result<bool, SchnorrError> {
68 let r_bytes = hex::decode(&proof.r_hex).map_err(|e| SchnorrError::HexError(e.to_string()))?;
69 let r_point = decode_point(&r_bytes)?;
70 let z_bytes = hex::decode(&proof.z_hex).map_err(|e| SchnorrError::HexError(e.to_string()))?;
71 if z_bytes.len() != 32 {
72 return Err(SchnorrError::InvalidScalar);
73 }
74 let z_arr: [u8; 32] = z_bytes.as_slice().try_into().unwrap();
75 let z_scalar = decode_scalar(&z_arr)?;
76
77 let c = fiat_shamir_challenge(public, &r_point, message);
78
79 let lhs = ProjectivePoint::GENERATOR * z_scalar;
80 let rhs = ProjectivePoint::from(r_point) + ProjectivePoint::from(*public) * c;
81
82 Ok(lhs == rhs)
83}
84
85fn fiat_shamir_challenge(public: &AffinePoint, r: &AffinePoint, message: &[u8]) -> Scalar {
86 let public_encoded = public.to_sec1_point(true);
87 let r_encoded = r.to_sec1_point(true);
88
89 let mut hasher = Sha256::new();
90 hasher.update(b"confium-schnorr-v1");
91 hasher.update(public_encoded.as_bytes());
92 hasher.update(r_encoded.as_bytes());
93 hasher.update(message);
94 let hash = hasher.finalize();
95
96 let mut fb: [u8; 32] = hash.into();
97 loop {
100 if let Some(s) = Option::<Scalar>::from(Scalar::from_repr(p256::FieldBytes::from(fb))) {
101 return s;
102 }
103 let mut h = Sha256::new();
104 h.update(b"confium-scalar-reduce-v1");
105 h.update(fb);
106 fb = h.finalize().into();
107 }
108}
109
110fn decode_point(bytes: &[u8]) -> Result<AffinePoint, SchnorrError> {
111 let encoded = p256::elliptic_curve::sec1::Sec1Point::<p256::NistP256>::from_bytes(bytes)
112 .map_err(|_| SchnorrError::InvalidPoint)?;
113 Option::<AffinePoint>::from(AffinePoint::from_sec1_point(&encoded))
114 .ok_or(SchnorrError::InvalidPoint)
115}
116
117fn decode_scalar(bytes: &[u8; 32]) -> Result<Scalar, SchnorrError> {
118 let fb = p256::FieldBytes::from(*bytes);
119 let ct = Scalar::from_repr(fb);
120 Option::<Scalar>::from(ct).ok_or(SchnorrError::InvalidScalar)
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126 use p256::elliptic_curve::Field as _;
127
128 fn random_keypair() -> (Scalar, AffinePoint) {
129 let mut buf = [0u8; 32];
130 UnwrapErr(SysRng).fill_bytes(&mut buf);
131 let fb = p256::FieldBytes::from(buf);
132 let ct = Scalar::from_repr(fb);
133 let secret =
134 Option::<Scalar>::from(ct).unwrap_or_else(|| Scalar::random(&mut UnwrapErr(SysRng)));
135 let public = (ProjectivePoint::GENERATOR * secret).to_affine();
136 (secret, public)
137 }
138
139 #[test]
140 fn valid_proof_verifies() {
141 let (secret, public) = random_keypair();
142 let proof = prove(&secret, &public, b"test message");
143 assert!(verify(&proof, &public, b"test message").unwrap());
144 }
145
146 #[test]
147 fn wrong_public_key_rejected() {
148 let (secret, correct_public) = random_keypair();
149 let (_, wrong_public) = random_keypair();
150 let proof = prove(&secret, &correct_public, b"msg");
151 assert!(verify(&proof, &correct_public, b"msg").unwrap());
152 assert!(!verify(&proof, &wrong_public, b"msg").unwrap());
153 }
154
155 #[test]
156 fn different_messages_produce_different_proofs() {
157 let (secret, public) = random_keypair();
158 let p1 = prove(&secret, &public, b"message A");
159 let p2 = prove(&secret, &public, b"message B");
160 assert_ne!(p1.r_hex, p2.r_hex);
161 }
162
163 #[test]
164 fn tampered_z_rejected() {
165 let (secret, public) = random_keypair();
166 let mut proof = prove(&secret, &public, b"msg");
167 let mut z_bytes = hex::decode(&proof.z_hex).unwrap();
168 z_bytes[0] ^= 0xFF;
169 proof.z_hex = hex::encode(&z_bytes);
170 assert!(!verify(&proof, &public, b"msg").unwrap());
171 }
172
173 #[test]
174 fn proof_serializes() {
175 let (secret, public) = random_keypair();
176 let proof = prove(&secret, &public, b"msg");
177 let json = serde_json::to_string(&proof).unwrap();
178 let recovered: SchnorrProof = serde_json::from_str(&json).unwrap();
179 assert_eq!(proof, recovered);
180 }
181
182 #[test]
183 fn empty_message_works() {
184 let (secret, public) = random_keypair();
185 let proof = prove(&secret, &public, b"");
186 assert!(verify(&proof, &public, b"").unwrap());
187 }
188
189 #[test]
190 fn proof_is_non_deterministic() {
191 let (secret, public) = random_keypair();
192 let p1 = prove(&secret, &public, b"same message");
193 let p2 = prove(&secret, &public, b"same message");
194 assert_ne!(p1.r_hex, p2.r_hex);
195 assert!(verify(&p1, &public, b"same message").unwrap());
196 assert!(verify(&p2, &public, b"same message").unwrap());
197 }
198}
199
200#[cfg(test)]
201mod adversarial_tests {
202 use super::*;
206 use p256::elliptic_curve::Field as _;
207
208 fn keypair() -> (Scalar, AffinePoint) {
209 let sk = Scalar::random(&mut UnwrapErr(SysRng));
210 let pk = (ProjectivePoint::GENERATOR * sk).to_affine();
211 (sk, pk)
212 }
213
214 #[test]
215 fn rejects_signature_for_a_different_message() {
216 let (sk, pk) = keypair();
217 let sig = prove(&sk, &pk, b"original message");
218 assert!(!verify(&sig, &pk, b"other message").unwrap());
220 }
221
222 #[test]
223 fn rejects_tampered_signature() {
224 let (sk, pk) = keypair();
225 let sig = prove(&sk, &pk, b"message");
226 let mut z = hex::decode(&sig.z_hex).unwrap();
228 z[0] ^= 0x01;
229 let mut forged = sig.clone();
230 forged.z_hex = z.iter().map(|b| format!("{b:02x}")).collect();
231 assert!(!verify(&forged, &pk, b"message").unwrap());
232 }
233
234 #[test]
235 fn rejects_signature_under_a_wrong_key() {
236 let (sk, _) = keypair();
237 let (_, other_pk) = keypair();
238 let sig = prove(&sk, &other_pk, b"message"); assert!(!verify(&sig, &other_pk, b"message").unwrap());
240 }
241}