ecdsa_fun/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc = include_str!("../README.md")]
3#![no_std]
4#![allow(non_snake_case)]
5
6#[cfg(feature = "alloc")]
7extern crate alloc;
8
9#[cfg(feature = "std")]
10#[macro_use]
11extern crate std;
12
13mod libsecp_compat;
14
15use fun::Tag;
16
17use fun::{G, Point, Scalar, derive_nonce, g, marker::*, nonce::NonceGen, s};
18pub use secp256kfun as fun;
19pub use secp256kfun::nonce;
20mod signature;
21pub use signature::Signature;
22#[cfg(feature = "adaptor")]
23#[cfg_attr(docsrs, doc(cfg(feature = "adaptor")))]
24pub mod adaptor;
25
26/// An instance of the ECDSA signature scheme.
27#[derive(Default, Clone, Debug)]
28pub struct ECDSA<NG> {
29    /// An instance of [`NonceGen`] to produce nonces.
30    ///
31    /// [`NonceGen`]: crate::nonce::NonceGen
32    pub nonce_gen: NG,
33    /// `enforce_low_s`: Whether the verify algorithm should enforce that the `s` component of the signature is low (see [BIP-146]).
34    ///
35    /// [BIP-146]: https://github.com/bitcoin/bips/blob/master/bip-0146.mediawiki#low_s
36    pub enforce_low_s: bool,
37}
38
39impl ECDSA<()> {
40    /// Creates an `ECDSA` instance that cannot be used to sign messages but can
41    /// verify signatures.
42    pub fn verify_only() -> Self {
43        ECDSA {
44            nonce_gen: (),
45            enforce_low_s: false,
46        }
47    }
48}
49
50impl<NG> ECDSA<NG> {
51    /// Creates a ECDSA instance.
52    ///
53    /// The caller chooses how nonces are generated by providing a [`NonceGen`].
54    ///
55    /// # Example
56    /// ```
57    /// use ecdsa_fun::{ECDSA, nonce};
58    /// use rand::rngs::ThreadRng;
59    /// use sha2::Sha256;
60    /// let nonce_gen = nonce::Synthetic::<Sha256, nonce::GlobalRng<ThreadRng>>::default();
61    /// let ecdsa = ECDSA::new(nonce_gen);
62    /// ```
63    ///
64    /// [`NonceGen`]: crate::nonce::NonceGen
65    pub fn new(nonce_gen: NG) -> Self
66    where
67        NG: Tag,
68    {
69        ECDSA {
70            nonce_gen: nonce_gen.tag(b"secp256kfun/ecdsa_fun"),
71            enforce_low_s: false,
72        }
73    }
74
75    /// Transforms the ECDSA instance into one which enforces the [BIP-146] low
76    /// s constraint **when verifying** (it is always low s when signing).
77    ///
78    /// *** DO NOT USE THIS IF VERIFYING BITCOIN TRANSACTIONS FROM THE CHAIN***:
79    /// [BIP-146] is only enforced for transaction relay so you can still have
80    /// valid high s signatures. This is especially true if you are using the
81    /// ECDSA adaptor signature scheme.
82    ///
83    /// [BIP-146]: https://github.com/bitcoin/bips/blob/master/bip-0146.mediawiki#low_s
84    pub fn enforce_low_s(self) -> Self {
85        ECDSA {
86            nonce_gen: self.nonce_gen,
87            enforce_low_s: true,
88        }
89    }
90}
91
92impl<NG> ECDSA<NG> {
93    /// Get the corresponding verification key for a secret key
94    ///
95    /// # Example
96    /// ```
97    /// use ecdsa_fun::{ECDSA, fun::Scalar};
98    /// let ecdsa = ECDSA::verify_only();
99    /// let secret_key = Scalar::random(&mut rand::thread_rng());
100    /// let verification_key = ecdsa.verification_key_for(&secret_key);
101    /// ```
102    pub fn verification_key_for(&self, secret_key: &Scalar) -> Point {
103        g!(secret_key * G).normalize()
104    }
105    /// Verify an ECDSA signature.
106    #[must_use]
107    pub fn verify(
108        &self,
109        verification_key: &Point<impl PointType, Public, NonZero>,
110        message: &[u8; 32],
111        signature: &Signature,
112    ) -> bool {
113        let (R_x, s) = signature.as_tuple();
114        // This ensures that there is only one valid s value per R_x for any given message.
115        if s.is_high() && self.enforce_low_s {
116            return false;
117        }
118
119        let m = Scalar::<Public, _>::from_bytes_mod_order(*message).public();
120        let s_inv = s.invert();
121
122        g!((s_inv * m) * G + (s_inv * R_x) * verification_key)
123            .non_zero()
124            .is_some_and(|implied_R| implied_R.x_eq_scalar(R_x))
125    }
126}
127
128impl<NG: NonceGen> ECDSA<NG> {
129    /// Deterministically produce a ECDSA signature on a message hash.
130    ///
131    /// # Examples
132    ///
133    /// ```
134    /// use ecdsa_fun::{
135    ///     ECDSA,
136    ///     fun::{digest::Digest, prelude::*},
137    ///     nonce,
138    /// };
139    /// use rand::rngs::ThreadRng;
140    /// use sha2::Sha256;
141    /// let secret_key = Scalar::random(&mut rand::thread_rng());
142    /// let nonce_gen = nonce::Synthetic::<Sha256, nonce::GlobalRng<ThreadRng>>::default();
143    /// let ecdsa = ECDSA::new(nonce_gen);
144    /// let verification_key = ecdsa.verification_key_for(&secret_key);
145    /// let message_hash = {
146    ///     let message = b"Attack at dawn";
147    ///     let mut message_hash = [0u8; 32];
148    ///     let hash = Sha256::default().chain_update(message);
149    ///     message_hash.copy_from_slice(hash.finalize().as_ref());
150    ///     message_hash
151    /// };
152    /// let signature = ecdsa.sign(&secret_key, &message_hash);
153    /// assert!(ecdsa.verify(&verification_key, &message_hash, &signature));
154    /// ```
155    pub fn sign(&self, secret_key: &Scalar, message_hash: &[u8; 32]) -> Signature {
156        let x = secret_key;
157        let m = Scalar::<Public, _>::from_bytes_mod_order(*message_hash).public();
158        let r = derive_nonce!(
159            nonce_gen => self.nonce_gen,
160            secret => x,
161            public => [&message_hash[..]]
162        );
163        let R = g!(r * G).normalize(); // Must be normal so we can get x-coordinate
164
165        // This coverts R is its x-coordinate mod q. This acts as a kind of poor
166        // man's version of the Fiat-Shamir challenge in a Schnorr
167        // signature. The lack of any known algebraic relationship between r and
168        // R_x is what makes ECDSA signatures difficult to forge.
169        let R_x = Scalar::<Public, _>::from_bytes_mod_order(R.to_xonly_bytes())
170            // There *is* a single point that will be zero here but since we're
171            // choosing R pseudorandomly it won't occur.
172            .public()
173            .non_zero()
174            .expect("computationally unreachable");
175
176        let mut s = s!((m + R_x * x) / r)
177            // Given R_x is determined by x and m through a hash, reaching
178            // (m + R_x * x) = 0 is intractable.
179            .non_zero()
180            .expect("computationally unreachable")
181            .public();
182
183        // s values must be low (less than half group order), otherwise signatures
184        // would be malleable i.e. (R,s) and (R,-s) would both be valid signatures.
185        s.conditional_negate(s.is_high());
186
187        Signature { R_x, s }
188    }
189}
190
191#[macro_export]
192#[doc(hidden)]
193macro_rules! test_instance {
194    () => {
195        $crate::ECDSA::new($crate::nonce::Deterministic::<sha2::Sha256>::default())
196    };
197}
198
199#[cfg(test)]
200mod test {
201    use super::*;
202    use rand::RngCore;
203
204    #[test]
205    fn repeated_sign_and_verify() {
206        let ecdsa = test_instance!();
207        for _ in 0..20 {
208            let mut message = [0u8; 32];
209            rand::thread_rng().fill_bytes(&mut message);
210            let secret_key = Scalar::random(&mut rand::thread_rng());
211            let public_key = g!(secret_key * G).normalize();
212            let sig = ecdsa.sign(&secret_key, &message);
213            assert!(ecdsa.verify(&public_key, &message, &sig))
214        }
215    }
216
217    #[test]
218    fn low_s() {
219        let ecdsa_enforce_low_s = test_instance!().enforce_low_s();
220        let ecdsa = test_instance!();
221        // TODO: use proptest
222        for _ in 0..20 {
223            let mut message = [0u8; 32];
224            rand::thread_rng().fill_bytes(&mut message);
225            let secret_key = Scalar::random(&mut rand::thread_rng());
226            let public_key = ecdsa.verification_key_for(&secret_key);
227            let mut sig = ecdsa.sign(&secret_key, &message);
228            assert!(ecdsa.verify(&public_key, &message, &sig));
229            assert!(ecdsa_enforce_low_s.verify(&public_key, &message, &sig));
230            sig.s = -sig.s;
231            assert!(!ecdsa_enforce_low_s.verify(&public_key, &message, &sig));
232            assert!(ecdsa.verify(&public_key, &message, &sig));
233        }
234    }
235}