ecdsa/hazmat.rs
1//! Low-level ECDSA primitives.
2//!
3//! <div class="warning">
4//! <b>Security๏ธ Warning: Hazardous Materials!</b>
5//!
6//! YOU PROBABLY DON'T WANT TO USE THESE!
7//!
8//! These primitives are easy-to-misuse low-level interfaces.
9//!
10//! If you are an end user / non-expert in cryptography, do not use these!
11//! Failure to use them correctly can lead to catastrophic failures including
12//! FULL PRIVATE KEY RECOVERY!
13//! </div>
14
15use crate::{EcdsaCurve, Error, RecoveryId, Result, Signature};
16use elliptic_curve::{
17 CurveArithmetic, FieldBytes, NonZeroScalar, ProjectivePoint, Scalar,
18 bigint::{BitOps, Encoding},
19 ff::PrimeField,
20 group::{Curve as _, Group},
21 ops::{Invert, MulByGeneratorVartime, Reduce},
22 point::AffineCoordinates,
23 scalar::IsHigh,
24};
25
26#[cfg(feature = "digest")]
27use digest::{Digest, block_api::BlockSizeUser};
28
29/// Sign a prehashed message digest using the provided secret scalar and
30/// ephemeral scalar, returning an ECDSA signature.
31///
32/// Accepts the following arguments:
33///
34/// - `d`: signing key. MUST BE UNIFORMLY RANDOM!!!
35/// - `k`: ephemeral scalar value. MUST BE UNIFORMLY RANDOM!!!
36/// - `z`: message digest to sign. MUST BE OUTPUT OF A CRYPTOGRAPHICALLY SECURE DIGEST ALGORITHM!
37///
38/// # Low-S Normalization
39///
40/// This function will apply low-S normalization if `<C as EcdsaCurve>::NORMALIZE_S` is true.
41///
42/// # Returns
43///
44/// ECDSA [`Signature`] and a [`RecoveryId`] which can be used to recover the verifying key for a
45/// given signature.
46///
47/// # Errors
48///
49/// This will return an error if a zero-scalar was generated. It can be tried again with a
50/// different `k`.
51#[allow(non_snake_case)]
52pub fn sign_prehashed<C>(
53 d: &NonZeroScalar<C>,
54 k: &NonZeroScalar<C>,
55 z: &[u8],
56) -> Result<(Signature<C>, RecoveryId)>
57where
58 C: EcdsaCurve + CurveArithmetic,
59{
60 // Reduce message hash into an element of the scalar field for `C`.
61 let z = bytes2scalar::<C>(z);
62
63 // Compute scalar inversion of ๐.
64 let k_inv = k.invert();
65
66 // Compute ๐น = ๐ร๐ฎ.
67 let R = ProjectivePoint::<C>::mul_by_generator(k).to_affine();
68
69 // Lift x-coordinate of ๐น (element of base field) into a serialized big
70 // integer, then reduce it into an element of the scalar field.
71 let r = bytes2scalar::<C>(&R.x());
72
73 // Compute ๐ as a signature over ๐ and ๐.
74 let s = *k_inv * (z + (r * d.as_ref()));
75
76 // NOTE: `Signature::from_scalars` checks that both `r` and `s` are non-zero.
77 let mut signature = Signature::from_scalars(r, s)?;
78
79 // Compute recovery ID.
80 let x_is_reduced = r.to_repr() != R.x();
81 let y_is_odd = R.y_is_odd();
82 let mut recovery_id = RecoveryId::new(y_is_odd.into(), x_is_reduced);
83
84 // Apply low-S normalization if the curve is configured for it
85 if C::NORMALIZE_S {
86 recovery_id.0 ^= s.is_high().unwrap_u8();
87 signature = signature.normalize_s();
88 }
89
90 Ok((signature, recovery_id))
91}
92
93/// Try to sign the given message digest deterministically using the method
94/// described in [RFC6979] for computing ECDSA ephemeral scalar `k`.
95///
96/// Accepts the following parameters:
97/// - `d`: signing key. MUST BE UNIFORMLY RANDOM!!!
98/// - `z`: message digest to be signed, i.e. `H(m)`. Does not have to be reduced in advance.
99/// - `ad`: optional additional data, e.g. added entropy from an RNG
100///
101/// [RFC6979]: https://datatracker.ietf.org/doc/html/rfc6979
102pub fn sign_prehashed_rfc6979<C, D>(
103 d: &NonZeroScalar<C>,
104 z: &[u8],
105 ad: &[u8],
106) -> (Signature<C>, RecoveryId)
107where
108 C: EcdsaCurve + CurveArithmetic,
109 D: Digest + BlockSizeUser,
110{
111 let order = C::ORDER;
112 let mut kgen = rfc6979::KGenerator::<D, C::Uint>::new(&d.to_repr(), z, ad, &order);
113
114 loop {
115 let mut k_bytes = FieldBytes::<C>::default();
116 kgen.fill_next_k(&mut k_bytes);
117
118 if let Some(k) = NonZeroScalar::<C>::from_repr(k_bytes).into_option() {
119 if let Ok(ret) = sign_prehashed(d, &k, z) {
120 return ret;
121 }
122 }
123 }
124}
125
126/// Verify the prehashed message against the provided ECDSA signature.
127///
128/// Accepts the following arguments:
129///
130/// - `q`: public key with which to verify the signature.
131/// - `z`: message digest to verify. MUST BE OUTPUT OF A CRYPTOGRAPHICALLY SECURE DIGEST ALGORITHM!
132/// - `signature`: purported signature to verify against the key and message.
133///
134/// # Errors
135/// Returns [`Error`] if the signature failed to verify.
136pub fn verify_prehashed<C>(q: &ProjectivePoint<C>, z: &[u8], signature: &Signature<C>) -> Result<()>
137where
138 C: EcdsaCurve + CurveArithmetic,
139{
140 let (r, s) = signature.split_scalars();
141
142 if C::NORMALIZE_S && s.is_high().into() {
143 return Err(Error::new());
144 }
145
146 let z = bytes2scalar::<C>(z);
147 let s_inv = *s.invert_vartime();
148 let u1 = z * s_inv;
149 let u2 = *r * s_inv;
150 let x = ProjectivePoint::<C>::mul_by_generator_and_mul_add_vartime(&u1, &u2, q)
151 .to_affine()
152 .x();
153
154 if *r == bytes2scalar::<C>(&x) {
155 Ok(())
156 } else {
157 Err(Error::new())
158 }
159}
160
161/// Convert the provided bytestring into a `Scalar` for the given curve, interpreting it as big
162/// endian, zero-padding or truncating it to the bit length of `n` (curve order) if necessary,
163/// and then reducing it mod `n`.
164pub(crate) fn bytes2scalar<C: EcdsaCurve + CurveArithmetic>(mut bytes: &[u8]) -> Scalar<C> {
165 // Compute number of bytes in `n` (curve order)
166 let n_bits = C::ORDER.bits();
167 let n_bytes = usize::try_from(n_bits.div_ceil(8)).expect("overflow");
168 if bytes.len() > n_bytes {
169 bytes = &bytes[..n_bytes];
170 }
171
172 <Scalar<C> as Reduce<C::Uint>>::reduce(&C::Uint::from_be_slice_truncated(bytes, n_bits))
173}