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}