Skip to main content

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::{format, string::ToString, vec::Vec};
5use core::fmt;
6
7use k256::{
8    AffinePoint,
9    ecdh::diffie_hellman,
10    ecdsa,
11    ecdsa::{RecoveryId, VerifyingKey, signature::hazmat::PrehashVerifier},
12    elliptic_curve::{Generate, point::AffineCoordinates, scalar::IsHigh},
13    pkcs8::DecodePublicKey,
14};
15use miden_crypto_derive::{SilentDebug, SilentDisplay};
16use rand::CryptoRng;
17use thiserror::Error;
18
19use crate::{
20    Felt, SequentialCommit, Word,
21    ecdh::k256::{EphemeralPublicKey, SharedSecret},
22    utils::{
23        ByteReader, ByteWriter, Deserializable, DeserializationError, HexParseError, Serializable,
24        hex_to_bytes, read_sensitive_array, zeroize::ZeroizeOnDrop,
25    },
26};
27
28mod tests;
29
30// CONSTANTS
31// ================================================================================================
32
33/// Length of secret key in bytes
34const SECRET_KEY_BYTES: usize = 32;
35/// Length of public key in bytes when using compressed format encoding
36pub(crate) const PUBLIC_KEY_BYTES: usize = 33;
37/// Length of public key in bytes when using uncompressed format encoding
38const PUBLIC_KEY_UNCOMPRESSED_BYTES: usize = 65;
39/// Length of signature in bytes using our custom serialization
40const SIGNATURE_BYTES: usize = 65;
41/// Length of signature in bytes using standard serialization i.e., `SEC1`
42const SIGNATURE_STANDARD_BYTES: usize = 64;
43/// Length of scalars for the `secp256k1` curve
44const SCALARS_SIZE_BYTES: usize = 32;
45
46// SECRET KEY
47// ================================================================================================
48
49/// Secret key for ECDSA signature verification over secp256k1 curve.
50#[derive(Clone, SilentDebug, SilentDisplay)]
51struct SecretKey {
52    inner: ecdsa::SigningKey,
53}
54
55impl SecretKey {
56    /// Generates a new secret key using the provided random number generator.
57    fn with_rng<R: CryptoRng>(rng: &mut R) -> Self {
58        let signing_key = ecdsa::SigningKey::generate_from_rng(rng);
59
60        Self { inner: signing_key }
61    }
62
63    /// Gets the corresponding public key for this secret key.
64    fn public_key(&self) -> PublicKey {
65        let verifying_key = self.inner.verifying_key();
66        PublicKey { inner: *verifying_key }
67    }
68
69    /// Signs a message (represented as a Word) with this secret key.
70    fn sign(&self, message: Word) -> Signature {
71        let message_digest = hash_message(message);
72        self.sign_prehash(message_digest)
73    }
74
75    /// Signs a pre-hashed message with this secret key.
76    fn sign_prehash(&self, message_digest: [u8; 32]) -> Signature {
77        let (signature_inner, recovery_id) = self.inner.sign_prehash_recoverable(&message_digest);
78
79        let (r, s) = signature_inner.split_scalars();
80
81        Signature {
82            r: r.to_bytes().into(),
83            s: s.to_bytes().into(),
84            v: recovery_id.into(),
85        }
86    }
87
88    /// Computes a Diffie-Hellman shared secret from this secret key and the ephemeral public key
89    /// generated by the other party.
90    fn get_shared_secret(&self, pk_e: &EphemeralPublicKey) -> SharedSecret {
91        let shared_secret_inner = diffie_hellman(self.inner.as_nonzero_scalar(), pk_e.as_affine());
92
93        SharedSecret::new(shared_secret_inner)
94    }
95}
96
97// SAFETY: The inner `k256::ecdsa::SigningKey` already implements `ZeroizeOnDrop`,
98// which ensures that the secret key material is securely zeroized when dropped.
99impl ZeroizeOnDrop for SecretKey {}
100
101impl PartialEq for SecretKey {
102    fn eq(&self, other: &Self) -> bool {
103        use subtle::ConstantTimeEq;
104        self.to_bytes().ct_eq(&other.to_bytes()).into()
105    }
106}
107
108impl Eq for SecretKey {}
109
110// SIGNING KEY
111// ================================================================================================
112
113/// A secret key for ECDSA signature verification over the secp256k1 curve.
114#[derive(Clone, Eq, PartialEq, SilentDebug, SilentDisplay)] // Safe as SecretKey has const-time eq
115pub struct SigningKey(SecretKey);
116
117impl SigningKey {
118    /// Generates a new random signing key using the OS random number generator.
119    ///
120    /// This is cryptographically secure as long as [`rand::rng`] remains so.
121    #[cfg(feature = "std")]
122    #[allow(clippy::new_without_default)]
123    pub fn new() -> Self {
124        let mut rng = rand::rng();
125        Self::with_rng(&mut rng)
126    }
127
128    /// Generates a new signing key using the provided random number generator.
129    pub fn with_rng<R: CryptoRng>(rng: &mut R) -> Self {
130        Self(SecretKey::with_rng(rng))
131    }
132
133    /// Gets the public key that corresponds to this signing key.
134    pub fn public_key(&self) -> PublicKey {
135        self.0.public_key()
136    }
137
138    /// Signs a message (represented as a word) with this signing key.
139    pub fn sign(&self, message: Word) -> Signature {
140        self.0.sign(message)
141    }
142
143    /// Signs a pre-hashed message with this signing key.
144    pub fn sign_prehash(&self, message_digest: [u8; 32]) -> Signature {
145        self.0.sign_prehash(message_digest)
146    }
147}
148
149impl From<SecretKey> for SigningKey {
150    fn from(secret_key: SecretKey) -> Self {
151        Self(secret_key)
152    }
153}
154
155// SAFETY: The inner `SecretKey` already implements `ZeroizeOnDrop` which ensures that the secret
156// key material is securely zeroized when dropped.
157impl ZeroizeOnDrop for SigningKey {}
158
159impl Serializable for SigningKey {
160    fn write_into<W: ByteWriter>(&self, target: &mut W) {
161        self.0.write_into(target);
162    }
163}
164
165impl Deserializable for SigningKey {
166    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
167        Ok(Self(SecretKey::read_from(source)?))
168    }
169}
170
171// KEY EXCHANGE KEY
172// ================================================================================================
173
174/// A secret key for ECDH key-exchange over the secp256k1 curve.
175#[derive(Clone, Eq, PartialEq, SilentDebug, SilentDisplay)] // Safe as SecretKey has const-time eq
176pub struct KeyExchangeKey(SecretKey);
177
178impl KeyExchangeKey {
179    /// Generates a new random key exchange key using the OS random number generator.
180    ///
181    /// This is cryptographically secure as long as [`rand::rng`] remains so.
182    #[cfg(feature = "std")]
183    #[allow(clippy::new_without_default)]
184    pub fn new() -> Self {
185        let mut rng = rand::rng();
186        Self::with_rng(&mut rng)
187    }
188
189    /// Generates a new signing key using the provided random number generator.
190    pub fn with_rng<R: CryptoRng>(rng: &mut R) -> Self {
191        Self(SecretKey::with_rng(rng))
192    }
193
194    /// Gets the public key that corresponds to this key exchange key.
195    pub fn public_key(&self) -> PublicKey {
196        self.0.public_key()
197    }
198
199    /// Computes a Diffie-Hellman shared secret from this secret key and the ephemeral public key
200    /// generated by the other party.
201    pub fn get_shared_secret(&self, pk_e: EphemeralPublicKey) -> SharedSecret {
202        self.0.get_shared_secret(&pk_e)
203    }
204}
205
206impl From<SecretKey> for KeyExchangeKey {
207    fn from(value: SecretKey) -> Self {
208        Self(value)
209    }
210}
211
212// SAFETY: The inner `SecretKey` already implements `ZeroizeOnDrop` which ensures that the secret
213// key material is securely zeroized when dropped.
214impl ZeroizeOnDrop for KeyExchangeKey {}
215
216impl Serializable for KeyExchangeKey {
217    fn write_into<W: ByteWriter>(&self, target: &mut W) {
218        self.0.write_into(target);
219    }
220}
221
222impl Deserializable for KeyExchangeKey {
223    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
224        Ok(Self(SecretKey::read_from(source)?))
225    }
226}
227
228// PUBLIC KEY
229// ================================================================================================
230
231/// Public key for ECDSA signature verification over secp256k1 curve.
232#[derive(Debug, Clone, PartialEq, Eq)]
233pub struct PublicKey {
234    pub(crate) inner: VerifyingKey,
235}
236
237impl PublicKey {
238    /// Returns a commitment to the public key using the Poseidon2 hash function.
239    ///
240    /// Public key serialization remains compressed SEC1. The commitment preimage is not the
241    /// compressed SEC1 encoding; it is `qx || qy`, where each secp256k1 affine coordinate is
242    /// represented as eight little-endian numeric `u32` limbs, one limb per [`Felt`].
243    pub fn to_commitment(&self) -> Word {
244        <Self as SequentialCommit>::to_commitment(self)
245    }
246
247    /// Returns a reference to this public key as an elliptic curve point in affine coordinates.
248    pub fn as_affine(&self) -> &AffinePoint {
249        self.inner.as_affine()
250    }
251
252    /// Verifies a signature against this public key and message.
253    pub fn verify(&self, message: Word, signature: &Signature) -> bool {
254        let message_digest = hash_message(message);
255        self.verify_prehash(message_digest, signature)
256    }
257
258    /// Verifies a signature against this public key and pre-hashed message.
259    pub fn verify_prehash(&self, message_digest: [u8; 32], signature: &Signature) -> bool {
260        let signature_inner = ecdsa::Signature::from_scalars(*signature.r(), *signature.s());
261
262        match signature_inner {
263            Ok(signature) => self.inner.verify_prehash(&message_digest, &signature).is_ok(),
264            Err(_) => false,
265        }
266    }
267
268    /// Recovers from the signature the public key associated to the secret key used to sign the
269    /// message.
270    pub fn recover_from(message: Word, signature: &Signature) -> Result<Self, PublicKeyError> {
271        let message_digest = hash_message(message);
272        Self::recover_from_prehash(message_digest, signature)
273    }
274
275    /// Recovers the public key associated with a signature over a pre-hashed message.
276    ///
277    /// # Security
278    ///
279    /// `message_digest` must be produced by a cryptographically secure hash of the intended
280    /// message. If an attacker can choose it as arbitrary algebraic data, they can construct a
281    /// signature that recovers a target public key without knowing the corresponding secret key.
282    ///
283    /// Recovery returns a key consistent with the supplied digest and signature; it does not by
284    /// itself authenticate an identity. Before authorizing an action, callers must compare the
285    /// recovered key with an expected key from trusted state.
286    pub fn recover_from_prehash(
287        message_digest: [u8; 32],
288        signature: &Signature,
289    ) -> Result<Self, PublicKeyError> {
290        let signature_data = ecdsa::Signature::from_scalars(*signature.r(), *signature.s())
291            .map_err(|_| PublicKeyError::RecoveryFailed)?;
292
293        let verifying_key = VerifyingKey::recover_from_prehash(
294            &message_digest,
295            &signature_data,
296            RecoveryId::from_byte(signature.v()).ok_or(PublicKeyError::RecoveryFailed)?,
297        )
298        .map_err(|_| PublicKeyError::RecoveryFailed)?;
299
300        Ok(Self { inner: verifying_key })
301    }
302
303    /// Creates a public key from SPKI ASN.1 DER format bytes.
304    ///
305    /// # Arguments
306    /// * `bytes` - SPKI ASN.1 DER format bytes
307    pub fn from_der(bytes: &[u8]) -> Result<Self, DeserializationError> {
308        let verifying_key = VerifyingKey::from_public_key_der(bytes)
309            .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?;
310        Ok(PublicKey { inner: verifying_key })
311    }
312
313    /// Creates a public key from SEC1-encoded bytes.
314    ///
315    /// Accepts only the 33-byte compressed encoding (tag byte `0x02` or `0x03`) and the 65-byte
316    /// uncompressed encoding (tag byte `0x04`). Any other tag byte is rejected, including the
317    /// identity (`0x00`) and the compact encoding (`0x05`). The point is fully validated: an
318    /// uncompressed key whose coordinates do not lie on the curve is rejected.
319    pub fn from_sec1_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
320        if bytes.len() != PUBLIC_KEY_BYTES && bytes.len() != PUBLIC_KEY_UNCOMPRESSED_BYTES {
321            return Err(DeserializationError::InvalidValue(format!(
322                "unsupported ECDSA public key length: expected {PUBLIC_KEY_BYTES} (compressed) or \
323                {PUBLIC_KEY_UNCOMPRESSED_BYTES} (uncompressed) bytes, got {}",
324                bytes.len()
325            )));
326        }
327
328        // The length check above guarantees that `bytes` is not empty.
329        let tag = bytes[0];
330        if !matches!(
331            (bytes.len(), tag),
332            (PUBLIC_KEY_BYTES, 0x02 | 0x03) | (PUBLIC_KEY_UNCOMPRESSED_BYTES, 0x04)
333        ) {
334            return Err(DeserializationError::InvalidValue(format!(
335                "unsupported ECDSA public key tag byte {tag:#04x}: expected 0x02 or 0x03 \
336                ({PUBLIC_KEY_BYTES}-byte compressed) or 0x04 \
337                ({PUBLIC_KEY_UNCOMPRESSED_BYTES}-byte uncompressed)"
338            )));
339        }
340
341        let verifying_key = VerifyingKey::from_sec1_bytes(bytes)
342            .map_err(|_| DeserializationError::InvalidValue("Invalid public key".to_string()))?;
343        Ok(PublicKey { inner: verifying_key })
344    }
345
346    /// Creates a public key from a `0x`-prefixed hex string in SEC1 format.
347    ///
348    /// Accepts the 33-byte compressed and the 65-byte uncompressed encoding (the form Ledger and
349    /// other Ethereum-style signers export). See [`Self::from_sec1_bytes`] for the accepted tag
350    /// bytes and for validation.
351    pub fn from_hex(encoded: &str) -> Result<Self, DeserializationError> {
352        let invalid_hex = |err: HexParseError| DeserializationError::InvalidValue(err.to_string());
353
354        let hex_digits = encoded.strip_prefix("0x").ok_or_else(|| {
355            DeserializationError::InvalidValue(
356                "ECDSA public key must use a 0x-prefixed hexadecimal encoding".to_string(),
357            )
358        })?;
359
360        match hex_digits.len() {
361            len if len == PUBLIC_KEY_BYTES * 2 => Self::from_sec1_bytes(
362                &hex_to_bytes::<PUBLIC_KEY_BYTES>(encoded).map_err(invalid_hex)?,
363            ),
364            len if len == PUBLIC_KEY_UNCOMPRESSED_BYTES * 2 => Self::from_sec1_bytes(
365                &hex_to_bytes::<PUBLIC_KEY_UNCOMPRESSED_BYTES>(encoded).map_err(invalid_hex)?,
366            ),
367            len => Err(DeserializationError::InvalidValue(format!(
368                "unsupported ECDSA public key length: expected {} (compressed) or {} \
369                (uncompressed) hexadecimal digits after the 0x prefix, got {len}",
370                PUBLIC_KEY_BYTES * 2,
371                PUBLIC_KEY_UNCOMPRESSED_BYTES * 2,
372            ))),
373        }
374    }
375}
376
377impl SequentialCommit for PublicKey {
378    type Commitment = Word;
379
380    fn to_elements(&self) -> Vec<Felt> {
381        affine_point_to_elements(self.as_affine()).to_vec()
382    }
383}
384
385#[derive(Debug, Error)]
386pub enum PublicKeyError {
387    #[error("Could not recover the public key from the message and signature")]
388    RecoveryFailed,
389}
390
391// SIGNATURE
392// ================================================================================================
393
394/// ECDSA signature over secp256k1 curve using Keccak to hash the messages when signing.
395///
396/// ## Serialization Formats
397///
398/// This implementation supports two serialization formats:
399///
400/// ### Custom Format (65 bytes):
401/// - Bytes 0-31: r component (32 bytes, big-endian)
402/// - Bytes 32-63: s component (32 bytes, big-endian)
403/// - Byte 64: recovery ID (v) - values 0-3
404///
405/// ### SEC1 Format (64 bytes):
406/// - Bytes 0-31: r component (32 bytes, big-endian)
407/// - Bytes 32-63: s component (32 bytes, big-endian)
408/// - Note: Recovery ID
409///
410/// # Examples
411///
412/// ```
413/// use miden_crypto::{
414///     Felt, Word,
415///     dsa::ecdsa_k256_keccak::{Signature, SigningKey},
416/// };
417/// use miden_serde_utils::{Deserializable, Serializable};
418/// use rand::SeedableRng;
419/// use rand_chacha::ChaCha20Rng;
420///
421/// let mut rng = ChaCha20Rng::from_seed([7; 32]);
422/// let signing_key = SigningKey::with_rng(&mut rng);
423/// let message = Word::new([
424///     Felt::new_unchecked(1),
425///     Felt::new_unchecked(2),
426///     Felt::new_unchecked(3),
427///     Felt::new_unchecked(4),
428/// ]);
429///
430/// let signature = signing_key.sign(message);
431/// let encoded = signature.to_bytes();
432///
433/// assert_eq!(encoded.len(), 65);
434/// assert_eq!(Signature::read_from_bytes(&encoded).unwrap(), signature);
435/// ```
436#[derive(Debug, Clone, PartialEq, Eq)]
437pub struct Signature {
438    r: [u8; SCALARS_SIZE_BYTES],
439    s: [u8; SCALARS_SIZE_BYTES],
440    v: u8,
441}
442
443impl Signature {
444    /// Returns the `r` scalar of this signature.
445    pub fn r(&self) -> &[u8; SCALARS_SIZE_BYTES] {
446        &self.r
447    }
448
449    /// Returns the `s` scalar of this signature.
450    pub fn s(&self) -> &[u8; SCALARS_SIZE_BYTES] {
451        &self.s
452    }
453
454    /// Returns the `v` component of this signature, which is a `u8` representing the recovery id.
455    pub fn v(&self) -> u8 {
456        self.v
457    }
458
459    /// Verifies this signature against a message and public key.
460    pub fn verify(&self, message: Word, pub_key: &PublicKey) -> bool {
461        pub_key.verify(message, self)
462    }
463
464    /// Converts signature to SEC1 format (standard 64-byte r||s format).
465    ///
466    /// This format is the standard one used by most ECDSA libraries but loses the recovery ID.
467    pub fn to_sec1_bytes(&self) -> [u8; SIGNATURE_STANDARD_BYTES] {
468        let mut bytes = [0u8; 2 * SCALARS_SIZE_BYTES];
469        bytes[0..SCALARS_SIZE_BYTES].copy_from_slice(self.r());
470        bytes[SCALARS_SIZE_BYTES..2 * SCALARS_SIZE_BYTES].copy_from_slice(self.s());
471        bytes
472    }
473
474    /// Creates a signature from SEC1 format bytes with a given recovery id.
475    ///
476    /// # Arguments
477    /// * `bytes` - 64-byte array containing r and s components
478    /// * `recovery_id` - recovery ID (0-3)
479    pub fn from_sec1_bytes_and_recovery_id(
480        bytes: [u8; SIGNATURE_STANDARD_BYTES],
481        recovery_id: u8,
482    ) -> Result<Self, DeserializationError> {
483        let mut r = [0u8; SCALARS_SIZE_BYTES];
484        let mut s = [0u8; SCALARS_SIZE_BYTES];
485        r.copy_from_slice(&bytes[0..SCALARS_SIZE_BYTES]);
486        s.copy_from_slice(&bytes[SCALARS_SIZE_BYTES..2 * SCALARS_SIZE_BYTES]);
487
488        if recovery_id > 3 {
489            return Err(DeserializationError::InvalidValue(r#"Invalid recovery ID"#.to_string()));
490        }
491
492        Ok(Signature { r, s, v: recovery_id })
493    }
494
495    /// Creates a signature from ASN.1 DER format bytes with a given recovery id.
496    ///
497    /// # Arguments
498    /// * `bytes` - ASN.1 DER format bytes
499    /// * `recovery_id` - recovery ID (0-3)
500    pub fn from_der(bytes: &[u8], mut recovery_id: u8) -> Result<Self, DeserializationError> {
501        if recovery_id > 3 {
502            return Err(DeserializationError::InvalidValue(r#"Invalid recovery ID"#.to_string()));
503        }
504
505        let sig = ecdsa::Signature::from_der(bytes)
506            .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?;
507
508        // Normalize signature into "low s" form.
509        // See https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki.
510        let high_s = sig.s().is_high();
511        if bool::from(high_s) {
512            // Replacing s with (n - s) corresponds to negating the ephemeral point R
513            // (i.e. R -> -R), which flips the y-parity of R. A recoverable signature's
514            // `v` encodes that y-parity in its LSB, so we must toggle only that bit to
515            // preserve recoverability.
516            recovery_id ^= 1;
517        }
518        let sig = sig.normalize_s();
519
520        let (r, s) = sig.split_scalars();
521
522        Ok(Signature {
523            r: <[u8; SCALARS_SIZE_BYTES]>::from(r.to_bytes()),
524            s: <[u8; SCALARS_SIZE_BYTES]>::from(s.to_bytes()),
525            v: recovery_id,
526        })
527    }
528}
529
530// SERIALIZATION / DESERIALIZATION
531// ================================================================================================
532
533impl Serializable for SecretKey {
534    fn write_into<W: ByteWriter>(&self, target: &mut W) {
535        let mut buffer = Vec::with_capacity(SECRET_KEY_BYTES);
536        let sk_bytes: [u8; SECRET_KEY_BYTES] = self.inner.to_bytes().into();
537        buffer.extend_from_slice(&sk_bytes);
538
539        target.write_bytes(&buffer);
540    }
541}
542
543impl Deserializable for SecretKey {
544    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
545        let bytes = read_sensitive_array::<SECRET_KEY_BYTES, _>(source)?;
546
547        let signing_key = ecdsa::SigningKey::from_slice(bytes.as_slice())
548            .map_err(|_| DeserializationError::InvalidValue("Invalid secret key".to_string()))?;
549
550        Ok(Self { inner: signing_key })
551    }
552}
553
554impl Serializable for PublicKey {
555    fn write_into<W: ByteWriter>(&self, target: &mut W) {
556        let encoded = self.inner.to_sec1_point(true);
557        target.write_bytes(encoded.as_bytes());
558    }
559}
560
561impl Deserializable for PublicKey {
562    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
563        let bytes: [u8; PUBLIC_KEY_BYTES] = source.read_array()?;
564
565        let verifying_key = VerifyingKey::from_sec1_bytes(&bytes)
566            .map_err(|_| DeserializationError::InvalidValue("Invalid public key".to_string()))?;
567
568        Ok(Self { inner: verifying_key })
569    }
570}
571
572impl fmt::Display for PublicKey {
573    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
574        crate::utils::write_hex(f, &self.to_bytes())
575    }
576}
577
578impl Serializable for Signature {
579    fn write_into<W: ByteWriter>(&self, target: &mut W) {
580        let mut bytes = [0u8; SIGNATURE_BYTES];
581        bytes[0..SCALARS_SIZE_BYTES].copy_from_slice(self.r());
582        bytes[SCALARS_SIZE_BYTES..2 * SCALARS_SIZE_BYTES].copy_from_slice(self.s());
583        bytes[2 * SCALARS_SIZE_BYTES] = self.v();
584        target.write_bytes(&bytes);
585    }
586}
587
588impl Deserializable for Signature {
589    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
590        let r: [u8; SCALARS_SIZE_BYTES] = source.read_array()?;
591        let s: [u8; SCALARS_SIZE_BYTES] = source.read_array()?;
592        let v: u8 = source.read_u8()?;
593
594        if v > 3 {
595            Err(DeserializationError::InvalidValue(r#"Invalid recovery ID"#.to_string()))
596        } else {
597            Ok(Signature { r, s, v })
598        }
599    }
600}
601
602impl fmt::Display for Signature {
603    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
604        crate::utils::write_hex(f, &self.to_bytes())
605    }
606}
607
608// HELPER
609// ================================================================================================
610
611fn affine_point_to_elements(point: &AffinePoint) -> [Felt; 16] {
612    let qx = be_bytes_to_le_u32_limbs(point.x().as_ref());
613    let qy = be_bytes_to_le_u32_limbs(point.y().as_ref());
614
615    core::array::from_fn(|idx| {
616        if idx < 8 {
617            Felt::from_u32(qx[idx])
618        } else {
619            Felt::from_u32(qy[idx - 8])
620        }
621    })
622}
623
624fn be_bytes_to_le_u32_limbs(bytes: &[u8]) -> [u32; 8] {
625    debug_assert_eq!(bytes.len(), SCALARS_SIZE_BYTES);
626    core::array::from_fn(|idx| {
627        let start = SCALARS_SIZE_BYTES - 4 * (idx + 1);
628        u32::from_be_bytes(bytes[start..start + 4].try_into().expect("chunk is exactly 4 bytes"))
629    })
630}
631
632/// Hashes a word message using Keccak.
633fn hash_message(message: Word) -> [u8; 32] {
634    use sha3::{Digest, Keccak256};
635    let mut hasher = Keccak256::new();
636    let message_bytes: [u8; 32] = message.into();
637    hasher.update(message_bytes);
638    hasher.finalize().into()
639}