aztec_crypto/
schnorr.rs

1//! Schnorr signature scheme on the Grumpkin curve.
2//!
3//! Implements the same Schnorr construction used by the Noir schnorr library:
4//! - Pedersen hash for binding the nonce point R to the public key
5//! - Blake2s-256 for the final challenge hash
6//! - Deterministic nonce via Blake2s(private_key || message)
7//! - Signature = (s, e) where s and e are 32-byte scalars
8
9use aztec_core::grumpkin;
10use aztec_core::types::{Fq, Fr, GrumpkinScalar, Point};
11use blake2::{Blake2s256, Digest};
12
13use crate::pedersen::pedersen_hash;
14
15/// A Schnorr signature (s, e) on the Grumpkin curve.
16#[derive(Clone, Debug, PartialEq, Eq)]
17pub struct SchnorrSignature {
18    /// The s component (32 bytes).
19    pub s: [u8; 32],
20    /// The e component / challenge (32 bytes).
21    pub e: [u8; 32],
22}
23
24impl SchnorrSignature {
25    /// Serialize to 64 bytes: `s || e`.
26    pub fn to_bytes(&self) -> [u8; 64] {
27        let mut out = [0u8; 64];
28        out[..32].copy_from_slice(&self.s);
29        out[32..].copy_from_slice(&self.e);
30        out
31    }
32
33    /// Deserialize from 64 bytes: `s || e`.
34    pub fn from_bytes(bytes: &[u8; 64]) -> Self {
35        let mut s = [0u8; 32];
36        let mut e = [0u8; 32];
37        s.copy_from_slice(&bytes[..32]);
38        e.copy_from_slice(&bytes[32..]);
39        Self { s, e }
40    }
41
42    /// Convert signature bytes to field elements (one Fr per byte).
43    ///
44    /// This matches the TS SDK convention where auth witness fields
45    /// are the individual signature bytes as field elements.
46    pub fn to_fields(&self) -> Vec<Fr> {
47        self.to_bytes()
48            .iter()
49            .map(|&b| Fr::from(b as u64))
50            .collect()
51    }
52}
53
54/// Generate a deterministic nonce for Schnorr signing.
55///
56/// `k = Blake2s(private_key_bytes || message_bytes)` reduced to a Grumpkin scalar.
57fn generate_nonce(private_key: &GrumpkinScalar, message: &Fr) -> GrumpkinScalar {
58    let mut hasher = Blake2s256::new();
59    hasher.update(private_key.to_be_bytes());
60    hasher.update(message.to_be_bytes());
61    let hash = hasher.finalize();
62
63    // Use 32-byte hash as a Grumpkin scalar (reduced mod order).
64    // For better uniformity we'd want 64 bytes, but this matches
65    // the barretenberg Schnorr nonce derivation pattern.
66    GrumpkinScalar::from_be_bytes_mod_order(&hash)
67}
68
69/// Compute the Schnorr challenge matching the Noir schnorr library:
70///
71/// ```text
72/// pedersen_h = pedersen_hash([R.x, public_key.x, public_key.y])
73/// e = blake2s(pedersen_h.to_be_bytes() || message)
74/// ```
75fn compute_challenge(r: &Point, public_key: &Point, message: &[u8]) -> [u8; 32] {
76    // Pedersen hash binds the nonce R to the signer's public key
77    let pedersen_h = pedersen_hash(&[r.x, public_key.x, public_key.y]);
78
79    let mut hasher = Blake2s256::new();
80    hasher.update(pedersen_h.to_be_bytes());
81    hasher.update(message);
82    let hash = hasher.finalize();
83    let mut out = [0u8; 32];
84    out.copy_from_slice(&hash);
85    out
86}
87
88/// Sign a message with a Grumpkin private key using Schnorr.
89///
90/// The signing algorithm matches the Noir schnorr library:
91/// 1. `k = Blake2s(private_key || message)` (deterministic nonce)
92/// 2. `R = k * G`
93/// 3. `public_key = private_key * G`
94/// 4. `e = Blake2s(pedersen_hash([R.x, public_key.x, public_key.y]) || message)`
95/// 5. `s = k - private_key * e` (mod Grumpkin scalar order)
96///
97/// Returns a `SchnorrSignature` containing `(s, e)`.
98pub fn schnorr_sign(private_key: &GrumpkinScalar, message: &Fr) -> SchnorrSignature {
99    let g = grumpkin::generator();
100    let public_key = grumpkin::scalar_mul(private_key, &g);
101
102    // 1. Deterministic nonce
103    let k = generate_nonce(private_key, message);
104
105    // 2. R = k * G
106    let r = grumpkin::scalar_mul(&k, &g);
107
108    // 3. Challenge: e = Blake2s(pedersen_hash([R.x, pubkey.x, pubkey.y]) || message_bytes)
109    let e_bytes = compute_challenge(&r, &public_key, &message.to_be_bytes());
110    let e_scalar = GrumpkinScalar::from_be_bytes_mod_order(&e_bytes);
111
112    // 4. s = k - private_key * e (mod Grumpkin scalar order)
113    let s_scalar = Fq(k.0 - private_key.0 * e_scalar.0);
114
115    SchnorrSignature {
116        s: s_scalar.to_be_bytes(),
117        e: e_bytes,
118    }
119}
120
121/// Verify a Schnorr signature against a public key and message hash.
122///
123/// The verification algorithm matches the Noir schnorr library:
124/// 1. `R' = s * G + e * public_key`
125/// 2. `e' = Blake2s(pedersen_hash([R'.x, public_key.x, public_key.y]) || message)`
126/// 3. Accept if `e == e'`
127pub fn schnorr_verify(public_key: &Point, message: &Fr, signature: &SchnorrSignature) -> bool {
128    let g = grumpkin::generator();
129
130    let s_scalar = GrumpkinScalar::from_be_bytes_mod_order(&signature.s);
131    let e_scalar = GrumpkinScalar::from_be_bytes_mod_order(&signature.e);
132
133    // R' = s * G + e * public_key
134    let s_g = grumpkin::scalar_mul(&s_scalar, &g);
135    let e_pk = grumpkin::scalar_mul(&e_scalar, public_key);
136    let r_prime = grumpkin::point_add(&s_g, &e_pk);
137
138    // e' = Blake2s(pedersen_hash([R'.x, public_key.x, public_key.y]) || message)
139    let e_prime = compute_challenge(&r_prime, public_key, &message.to_be_bytes());
140
141    signature.e == e_prime
142}
143
144#[cfg(test)]
145#[allow(clippy::expect_used)]
146mod tests {
147    use super::*;
148    use crate::keys::{derive_public_key_from_secret_key, derive_signing_key};
149
150    #[test]
151    fn sign_and_verify_roundtrip() {
152        let secret = Fr::from(12345u64);
153        let signing_key = derive_signing_key(&secret);
154        let public_key = derive_public_key_from_secret_key(&signing_key);
155
156        let message = Fr::from(42u64);
157        let sig = schnorr_sign(&signing_key, &message);
158
159        assert!(schnorr_verify(&public_key, &message, &sig));
160    }
161
162    #[test]
163    fn wrong_message_fails_verification() {
164        let secret = Fr::from(12345u64);
165        let signing_key = derive_signing_key(&secret);
166        let public_key = derive_public_key_from_secret_key(&signing_key);
167
168        let message = Fr::from(42u64);
169        let sig = schnorr_sign(&signing_key, &message);
170
171        let wrong_message = Fr::from(43u64);
172        assert!(!schnorr_verify(&public_key, &wrong_message, &sig));
173    }
174
175    #[test]
176    fn wrong_key_fails_verification() {
177        let secret = Fr::from(12345u64);
178        let signing_key = derive_signing_key(&secret);
179
180        let other_secret = Fr::from(99999u64);
181        let other_signing_key = derive_signing_key(&other_secret);
182        let other_public_key = derive_public_key_from_secret_key(&other_signing_key);
183
184        let message = Fr::from(42u64);
185        let sig = schnorr_sign(&signing_key, &message);
186
187        assert!(!schnorr_verify(&other_public_key, &message, &sig));
188    }
189
190    #[test]
191    fn signing_is_deterministic() {
192        let secret = Fr::from(8923u64);
193        let signing_key = derive_signing_key(&secret);
194
195        let message = Fr::from(1000u64);
196        let sig1 = schnorr_sign(&signing_key, &message);
197        let sig2 = schnorr_sign(&signing_key, &message);
198
199        assert_eq!(sig1, sig2);
200    }
201
202    #[test]
203    fn different_messages_produce_different_signatures() {
204        let secret = Fr::from(8923u64);
205        let signing_key = derive_signing_key(&secret);
206
207        let sig1 = schnorr_sign(&signing_key, &Fr::from(1u64));
208        let sig2 = schnorr_sign(&signing_key, &Fr::from(2u64));
209
210        assert_ne!(sig1, sig2);
211    }
212
213    #[test]
214    fn signature_serialization_roundtrip() {
215        let secret = Fr::from(42u64);
216        let signing_key = derive_signing_key(&secret);
217
218        let sig = schnorr_sign(&signing_key, &Fr::from(100u64));
219        let bytes = sig.to_bytes();
220        let recovered = SchnorrSignature::from_bytes(&bytes);
221
222        assert_eq!(sig, recovered);
223    }
224
225    #[test]
226    fn to_fields_produces_64_elements() {
227        let secret = Fr::from(42u64);
228        let signing_key = derive_signing_key(&secret);
229
230        let sig = schnorr_sign(&signing_key, &Fr::from(100u64));
231        let fields = sig.to_fields();
232
233        assert_eq!(fields.len(), 64);
234        // Each field should be a byte value (0..256)
235        for field in &fields {
236            let val = field.to_usize();
237            assert!(val < 256);
238        }
239    }
240
241    #[test]
242    fn verify_with_realistic_key_derivation() {
243        // Simulate the full flow: secret_key -> signing_key -> public_key -> sign -> verify
244        let secret_key = Fr::from(8923u64);
245        let signing_key = derive_signing_key(&secret_key);
246        let public_key = derive_public_key_from_secret_key(&signing_key);
247
248        // Sign a realistic-looking message hash
249        let message_hash =
250            Fr::from_hex("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef")
251                .expect("valid hex");
252        let sig = schnorr_sign(&signing_key, &message_hash);
253
254        assert!(schnorr_verify(&public_key, &message_hash, &sig));
255    }
256}