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_elements_with_padding,
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    pub fn to_commitment(&self) -> Word {
141        <Self as SequentialCommit>::to_commitment(self)
142    }
143
144    /// Verifies a signature against this public key and message.
145    pub fn verify(&self, message: Word, signature: &Signature) -> bool {
146        let message_digest = hash_message(message);
147        self.verify_prehash(message_digest, signature)
148    }
149
150    /// Verifies a signature against this public key and pre-hashed message.
151    pub fn verify_prehash(&self, message_digest: [u8; 32], signature: &Signature) -> bool {
152        let signature_inner = k256::ecdsa::Signature::from_scalars(*signature.r(), *signature.s());
153
154        match signature_inner {
155            Ok(signature) => self.inner.verify_prehash(&message_digest, &signature).is_ok(),
156            Err(_) => false,
157        }
158    }
159
160    /// Recovers from the signature the public key associated to the secret key used to sign the
161    /// message.
162    pub fn recover_from(message: Word, signature: &Signature) -> Result<Self, PublicKeyError> {
163        let message_digest = hash_message(message);
164        let signature_data = k256::ecdsa::Signature::from_scalars(*signature.r(), *signature.s())
165            .map_err(|_| PublicKeyError::RecoveryFailed)?;
166
167        let verifying_key = k256::ecdsa::VerifyingKey::recover_from_prehash(
168            &message_digest,
169            &signature_data,
170            RecoveryId::from_byte(signature.v()).ok_or(PublicKeyError::RecoveryFailed)?,
171        )
172        .map_err(|_| PublicKeyError::RecoveryFailed)?;
173
174        Ok(Self { inner: verifying_key })
175    }
176}
177
178impl SequentialCommit for PublicKey {
179    type Commitment = Word;
180
181    fn to_elements(&self) -> Vec<Felt> {
182        bytes_to_elements_with_padding(&self.to_bytes())
183    }
184}
185
186#[derive(Debug, Error)]
187pub enum PublicKeyError {
188    #[error("Could not recover the public key from the message and signature")]
189    RecoveryFailed,
190}
191
192// SIGNATURE
193// ================================================================================================
194
195/// ECDSA signature over secp256k1 curve using Keccak to hash the messages when signing.
196///
197/// ## Serialization Formats
198///
199/// This implementation supports 2 serialization formats:
200///
201/// ### Custom Format (66 bytes):
202/// - Bytes 0-31: r component (32 bytes, big-endian)
203/// - Bytes 32-63: s component (32 bytes, big-endian)
204/// - Byte 64: recovery ID (v) - values 0-3
205///
206/// ### SEC1 Format (64 bytes):
207/// - Bytes 0-31: r component (32 bytes, big-endian)
208/// - Bytes 32-63: s component (32 bytes, big-endian)
209/// - Note: Recovery ID
210#[derive(Debug, Clone, PartialEq, Eq)]
211pub struct Signature {
212    r: [u8; SCALARS_SIZE_BYTES],
213    s: [u8; SCALARS_SIZE_BYTES],
214    v: u8,
215}
216
217impl Signature {
218    /// Returns the `r` scalar of this signature.
219    pub fn r(&self) -> &[u8; SCALARS_SIZE_BYTES] {
220        &self.r
221    }
222
223    /// Returns the `s` scalar of this signature.
224    pub fn s(&self) -> &[u8; SCALARS_SIZE_BYTES] {
225        &self.s
226    }
227
228    /// Returns the `v` component of this signature, which is a `u8` representing the recovery id.
229    pub fn v(&self) -> u8 {
230        self.v
231    }
232
233    /// Verifies this signature against a message and public key.
234    pub fn verify(&self, message: Word, pub_key: &PublicKey) -> bool {
235        pub_key.verify(message, self)
236    }
237
238    /// Converts signature to SEC1 format (standard 64-byte r||s format).
239    ///
240    /// This format is the standard one used by most ECDSA libraries but loses the recovery ID.
241    pub fn to_sec1_bytes(&self) -> [u8; SIGNATURE_STANDARD_BYTES] {
242        let mut bytes = [0u8; 2 * SCALARS_SIZE_BYTES];
243        bytes[0..SCALARS_SIZE_BYTES].copy_from_slice(self.r());
244        bytes[SCALARS_SIZE_BYTES..2 * SCALARS_SIZE_BYTES].copy_from_slice(self.s());
245        bytes
246    }
247
248    /// Creates a signature from SEC1 format bytes with a given recovery id.
249    ///
250    /// # Arguments
251    /// * `bytes` - 64-byte array containing r and s components
252    /// * `recovery_id` - recovery ID (0-3)
253    pub fn from_sec1_bytes_and_recovery_id(
254        bytes: [u8; SIGNATURE_STANDARD_BYTES],
255        v: u8,
256    ) -> Result<Self, DeserializationError> {
257        let mut r = [0u8; SCALARS_SIZE_BYTES];
258        let mut s = [0u8; SCALARS_SIZE_BYTES];
259        r.copy_from_slice(&bytes[0..SCALARS_SIZE_BYTES]);
260        s.copy_from_slice(&bytes[SCALARS_SIZE_BYTES..2 * SCALARS_SIZE_BYTES]);
261
262        if v > 3 {
263            return Err(DeserializationError::InvalidValue(r#"Invalid recovery ID"#.to_string()));
264        }
265
266        Ok(Signature { r, s, v })
267    }
268}
269
270// SERIALIZATION / DESERIALIZATION
271// ================================================================================================
272
273impl Serializable for SecretKey {
274    fn write_into<W: ByteWriter>(&self, target: &mut W) {
275        let mut buffer = Vec::with_capacity(SECRET_KEY_BYTES);
276        let sk_bytes: [u8; SECRET_KEY_BYTES] = self.inner.to_bytes().into();
277        buffer.extend_from_slice(&sk_bytes);
278
279        target.write_bytes(&buffer);
280    }
281}
282
283impl Deserializable for SecretKey {
284    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
285        let mut bytes: [u8; SECRET_KEY_BYTES] = source.read_array()?;
286
287        let signing_key = SigningKey::from_slice(&bytes)
288            .map_err(|_| DeserializationError::InvalidValue("Invalid secret key".to_string()))?;
289        bytes.zeroize();
290
291        Ok(Self { inner: signing_key })
292    }
293}
294
295impl Serializable for PublicKey {
296    fn write_into<W: ByteWriter>(&self, target: &mut W) {
297        // Compressed format
298        let encoded = self.inner.to_encoded_point(true);
299
300        target.write_bytes(encoded.as_bytes());
301    }
302}
303
304impl Deserializable for PublicKey {
305    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
306        let bytes: [u8; PUBLIC_KEY_BYTES] = source.read_array()?;
307
308        let verifying_key = VerifyingKey::from_sec1_bytes(&bytes)
309            .map_err(|_| DeserializationError::InvalidValue("Invalid public key".to_string()))?;
310
311        Ok(Self { inner: verifying_key })
312    }
313}
314
315impl Serializable for Signature {
316    fn write_into<W: ByteWriter>(&self, target: &mut W) {
317        let mut bytes = [0u8; SIGNATURE_BYTES];
318        bytes[0..SCALARS_SIZE_BYTES].copy_from_slice(self.r());
319        bytes[SCALARS_SIZE_BYTES..2 * SCALARS_SIZE_BYTES].copy_from_slice(self.s());
320        bytes[2 * SCALARS_SIZE_BYTES] = self.v();
321        target.write_bytes(&bytes);
322    }
323}
324
325impl Deserializable for Signature {
326    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
327        let r: [u8; SCALARS_SIZE_BYTES] = source.read_array()?;
328        let s: [u8; SCALARS_SIZE_BYTES] = source.read_array()?;
329        let v: u8 = source.read_u8()?;
330
331        Ok(Signature { r, s, v })
332    }
333}
334
335// HELPER
336// ================================================================================================
337
338/// Hashes a word message using Keccak.
339fn hash_message(message: Word) -> [u8; 32] {
340    use sha3::{Digest, Keccak256};
341    let mut hasher = Keccak256::new();
342    let message_bytes: [u8; 32] = message.into();
343    hasher.update(message_bytes);
344    hasher.finalize().into()
345}