mina_signer/signature.rs
1//! Mina signature structure and associated helpers
2//!
3//! This module provides the core signature functionality for the Mina protocol.
4//! Signatures in Mina are based on Schnorr signatures over elliptic curves,
5//! specifically using the Pallas curve and its associated scalar field.
6//!
7//! The signature scheme is compatible with the signature scheme used in the
8//! Mina protocol, and follows the standard Schnorr signature construction.
9//!
10//! # Examples
11//!
12//! ```rust
13//! use mina_signer::{Signature, BaseField, ScalarField};
14//!
15//! // Create a new signature from field elements
16//! let rx = BaseField::from(42u64);
17//! let s = ScalarField::from(123u64);
18//! let signature = Signature::new(rx, s);
19//!
20//! // Display signature as hexadecimal string
21//! println!("Signature: {}", signature);
22//! ```
23
24use crate::{BaseField, ScalarField};
25use ark_ff::One;
26use core::fmt;
27use o1_utils::FieldHelpers;
28
29/// A Schnorr signature for the Mina protocol.
30///
31/// This structure represents a cryptographic signature that consists of two
32/// components:
33/// - `rx`: The x-coordinate of the commitment point R, represented as a base
34/// field element
35/// - `s`: The signature scalar, computed as `s = k + r * private_key` where `k`
36/// is a random nonce
37///
38/// The signature follows the standard Schnorr signature scheme adapted for
39/// Mina's elliptic curve choice (Pallas curve). This ensures compatibility with
40/// the Mina blockchain's verification requirements.
41///
42/// # Security Properties
43///
44/// - **Unforgeability**: Cannot be forged without knowledge of the private key
45/// - **Non-malleability**: Signature cannot be modified to create a valid
46/// signature for a different message
47/// - **Deterministic**: Given the same message and private key, produces the
48/// same signature (when using deterministic nonce generation)
49///
50/// # Field Element Encoding
51///
52/// - `rx` is encoded as a base field element (typically Fp for Pallas curve)
53/// - `s` is encoded as a scalar field element (typically Fq for Pallas curve)
54///
55/// Both components are essential for signature verification and must be
56/// preserved with full precision during serialization and transmission.
57#[derive(Clone, Eq, fmt::Debug, PartialEq)]
58pub struct Signature {
59 /// The x-coordinate of the commitment point R from the Schnorr signature.
60 ///
61 /// This value is derived from the random nonce used during signing and
62 /// represents the x-coordinate of the point R = k * G, where k is the nonce
63 /// and G is the generator point of the elliptic curve.
64 ///
65 /// The rx component is crucial for signature verification as it's used to
66 /// reconstruct the commitment point during the verification process.
67 pub rx: BaseField,
68
69 /// The signature scalar component.
70 ///
71 /// This scalar is computed as `s = k + r * private_key (mod n)`, where:
72 /// - `k` is the random nonce used during signing
73 /// - `r` is the challenge derived from the commitment point and message
74 /// - `private_key` is the signer's secret key
75 /// - `n` is the order of the scalar field
76 ///
77 /// The scalar `s` proves knowledge of the private key without revealing it,
78 /// making it the core component that provides the signature's authenticity.
79 pub s: ScalarField,
80}
81
82impl Signature {
83 /// Creates a new signature from the given field elements.
84 ///
85 /// This constructor builds a signature from its two core components:
86 /// the commitment point's x-coordinate and the signature scalar.
87 ///
88 /// # Arguments
89 ///
90 /// * `rx` - The x-coordinate of the commitment point R, as a base field
91 /// element
92 /// * `s` - The signature scalar, as a scalar field element
93 ///
94 /// # Returns
95 ///
96 /// A new `Signature` instance containing the provided components.
97 ///
98 /// # Examples
99 ///
100 /// ```rust
101 /// use mina_signer::{Signature, BaseField, ScalarField};
102 ///
103 /// let rx = BaseField::from(123u64);
104 /// let s = ScalarField::from(456u64);
105 /// let signature = Signature::new(rx, s);
106 ///
107 /// assert_eq!(signature.rx, rx);
108 /// assert_eq!(signature.s, s);
109 /// ```
110 ///
111 /// # Security Note
112 ///
113 /// This constructor does not validate that the provided components form
114 /// a valid signature for any particular message or public key. It simply
115 /// creates the data structure. Signature validation must be performed
116 /// separately using appropriate verification functions.
117 pub fn new(rx: BaseField, s: ScalarField) -> Self {
118 Self { rx, s }
119 }
120
121 /// Create a dummy signature, whose components are both equal to one.
122 /// Use it with caution.
123 pub fn dummy() -> Self {
124 Self {
125 rx: BaseField::one(),
126 s: ScalarField::one(),
127 }
128 }
129}
130
131impl fmt::Display for Signature {
132 /// Formats the signature as a hexadecimal string.
133 ///
134 /// Returns the signature as concatenated hex-encoded bytes in big-endian
135 /// order: `rx || s`.
136 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137 let mut rx_bytes = self.rx.to_bytes();
138 let mut s_bytes = self.s.to_bytes();
139 rx_bytes.reverse();
140 s_bytes.reverse();
141
142 write!(f, "{}{}", hex::encode(rx_bytes), hex::encode(s_bytes))
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149 use alloc::{format, string::ToString};
150
151 #[test]
152 fn test_signature_encoding() {
153 // Test with small values to verify big-endian encoding and rx || s
154 // order
155 let rx = BaseField::from(4u64);
156 let s = ScalarField::from(42u64);
157 let signature = Signature::new(rx, s);
158
159 let encoded = signature.to_string();
160
161 // Should always be 128 hex characters (64 bytes total)
162 assert_eq!(
163 encoded.len(),
164 128,
165 "Signature encoding should be exactly 128 hex characters"
166 );
167
168 // Split into rx and s parts (64 chars each)
169 let (rx_hex, s_hex) = encoded.split_at(64);
170
171 // Verify rx part ends with "04" (4 in big-endian hex, padded to 32
172 // bytes)
173 assert!(
174 rx_hex.ends_with("04"),
175 "rx component should end with '04' for value 4"
176 );
177
178 // Verify s part ends with "2a" (42 in big-endian hex, padded to 32
179 // bytes)
180 assert!(
181 s_hex.ends_with("2a"),
182 "s component should end with '2a' for value 42"
183 );
184
185 // Test with another small value to confirm pattern
186 let rx2 = BaseField::from(255u64); // 0xff
187 let s2 = ScalarField::from(256u64); // 0x100
188 let signature2 = Signature::new(rx2, s2);
189 let encoded2 = signature2.to_string();
190
191 let (rx2_hex, s2_hex) = encoded2.split_at(64);
192 assert!(
193 rx2_hex.ends_with("ff"),
194 "rx should end with 'ff' for value 255"
195 );
196 assert!(
197 s2_hex.ends_with("0100"),
198 "s should end with '0100' for value 256"
199 );
200
201 // Verify the order: rx comes first, then s
202 assert_ne!(rx_hex, s_hex, "rx and s components should be different");
203 assert_eq!(
204 encoded,
205 format!("{}{}", rx_hex, s_hex),
206 "Encoding should be rx followed by s"
207 );
208 }
209}