Skip to content

Commit 4c13062

Browse files
committed
[WIP] rfc6979: integrate with crypto-bigint; KGenerator API
Implements functions which are a 1:1 mapping to the ones defined in RFC6979, operating over `U: Unsigned + Encoding`. Notably this also implements the modular reduction instead of leaving it to the caller to perform. As the RFC notes, it's easily implemented as conditional subtraction. The public API has been changed to a `KGenerator` struct which provides an iterator-like API that can be called repeatedly if needed, e.g. if the computed `r` is zero. This makes it possible to make the `ecdsa::hazmat` RFC6979-based functions infallible, because they can automatically retry with the next `k` until they find one that works.
1 parent 83e53c9 commit 4c13062

10 files changed

Lines changed: 502 additions & 446 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ecdsa/src/hazmat.rs

Lines changed: 32 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,16 @@
1313
//! </div>
1414
1515
use crate::{EcdsaCurve, Error, Result};
16-
use core::cmp;
17-
use elliptic_curve::{FieldBytes, array::typenum::Unsigned};
16+
use elliptic_curve::FieldBytes;
1817

1918
#[cfg(feature = "algorithm")]
2019
use {
2120
crate::{RecoveryId, Signature, SignatureSize},
2221
elliptic_curve::{
2322
CurveArithmetic, NonZeroScalar, ProjectivePoint, Scalar,
2423
array::ArraySize,
24+
bigint::{BitOps, Encoding},
2525
ff::PrimeField,
26-
field,
2726
group::{Curve as _, Group},
2827
ops::{Invert, MulByGeneratorVartime, Reduce},
2928
point::AffineCoordinates,
@@ -45,40 +44,6 @@ pub trait DigestAlgorithm: EcdsaCurve {
4544
type Digest: BlockSizeUser + Digest + FixedOutput + FixedOutputReset;
4645
}
4746

48-
/// Partial implementation of the `bits2int` function as defined in
49-
/// [RFC6979 § 2.3.2] as well as [SEC1] § 2.3.8.
50-
///
51-
/// This is used to convert a message digest whose size may be smaller or
52-
/// larger than the size of the curve's scalar field into a serialized
53-
/// (unreduced) field element.
54-
///
55-
/// [RFC6979 § 2.3.2]: https://datatracker.ietf.org/doc/html/rfc6979#section-2.3.2
56-
/// [SEC1]: https://www.secg.org/sec1-v2.pdf
57-
pub fn bits2field<C: EcdsaCurve>(bits: &[u8]) -> Result<FieldBytes<C>> {
58-
// Absolute 128-bit / 16-byte minimum to catch egregious digest misuse
59-
// without rejecting legitimate combinations like SHA-256 with P-521
60-
// (see e.g. XML Signature, which permits such pairings).
61-
if bits.len() < 16 {
62-
return Err(Error::new());
63-
}
64-
65-
let mut field_bytes = FieldBytes::<C>::default();
66-
67-
match bits.len().cmp(&C::FieldBytesSize::USIZE) {
68-
cmp::Ordering::Equal => field_bytes.copy_from_slice(bits),
69-
cmp::Ordering::Less => {
70-
// If bits is smaller than the field size, pad with zeroes on the left
71-
field_bytes[(C::FieldBytesSize::USIZE - bits.len())..].copy_from_slice(bits);
72-
}
73-
cmp::Ordering::Greater => {
74-
// If bits is larger than the field size, truncate
75-
field_bytes.copy_from_slice(&bits[..C::FieldBytesSize::USIZE]);
76-
}
77-
}
78-
79-
Ok(field_bytes)
80-
}
81-
8247
/// Sign a prehashed message digest using the provided secret scalar and
8348
/// ephemeral scalar, returning an ECDSA signature.
8449
///
@@ -107,13 +72,13 @@ pub fn bits2field<C: EcdsaCurve>(bits: &[u8]) -> Result<FieldBytes<C>> {
10772
pub fn sign_prehashed<C>(
10873
d: &NonZeroScalar<C>,
10974
k: &NonZeroScalar<C>,
110-
z: &FieldBytes<C>,
75+
z: &[u8],
11176
) -> Result<(Signature<C>, RecoveryId)>
11277
where
11378
C: EcdsaCurve + CurveArithmetic,
11479
SignatureSize<C>: ArraySize,
11580
{
116-
let z = Scalar::<C>::reduce(z);
81+
let z = bytes2scalar::<C>(z);
11782

11883
// Compute scalar inversion of 𝑘.
11984
let k_inv = k.invert();
@@ -123,7 +88,7 @@ where
12388

12489
// Lift x-coordinate of 𝑹 (element of base field) into a serialized big
12590
// integer, then reduce it into an element of the scalar field.
126-
let r = Scalar::<C>::reduce(&R.x());
91+
let r = bytes2scalar::<C>(&R.x());
12792

12893
// Compute 𝒔 as a signature over 𝒓 and 𝒛.
12994
let s = *k_inv * (z + (r * d.as_ref()));
@@ -153,40 +118,31 @@ where
153118
/// - `z`: message digest to be signed, i.e. `H(m)`. Does not have to be reduced in advance.
154119
/// - `ad`: optional additional data, e.g. added entropy from an RNG
155120
///
156-
/// # Errors
157-
///
158-
/// This will return an error if a zero-scalar was generated. It can be tried again with different
159-
/// entropy `ad`.
160-
///
161121
/// [RFC6979]: https://datatracker.ietf.org/doc/html/rfc6979
162122
#[cfg(feature = "algorithm")]
163123
pub fn sign_prehashed_rfc6979<C, D>(
164124
d: &NonZeroScalar<C>,
165-
z: &FieldBytes<C>,
125+
z: &[u8],
166126
ad: &[u8],
167-
) -> Result<(Signature<C>, RecoveryId)>
127+
) -> (Signature<C>, RecoveryId)
168128
where
169129
C: EcdsaCurve + CurveArithmetic,
170130
D: Digest + BlockSizeUser + FixedOutput + FixedOutputReset,
171131
SignatureSize<C>: ArraySize,
172132
{
173-
// From RFC6979 § 2.4:
174-
//
175-
// H(m) is transformed into an integer modulo q using the bits2int
176-
// transform and an extra modular reduction:
177-
//
178-
// h = bits2int(H(m)) mod q
179-
let z2 = Scalar::<C>::reduce(z);
133+
let order = C::ORDER;
134+
let mut kgen = rfc6979::KGenerator::<D, C::Uint>::new(&d.to_repr(), z, ad, &order);
180135

181-
let k = NonZeroScalar::<C>::from_repr(rfc6979::generate_k::<D, _>(
182-
&d.to_repr(),
183-
&field::uint_to_bytes::<C>(&C::ORDER),
184-
&z2.to_repr(),
185-
ad,
186-
))
187-
.unwrap();
136+
loop {
137+
let mut k_bytes = FieldBytes::<C>::default();
138+
kgen.fill_next_k(&mut k_bytes);
188139

189-
sign_prehashed(d, &k, z)
140+
if let Some(k) = NonZeroScalar::<C>::from_repr(k_bytes).into_option() {
141+
if let Ok(ret) = sign_prehashed(d, &k, z) {
142+
return ret;
143+
}
144+
}
145+
}
190146
}
191147

192148
/// Verify the prehashed message against the provided ECDSA signature.
@@ -198,11 +154,7 @@ where
198154
/// ALGORITHM!!!
199155
/// - `sig`: signature to be verified against the key and message.
200156
#[cfg(feature = "algorithm")]
201-
pub fn verify_prehashed<C>(
202-
q: &ProjectivePoint<C>,
203-
z: &FieldBytes<C>,
204-
sig: &Signature<C>,
205-
) -> Result<()>
157+
pub fn verify_prehashed<C>(q: &ProjectivePoint<C>, z: &[u8], sig: &Signature<C>) -> Result<()>
206158
where
207159
C: EcdsaCurve + CurveArithmetic,
208160
SignatureSize<C>: ArraySize,
@@ -211,7 +163,7 @@ where
211163
return Err(Error::new());
212164
}
213165

214-
let z = Scalar::<C>::reduce(z);
166+
let z = bytes2scalar::<C>(z);
215167
let (r, s) = sig.split_scalars();
216168
let s_inv = *s.invert_vartime();
217169
let u1 = z * s_inv;
@@ -220,53 +172,24 @@ where
220172
.to_affine()
221173
.x();
222174

223-
if *r == Scalar::<C>::reduce(&x) {
175+
if *r == bytes2scalar::<C>(&x) {
224176
Ok(())
225177
} else {
226178
Err(Error::new())
227179
}
228180
}
229181

230-
#[cfg(all(test, feature = "dev"))]
231-
mod tests {
232-
use super::bits2field;
233-
use elliptic_curve::dev::MockCurve;
234-
use hex_literal::hex;
235-
236-
#[test]
237-
fn bits2field_too_small() {
238-
assert!(bits2field::<MockCurve>(b"").is_err());
239-
// 15 bytes is one short of the 128-bit absolute floor.
240-
let prehash = hex!("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
241-
assert!(bits2field::<MockCurve>(&prehash).is_err());
242-
}
243-
244-
#[test]
245-
fn bits2field_size_less() {
246-
let prehash = hex!("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
247-
let field_bytes = bits2field::<MockCurve>(&prehash).expect("field bytes");
248-
assert_eq!(
249-
field_bytes.as_slice(),
250-
&hex!("00000000000000000000000000000000AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
251-
);
252-
}
253-
254-
#[test]
255-
fn bits2field_size_eq() {
256-
let prehash = hex!("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
257-
let field_bytes = bits2field::<MockCurve>(&prehash).expect("field bytes");
258-
assert_eq!(field_bytes.as_slice(), &prehash);
182+
/// Convert the provided bytestring into a `Scalar` for the given curve, interpreting it as big
183+
/// endian, zero-padding or truncating it to the bit length of `n` (curve order) if necessary,
184+
/// and then reducing it mod `n`.
185+
#[cfg(feature = "algorithm")]
186+
pub(crate) fn bytes2scalar<C: EcdsaCurve + CurveArithmetic>(mut bytes: &[u8]) -> Scalar<C> {
187+
// Compute number of bytes in `n` (curve order)
188+
let n_bits = C::ORDER.bits();
189+
let n_bytes = n_bits.div_ceil(8) as usize;
190+
if bytes.len() > n_bytes {
191+
bytes = &bytes[..n_bytes];
259192
}
260193

261-
#[test]
262-
fn bits2field_size_greater() {
263-
let prehash = hex!(
264-
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
265-
);
266-
let field_bytes = bits2field::<MockCurve>(&prehash).expect("field bytes");
267-
assert_eq!(
268-
field_bytes.as_slice(),
269-
&hex!("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
270-
);
271-
}
194+
<Scalar<C> as Reduce<C::Uint>>::reduce(&C::Uint::from_be_slice_truncated(bytes, n_bits))
272195
}

ecdsa/src/recovery.rs

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::{Error, Result};
66
use {
77
crate::{
88
EcdsaCurve, Signature, SignatureSize, SigningKey, VerifyingKey,
9-
hazmat::{DigestAlgorithm, bits2field, sign_prehashed_rfc6979, verify_prehashed},
9+
hazmat::{DigestAlgorithm, bytes2scalar, sign_prehashed_rfc6979, verify_prehashed},
1010
},
1111
digest::{Digest, FixedOutputReset, Update},
1212
elliptic_curve::{
@@ -16,7 +16,7 @@ use {
1616
bigint::CheckedAdd,
1717
field,
1818
ops::Invert,
19-
ops::{LinearCombination, Reduce},
19+
ops::LinearCombination,
2020
point::DecompressPoint,
2121
sec1::{self, FromSec1Point, ToSec1Point},
2222
subtle::CtOption,
@@ -135,7 +135,7 @@ impl RecoveryId {
135135
// Ensure signature verifies with the provided key
136136
verify_prehashed::<C>(
137137
&ProjectivePoint::<C>::from(*verifying_key.as_affine()),
138-
&bits2field::<C>(prehash)?,
138+
prehash,
139139
signature,
140140
)?;
141141

@@ -181,24 +181,22 @@ where
181181
rng: &mut R,
182182
prehash: &[u8],
183183
) -> Result<(Signature<C>, RecoveryId)> {
184-
let z = bits2field::<C>(prehash)?;
185-
186-
loop {
187-
let mut ad = FieldBytes::<C>::default();
188-
rng.try_fill_bytes(&mut ad).map_err(|_| Error::new())?;
189-
190-
if let Ok(result) =
191-
sign_prehashed_rfc6979::<C, C::Digest>(self.as_nonzero_scalar(), &z, &ad)
192-
{
193-
break Ok(result);
194-
}
195-
}
184+
let mut ad = FieldBytes::<C>::default();
185+
rng.try_fill_bytes(&mut ad).map_err(|_| Error::new())?;
186+
Ok(sign_prehashed_rfc6979::<C, C::Digest>(
187+
self.as_nonzero_scalar(),
188+
prehash,
189+
&ad,
190+
))
196191
}
197192

198193
/// Sign the given message prehash, returning a signature and recovery ID.
199194
pub fn sign_prehash_recoverable(&self, prehash: &[u8]) -> Result<(Signature<C>, RecoveryId)> {
200-
let z = bits2field::<C>(prehash)?;
201-
sign_prehashed_rfc6979::<C, C::Digest>(self.as_nonzero_scalar(), &z, &[])
195+
Ok(sign_prehashed_rfc6979::<C, C::Digest>(
196+
self.as_nonzero_scalar(),
197+
prehash,
198+
b"",
199+
))
202200
}
203201

204202
/// Sign the given message digest, returning a signature and recovery ID.
@@ -362,7 +360,7 @@ where
362360
recovery_id: RecoveryId,
363361
) -> Result<Self> {
364362
let (r, s) = signature.split_scalars();
365-
let z = Scalar::<C>::reduce(&bits2field::<C>(prehash)?);
363+
let z = bytes2scalar::<C>(prehash);
366364

367365
let r_bytes = if recovery_id.is_x_reduced() {
368366
let uint = field::bytes_to_uint::<C>(&r.to_repr())

ecdsa/src/signing.rs

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use crate::{
44
EcdsaCurve, Error, Result, Signature, SignatureSize, SignatureWithOid, ecdsa_oid_for_digest,
5-
hazmat::{DigestAlgorithm, bits2field, sign_prehashed_rfc6979},
5+
hazmat::{DigestAlgorithm, sign_prehashed_rfc6979},
66
};
77
use core::fmt::{self, Debug};
88
use digest::{Digest, FixedOutput, const_oid::AssociatedOid};
@@ -157,8 +157,8 @@ where
157157
}
158158
}
159159

160-
/// Sign message prehash using a deterministic ephemeral scalar (`k`)
161-
/// computed using the algorithm described in [RFC6979 § 3.2].
160+
/// Sign message prehash using a deterministic ephemeral scalar (`k`) computed using the algorithm
161+
/// described in [RFC6979 § 3.2].
162162
///
163163
/// [RFC6979 § 3.2]: https://tools.ietf.org/html/rfc6979#section-3
164164
impl<C> PrehashSigner<Signature<C>> for SigningKey<C>
@@ -168,8 +168,7 @@ where
168168
SignatureSize<C>: ArraySize,
169169
{
170170
fn sign_prehash(&self, prehash: &[u8]) -> Result<Signature<C>> {
171-
let z = bits2field::<C>(prehash)?;
172-
Ok(sign_prehashed_rfc6979::<C, C::Digest>(&self.secret_scalar, &z, &[])?.0)
171+
Ok(sign_prehashed_rfc6979::<C, C::Digest>(&self.secret_scalar, prehash, &[]).0)
173172
}
174173
}
175174

@@ -231,18 +230,9 @@ where
231230
rng: &mut R,
232231
prehash: &[u8],
233232
) -> Result<Signature<C>> {
234-
let z = bits2field::<C>(prehash)?;
235-
236-
loop {
237-
let mut ad = FieldBytes::<C>::default();
238-
rng.try_fill_bytes(&mut ad).map_err(|_| Error::new())?;
239-
240-
if let Ok((signature, _)) =
241-
sign_prehashed_rfc6979::<C, C::Digest>(&self.secret_scalar, &z, &ad)
242-
{
243-
break Ok(signature);
244-
}
245-
}
233+
let mut ad = FieldBytes::<C>::default();
234+
rng.try_fill_bytes(&mut ad).map_err(|_| Error::new())?;
235+
Ok(sign_prehashed_rfc6979::<C, C::Digest>(&self.secret_scalar, prehash, &ad).0)
246236
}
247237
}
248238

ecdsa/src/verifying.rs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
33
use crate::{
44
EcdsaCurve, Error, Result, Signature, SignatureSize,
5-
hazmat::{self, DigestAlgorithm, bits2field},
5+
hazmat::{self, DigestAlgorithm},
66
};
77
use core::{cmp::Ordering, fmt::Debug};
88
use digest::{Digest, Update};
99
use elliptic_curve::{
10-
AffinePoint, CurveArithmetic, FieldBytesSize, ProjectivePoint, PublicKey,
10+
AffinePoint, CurveArithmetic, FieldBytesSize, PublicKey,
1111
array::ArraySize,
1212
point::PointCompression,
1313
sec1::{self, CompressedPoint, FromSec1Point, Sec1Point, ToSec1Point},
@@ -163,11 +163,7 @@ where
163163
SignatureSize<C>: ArraySize,
164164
{
165165
fn verify_prehash(&self, prehash: &[u8], signature: &Signature<C>) -> Result<()> {
166-
hazmat::verify_prehashed::<C>(
167-
&ProjectivePoint::<C>::from(*self.inner.as_affine()),
168-
&bits2field::<C>(prehash)?,
169-
signature,
170-
)
166+
hazmat::verify_prehashed::<C>(&(*self.inner.as_affine()).into(), prehash, signature)
171167
}
172168
}
173169

0 commit comments

Comments
 (0)