miden_crypto/dsa/ecdsa_k256_keccak/
mod.rs

1//! ECDSA (Elliptic Curve Digital Signature Algorithm) signature implementation over secp256k1
2//! curve using Keccak to hash the messages when signing.
3
4use alloc::{string::ToString, vec::Vec};
5
6use k256::{
7    ecdh::diffie_hellman,
8    ecdsa::{RecoveryId, SigningKey, VerifyingKey, signature::hazmat::PrehashVerifier},
9};
10use miden_crypto_derive::{SilentDebug, SilentDisplay};
11use rand::{CryptoRng, RngCore};
12use thiserror::Error;
13
14use crate::{
15    Felt, SequentialCommit, Word,
16    ecdh::k256::{EphemeralPublicKey, SharedSecret},
17    utils::{
18        ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
19        bytes_to_packed_u32_elements,
20    },
21    zeroize::{Zeroize, ZeroizeOnDrop},
22};
23
24#[cfg(test)]
25mod tests;
26
27// CONSTANTS
28// ================================================================================================
29
30/// Length of secret key in bytes
31const SECRET_KEY_BYTES: usize = 32;
32/// Length of public key in bytes when using compressed format encoding
33pub(crate) const PUBLIC_KEY_BYTES: usize = 33;
34/// Length of signature in bytes using our custom serialization
35const SIGNATURE_BYTES: usize = 66;
36/// Length of signature in bytes using standard serialization i.e., `SEC1`
37const SIGNATURE_STANDARD_BYTES: usize = 64;
38/// Length of scalars for the `secp256k1` curve
39const SCALARS_SIZE_BYTES: usize = 32;
40
41// SECRET KEY
42// ================================================================================================
43
44/// Secret key for ECDSA signature verification over secp256k1 curve.
45#[derive(Clone, SilentDebug, SilentDisplay)]
46pub struct SecretKey {
47    inner: SigningKey,
48}
49
50impl SecretKey {
51    /// Generates a new random secret key using the OS random number generator.
52    #[cfg(feature = "std")]
53    #[allow(clippy::new_without_default)]
54    pub fn new() -> Self {
55        let mut rng = rand::rng();
56
57        Self::with_rng(&mut rng)
58    }
59
60    /// Generates a new secret key using the provided random number generator.
61    pub fn with_rng<R: CryptoRng + RngCore>(rng: &mut R) -> Self {
62        // we use a seedable CSPRNG and seed it with `rng`
63        // this is a work around the fact that the version of the `rand` dependency in our crate
64        // is different than the one used in the `k256` one. This solution will no longer be needed
65        // once `k256` gets a new release with a version of the `rand` dependency matching ours
66        use k256::elliptic_curve::rand_core::SeedableRng;
67        let mut seed = [0_u8; 32];
68        rand::RngCore::fill_bytes(rng, &mut seed);
69        let mut rng = rand_hc::Hc128Rng::from_seed(seed);
70
71        let signing_key = SigningKey::random(&mut rng);
72
73        // Zeroize the seed to prevent leaking secret material
74        seed.zeroize();
75
76        Self { inner: signing_key }
77    }
78
79    /// Gets the corresponding public key for this secret key.
80    pub fn public_key(&self) -> PublicKey {
81        let verifying_key = self.inner.verifying_key();
82        PublicKey { inner: *verifying_key }
83    }
84
85    /// Signs a message (represented as a Word) with this secret key.
86    pub fn sign(&self, message: Word) -> Signature {
87        let message_digest = hash_message(message);
88        self.sign_prehash(message_digest)
89    }
90
91    /// Signs a pre-hashed message with this secret key.
92    pub fn sign_prehash(&self, message_digest: [u8; 32]) -> Signature {
93        let (signature_inner, recovery_id) = self
94            .inner
95            .sign_prehash_recoverable(&message_digest)
96            .expect("failed to generate signature");
97
98        let (r, s) = signature_inner.split_scalars();
99
100        Signature {
101            r: r.to_bytes().into(),
102            s: s.to_bytes().into(),
103            v: recovery_id.into(),
104        }
105    }
106
107    /// Computes a Diffie-Hellman shared secret from this secret key and the ephemeral public key
108    /// generated by the other party.
109    pub fn get_shared_secret(&self, pk_e: EphemeralPublicKey) -> SharedSecret {
110        let shared_secret_inner = diffie_hellman(self.inner.as_nonzero_scalar(), pk_e.as_affine());
111
112        SharedSecret::new(shared_secret_inner)
113    }
114}
115
116// SAFETY: The inner `k256::ecdsa::SigningKey` already implements `ZeroizeOnDrop`,
117// which ensures that the secret key material is securely zeroized when dropped.
118impl ZeroizeOnDrop for SecretKey {}
119
120impl PartialEq for SecretKey {
121    fn eq(&self, other: &Self) -> bool {
122        use subtle::ConstantTimeEq;
123        self.to_bytes().ct_eq(&other.to_bytes()).into()
124    }
125}
126
127impl Eq for SecretKey {}
128
129// PUBLIC KEY
130// ================================================================================================
131
132/// Public key for ECDSA signature verification over secp256k1 curve.
133#[derive(Debug, Clone, PartialEq, Eq)]
134pub struct PublicKey {
135    pub(crate) inner: VerifyingKey,
136}
137
138impl PublicKey {
139    /// Returns a commitment to the public key using the RPO256 hash function.
140    ///
141    /// The commitment is computed by first converting the public key to field elements (4 bytes
142    /// per element), and then computing a sequential hash of the elements.
143    pub fn to_commitment(&self) -> Word {
144        <Self as SequentialCommit>::to_commitment(self)
145    }
146
147    /// Verifies a signature against this public key and message.
148    pub fn verify(&self, message: Word, signature: &Signature) -> bool {
149        let message_digest = hash_message(message);
150        self.verify_prehash(message_digest, signature)
151    }
152
153    /// Verifies a signature against this public key and pre-hashed message.
154    pub fn verify_prehash(&self, message_digest: [u8; 32], signature: &Signature) -> bool {
155        let signature_inner = k256::ecdsa::Signature::from_scalars(*signature.r(), *signature.s());
156
157        match signature_inner {
158            Ok(signature) => self.inner.verify_prehash(&message_digest, &signature).is_ok(),
159            Err(_) => false,
160        }
161    }
162
163    /// Recovers from the signature the public key associated to the secret key used to sign the
164    /// message.
165    pub fn recover_from(message: Word, signature: &Signature) -> Result<Self, PublicKeyError> {
166        let message_digest = hash_message(message);
167        let signature_data = k256::ecdsa::Signature::from_scalars(*signature.r(), *signature.s())
168            .map_err(|_| PublicKeyError::RecoveryFailed)?;
169
170        let verifying_key = k256::ecdsa::VerifyingKey::recover_from_prehash(
171            &message_digest,
172            &signature_data,
173            RecoveryId::from_byte(signature.v()).ok_or(PublicKeyError::RecoveryFailed)?,
174        )
175        .map_err(|_| PublicKeyError::RecoveryFailed)?;
176
177        Ok(Self { inner: verifying_key })
178    }
179}
180
181impl SequentialCommit for PublicKey {
182    type Commitment = Word;
183
184    fn to_elements(&self) -> Vec<Felt> {
185        bytes_to_packed_u32_elements(&self.to_bytes())
186    }
187}
188
189#[derive(Debug, Error)]
190pub enum PublicKeyError {
191    #[error("Could not recover the public key from the message and signature")]
192    RecoveryFailed,
193}
194
195// SIGNATURE
196// ================================================================================================
197
198/// ECDSA signature over secp256k1 curve using Keccak to hash the messages when signing.
199///
200/// ## Serialization Formats
201///
202/// This implementation supports 2 serialization formats:
203///
204/// ### Custom Format (66 bytes):
205/// - Bytes 0-31: r component (32 bytes, big-endian)
206/// - Bytes 32-63: s component (32 bytes, big-endian)
207/// - Byte 64: recovery ID (v) - values 0-3
208///
209/// ### SEC1 Format (64 bytes):
210/// - Bytes 0-31: r component (32 bytes, big-endian)
211/// - Bytes 32-63: s component (32 bytes, big-endian)
212/// - Note: Recovery ID
213#[derive(Debug, Clone, PartialEq, Eq)]
214pub struct Signature {
215    r: [u8; SCALARS_SIZE_BYTES],
216    s: [u8; SCALARS_SIZE_BYTES],
217    v: u8,
218}
219
220impl Signature {
221    /// Returns the `r` scalar of this signature.
222    pub fn r(&self) -> &[u8; SCALARS_SIZE_BYTES] {
223        &self.r
224    }
225
226    /// Returns the `s` scalar of this signature.
227    pub fn s(&self) -> &[u8; SCALARS_SIZE_BYTES] {
228        &self.s
229    }
230
231    /// Returns the `v` component of this signature, which is a `u8` representing the recovery id.
232    pub fn v(&self) -> u8 {
233        self.v
234    }
235
236    /// Verifies this signature against a message and public key.
237    pub fn verify(&self, message: Word, pub_key: &PublicKey) -> bool {
238        pub_key.verify(message, self)
239    }
240
241    /// Converts signature to SEC1 format (standard 64-byte r||s format).
242    ///
243    /// This format is the standard one used by most ECDSA libraries but loses the recovery ID.
244    pub fn to_sec1_bytes(&self) -> [u8; SIGNATURE_STANDARD_BYTES] {
245        let mut bytes = [0u8; 2 * SCALARS_SIZE_BYTES];
246        bytes[0..SCALARS_SIZE_BYTES].copy_from_slice(self.r());
247        bytes[SCALARS_SIZE_BYTES..2 * SCALARS_SIZE_BYTES].copy_from_slice(self.s());
248        bytes
249    }
250
251    /// Creates a signature from SEC1 format bytes with a given recovery id.
252    ///
253    /// # Arguments
254    /// * `bytes` - 64-byte array containing r and s components
255    /// * `recovery_id` - recovery ID (0-3)
256    pub fn from_sec1_bytes_and_recovery_id(
257        bytes: [u8; SIGNATURE_STANDARD_BYTES],
258        v: u8,
259    ) -> Result<Self, DeserializationError> {
260        let mut r = [0u8; SCALARS_SIZE_BYTES];
261        let mut s = [0u8; SCALARS_SIZE_BYTES];
262        r.copy_from_slice(&bytes[0..SCALARS_SIZE_BYTES]);
263        s.copy_from_slice(&bytes[SCALARS_SIZE_BYTES..2 * SCALARS_SIZE_BYTES]);
264
265        if v > 3 {
266            return Err(DeserializationError::InvalidValue(r#"Invalid recovery ID"#.to_string()));
267        }
268
269        Ok(Signature { r, s, v })
270    }
271}
272
273// SERIALIZATION / DESERIALIZATION
274// ================================================================================================
275
276impl Serializable for SecretKey {
277    fn write_into<W: ByteWriter>(&self, target: &mut W) {
278        let mut buffer = Vec::with_capacity(SECRET_KEY_BYTES);
279        let sk_bytes: [u8; SECRET_KEY_BYTES] = self.inner.to_bytes().into();
280        buffer.extend_from_slice(&sk_bytes);
281
282        target.write_bytes(&buffer);
283    }
284}
285
286impl Deserializable for SecretKey {
287    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
288        let mut bytes: [u8; SECRET_KEY_BYTES] = source.read_array()?;
289
290        let signing_key = SigningKey::from_slice(&bytes)
291            .map_err(|_| DeserializationError::InvalidValue("Invalid secret key".to_string()))?;
292        bytes.zeroize();
293
294        Ok(Self { inner: signing_key })
295    }
296}
297
298impl Serializable for PublicKey {
299    fn write_into<W: ByteWriter>(&self, target: &mut W) {
300        // Compressed format
301        let encoded = self.inner.to_encoded_point(true);
302
303        target.write_bytes(encoded.as_bytes());
304    }
305}
306
307impl Deserializable for PublicKey {
308    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
309        let bytes: [u8; PUBLIC_KEY_BYTES] = source.read_array()?;
310
311        let verifying_key = VerifyingKey::from_sec1_bytes(&bytes)
312            .map_err(|_| DeserializationError::InvalidValue("Invalid public key".to_string()))?;
313
314        Ok(Self { inner: verifying_key })
315    }
316}
317
318impl Serializable for Signature {
319    fn write_into<W: ByteWriter>(&self, target: &mut W) {
320        let mut bytes = [0u8; SIGNATURE_BYTES];
321        bytes[0..SCALARS_SIZE_BYTES].copy_from_slice(self.r());
322        bytes[SCALARS_SIZE_BYTES..2 * SCALARS_SIZE_BYTES].copy_from_slice(self.s());
323        bytes[2 * SCALARS_SIZE_BYTES] = self.v();
324        target.write_bytes(&bytes);
325    }
326}
327
328impl Deserializable for Signature {
329    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
330        let r: [u8; SCALARS_SIZE_BYTES] = source.read_array()?;
331        let s: [u8; SCALARS_SIZE_BYTES] = source.read_array()?;
332        let v: u8 = source.read_u8()?;
333
334        Ok(Signature { r, s, v })
335    }
336}
337
338// HELPER
339// ================================================================================================
340
341/// Hashes a word message using Keccak.
342fn hash_message(message: Word) -> [u8; 32] {
343    use sha3::{Digest, Keccak256};
344    let mut hasher = Keccak256::new();
345    let message_bytes: [u8; 32] = message.into();
346    hasher.update(message_bytes);
347    hasher.finalize().into()
348}