1use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT as G, RistrettoPoint, Scalar};
8use rand::rngs::OsRng;
9use zeroize::Zeroizing;
10
11pub struct SchnorrKeypair {
12 secret: Zeroizing<Scalar>,
13 pub public: RistrettoPoint,
14}
15
16impl SchnorrKeypair {
17 pub fn generate() -> Self {
18 let secret = Scalar::random(&mut OsRng);
19 let public = secret * G;
20 Self { secret: Zeroizing::new(secret), public }
21 }
22
23 pub fn from_scalar_bytes(bytes: [u8; 32]) -> Option<Self> {
24 let secret = Scalar::from_canonical_bytes(bytes).into_option()?;
25 let public = secret * G;
26 Some(Self { secret: Zeroizing::new(secret), public })
27 }
28
29 pub fn public_bytes(&self) -> [u8; 32] {
30 self.public.compress().to_bytes()
31 }
32
33 pub fn prove(&self, msg: &[u8]) -> SchnorrProof {
35 let r = Scalar::random(&mut OsRng);
36 let r_point = r * G;
37 let c = challenge(&self.public, &r_point, msg);
38 let s = r + c * *self.secret;
39 SchnorrProof {
40 r_bytes: r_point.compress().to_bytes(),
41 s_bytes: s.to_bytes(),
42 }
43 }
44}
45
46#[derive(Clone, Debug)]
48pub struct SchnorrProof {
49 pub r_bytes: [u8; 32], pub s_bytes: [u8; 32], }
52
53pub fn schnorr_verify(pubkey_bytes: &[u8; 32], msg: &[u8], proof: &SchnorrProof) -> bool {
55 use curve25519_dalek::ristretto::CompressedRistretto;
56 let x_point = match CompressedRistretto(*pubkey_bytes).decompress() {
57 Some(p) => p,
58 None => return false,
59 };
60 let r_point = match CompressedRistretto(proof.r_bytes).decompress() {
61 Some(p) => p,
62 None => return false,
63 };
64 let s = match Scalar::from_canonical_bytes(proof.s_bytes).into_option() {
65 Some(s) => s,
66 None => return false,
67 };
68 let c = challenge(&x_point, &r_point, msg);
69 s * G == r_point + c * x_point
71}
72
73fn challenge(x_point: &RistrettoPoint, r_point: &RistrettoPoint, msg: &[u8]) -> Scalar {
74 let mut data = b"ling-schnorr-v1:".to_vec();
75 data.extend_from_slice(&x_point.compress().to_bytes());
76 data.extend_from_slice(&r_point.compress().to_bytes());
77 data.extend_from_slice(msg);
78 let h = blake3::hash(&data);
79 Scalar::from_bytes_mod_order(*h.as_bytes())
80}