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}