rfc6979: decouple from crypto-bigint (#639)

The previous approach complicates supporting ECDSA with `FieldSize`
which is different from `C::Uint::ByteSize`.

The new implementation eliminates `crypto-bigint` as a dependency and
makes the API entirely byte-oriented.

This is a simpler approach which also eliminates some previous trait
bounds on the generic ECDSA implementation.
This commit is contained in:
Tony Arcieri
2023-01-28 17:16:22 -07:00
committed by GitHub
parent 29163c5e95
commit bd7dfd1e34
8 changed files with 160 additions and 68 deletions
+18 -17
View File
@@ -9,20 +9,21 @@ on:
- cron: "0 0 * * *"
jobs:
security_audit:
name: Security Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
profile: minimal
- uses: actions/cache@v3
with:
path: ~/.cargo/bin
key: ${{ runner.os }}-cargo-audit-v0.17.2
- uses: actions-rs/audit-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
# TODO(tarcieri): re-enable when elliptic-curve has been bumped
# security_audit:
# name: Security Audit
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v3
# - uses: actions-rs/toolchain@v1
# with:
# toolchain: stable
# override: true
# profile: minimal
# - uses: actions/cache@v3
# with:
# path: ~/.cargo/bin
# key: ${{ runner.os }}-cargo-audit-v0.17.2
# - uses: actions-rs/audit-check@v1
# with:
# token: ${{ secrets.GITHUB_TOKEN }}
Generated
+2 -2
View File
@@ -639,10 +639,10 @@ dependencies = [
name = "rfc6979"
version = "0.4.0-pre"
dependencies = [
"crypto-bigint 0.5.0-pre.1",
"hex-literal",
"hmac",
"sha2 0.10.6",
"zeroize",
"subtle",
]
[[package]]
+17 -9
View File
@@ -18,11 +18,11 @@ use elliptic_curve::{bigint::Integer, FieldBytes, PrimeCurve};
use {
crate::{RecoveryId, SignatureSize},
elliptic_curve::{
group::Curve as _,
ff::PrimeField,
group::{Curve as _, Group},
ops::{Invert, LinearCombination, MulByGenerator, Reduce},
subtle::CtOption,
AffineXCoordinate, AffineYIsOdd, CurveArithmetic, Field, Group, IsHigh, ProjectivePoint,
Scalar,
AffineXCoordinate, AffineYIsOdd, CurveArithmetic, IsHigh, ProjectivePoint, Scalar,
},
};
@@ -39,7 +39,7 @@ use {
use crate::{elliptic_curve::generic_array::ArrayLength, Signature};
#[cfg(feature = "rfc6979")]
use elliptic_curve::ScalarPrimitive;
use elliptic_curve::{bigint::ArrayEncoding, ScalarPrimitive};
/// Try to sign the given prehashed message using ECDSA.
///
@@ -47,7 +47,12 @@ use elliptic_curve::ScalarPrimitive;
/// secret scalar via `&self`, such as particular curve's `Scalar` type.
#[cfg(feature = "arithmetic")]
pub trait SignPrimitive<C>:
AsRef<Self> + Field + Into<FieldBytes<C>> + IsHigh + Reduce<C::Uint> + Sized
AsRef<Self>
+ Into<FieldBytes<C>>
+ IsHigh
+ PrimeField<Repr = FieldBytes<C>>
+ Reduce<C::Uint>
+ Sized
where
C: PrimeCurve + CurveArithmetic + CurveArithmetic<Scalar = Self>,
SignatureSize<C>: ArrayLength<u8>,
@@ -123,12 +128,15 @@ where
) -> Result<(Signature<C>, Option<RecoveryId>)>
where
Self: From<ScalarPrimitive<C>>,
C::Uint: for<'a> From<&'a Self>,
D: Digest + BlockSizeUser + FixedOutput<OutputSize = FieldSize<C>> + FixedOutputReset,
{
let x = C::Uint::from(self);
let k = rfc6979::generate_k::<D, C::Uint>(&x, &C::ORDER, &z, ad);
let k = Self::from(ScalarPrimitive::<C>::new(*k).unwrap());
let k = C::Uint::from_be_byte_array(rfc6979::generate_k::<D, FieldSize<C>>(
&self.to_repr(),
&C::ORDER.to_be_byte_array(),
&z,
ad,
));
let k = Self::from(ScalarPrimitive::<C>::new(k).unwrap());
self.try_sign_prehashed(k, z)
}
}
-4
View File
@@ -176,7 +176,6 @@ impl From<RecoveryId> for u8 {
impl<C> SigningKey<C>
where
C: PrimeCurve + CurveArithmetic + DigestPrimitive,
C::Uint: for<'a> From<&'a Scalar<C>>,
Scalar<C>: Invert<Output = CtOption<Scalar<C>>> + Reduce<C::Uint> + SignPrimitive<C>,
SignatureSize<C>: ArrayLength<u8>,
{
@@ -209,7 +208,6 @@ where
impl<C, D> DigestSigner<D, (Signature<C>, RecoveryId)> for SigningKey<C>
where
C: PrimeCurve + CurveArithmetic + DigestPrimitive,
C::Uint: for<'a> From<&'a Scalar<C>>,
D: Digest,
Scalar<C>: Invert<Output = CtOption<Scalar<C>>> + Reduce<C::Uint> + SignPrimitive<C>,
SignatureSize<C>: ArrayLength<u8>,
@@ -223,7 +221,6 @@ where
impl<C> PrehashSigner<(Signature<C>, RecoveryId)> for SigningKey<C>
where
C: PrimeCurve + CurveArithmetic + DigestPrimitive,
C::Uint: for<'a> From<&'a Scalar<C>>,
Scalar<C>: Invert<Output = CtOption<Scalar<C>>> + Reduce<C::Uint> + SignPrimitive<C>,
SignatureSize<C>: ArrayLength<u8>,
{
@@ -236,7 +233,6 @@ where
impl<C> Signer<(Signature<C>, RecoveryId)> for SigningKey<C>
where
C: PrimeCurve + CurveArithmetic + DigestPrimitive,
C::Uint: for<'a> From<&'a Scalar<C>>,
Scalar<C>: Invert<Output = CtOption<Scalar<C>>> + Reduce<C::Uint> + SignPrimitive<C>,
SignatureSize<C>: ArrayLength<u8>,
{
-10
View File
@@ -111,7 +111,6 @@ where
impl<C, D> DigestSigner<D, Signature<C>> for SigningKey<C>
where
C: PrimeCurve + CurveArithmetic + DigestPrimitive,
C::Uint: for<'a> From<&'a Scalar<C>>,
D: Digest + FixedOutput<OutputSize = FieldSize<C>>,
Scalar<C>: Invert<Output = CtOption<Scalar<C>>> + Reduce<C::Uint> + SignPrimitive<C>,
SignatureSize<C>: ArrayLength<u8>,
@@ -128,7 +127,6 @@ where
impl<C> PrehashSigner<Signature<C>> for SigningKey<C>
where
C: PrimeCurve + CurveArithmetic + DigestPrimitive,
C::Uint: for<'a> From<&'a Scalar<C>>,
Scalar<C>: Invert<Output = CtOption<Scalar<C>>> + Reduce<C::Uint> + SignPrimitive<C>,
SignatureSize<C>: ArrayLength<u8>,
{
@@ -148,7 +146,6 @@ where
impl<C> Signer<Signature<C>> for SigningKey<C>
where
C: PrimeCurve + CurveArithmetic + DigestPrimitive,
C::Uint: for<'a> From<&'a Scalar<C>>,
Scalar<C>: Invert<Output = CtOption<Scalar<C>>> + Reduce<C::Uint> + SignPrimitive<C>,
SignatureSize<C>: ArrayLength<u8>,
{
@@ -160,7 +157,6 @@ where
impl<C, D> RandomizedDigestSigner<D, Signature<C>> for SigningKey<C>
where
C: PrimeCurve + CurveArithmetic + DigestPrimitive,
C::Uint: for<'a> From<&'a Scalar<C>>,
D: Digest + FixedOutput<OutputSize = FieldSize<C>>,
Scalar<C>: Invert<Output = CtOption<Scalar<C>>> + Reduce<C::Uint> + SignPrimitive<C>,
SignatureSize<C>: ArrayLength<u8>,
@@ -177,7 +173,6 @@ where
impl<C> RandomizedPrehashSigner<Signature<C>> for SigningKey<C>
where
C: PrimeCurve + CurveArithmetic + DigestPrimitive,
C::Uint: for<'a> From<&'a Scalar<C>>,
Scalar<C>: Invert<Output = CtOption<Scalar<C>>> + Reduce<C::Uint> + SignPrimitive<C>,
SignatureSize<C>: ArrayLength<u8>,
{
@@ -212,7 +207,6 @@ where
impl<C> PrehashSigner<der::Signature<C>> for SigningKey<C>
where
C: PrimeCurve + CurveArithmetic + DigestPrimitive,
C::Uint: for<'a> From<&'a Scalar<C>>,
Scalar<C>: Invert<Output = CtOption<Scalar<C>>> + Reduce<C::Uint> + SignPrimitive<C>,
SignatureSize<C>: ArrayLength<u8>,
der::MaxSize<C>: ArrayLength<u8>,
@@ -227,7 +221,6 @@ where
impl<C> Signer<der::Signature<C>> for SigningKey<C>
where
C: PrimeCurve + CurveArithmetic + DigestPrimitive,
C::Uint: for<'a> From<&'a Scalar<C>>,
Scalar<C>: Invert<Output = CtOption<Scalar<C>>> + Reduce<C::Uint> + SignPrimitive<C>,
SignatureSize<C>: ArrayLength<u8>,
der::MaxSize<C>: ArrayLength<u8>,
@@ -242,7 +235,6 @@ where
impl<C, D> RandomizedDigestSigner<D, der::Signature<C>> for SigningKey<C>
where
C: PrimeCurve + CurveArithmetic + DigestPrimitive,
C::Uint: for<'a> From<&'a Scalar<C>>,
D: Digest + FixedOutput<OutputSize = FieldSize<C>>,
Scalar<C>: Invert<Output = CtOption<Scalar<C>>> + Reduce<C::Uint> + SignPrimitive<C>,
SignatureSize<C>: ArrayLength<u8>,
@@ -263,7 +255,6 @@ where
impl<C> RandomizedPrehashSigner<der::Signature<C>> for SigningKey<C>
where
C: PrimeCurve + CurveArithmetic + DigestPrimitive,
C::Uint: for<'a> From<&'a Scalar<C>>,
Scalar<C>: Invert<Output = CtOption<Scalar<C>>> + Reduce<C::Uint> + SignPrimitive<C>,
SignatureSize<C>: ArrayLength<u8>,
der::MaxSize<C>: ArrayLength<u8>,
@@ -283,7 +274,6 @@ where
impl<C> RandomizedSigner<der::Signature<C>> for SigningKey<C>
where
C: PrimeCurve + CurveArithmetic + DigestPrimitive,
C::Uint: for<'a> From<&'a Scalar<C>>,
Scalar<C>: Invert<Output = CtOption<Scalar<C>>> + Reduce<C::Uint> + SignPrimitive<C>,
SignatureSize<C>: ArrayLength<u8>,
der::MaxSize<C>: ArrayLength<u8>,
+2 -2
View File
@@ -15,9 +15,9 @@ edition = "2021"
rust-version = "1.61"
[dependencies]
crypto-bigint = { version = "=0.5.0-pre.1", default-features = false, features = ["generic-array", "zeroize"] }
hmac = { version = "0.12", default-features = false, features = ["reset"] }
zeroize = { version = "1", default-features = false }
subtle = { version = "2", default-features = false }
[dev-dependencies]
hex-literal = "0.3"
sha2 = "0.10"
+87
View File
@@ -0,0 +1,87 @@
//! Constant-time comparison helpers for [`ByteArray`].
use crate::{ArrayLength, ByteArray};
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
/// Constant-time equals.
pub(crate) fn ct_eq<N: ArrayLength<u8>>(a: &ByteArray<N>, b: &ByteArray<N>) -> Choice {
let mut ret = Choice::from(1);
for (a, b) in a.iter().zip(b.iter()) {
ret.conditional_assign(&Choice::from(0), !a.ct_eq(b));
}
ret
}
/// Constant-time less than.
///
/// Inputs are interpreted as big endian integers.
pub(crate) fn ct_lt<N: ArrayLength<u8>>(a: &ByteArray<N>, b: &ByteArray<N>) -> Choice {
let mut borrow = 0;
// Perform subtraction with borrow a byte-at-a-time, interpreting a
// no-borrow condition as the less-than case
for (&a, &b) in a.iter().zip(b.iter()).rev() {
let c = (b as u16).wrapping_add(borrow >> (u8::BITS - 1));
borrow = (a as u16).wrapping_sub(c) >> u8::BITS as u8;
}
!borrow.ct_eq(&0)
}
#[cfg(test)]
mod tests {
const A: [u8; 4] = [0, 0, 0, 0];
const B: [u8; 4] = [0, 0, 0, 1];
const C: [u8; 4] = [0xFF, 0, 0, 0];
const D: [u8; 4] = [0xFF, 0, 0, 1];
const E: [u8; 4] = [0xFF, 0xFF, 0xFF, 0xFE];
const F: [u8; 4] = [0xFF, 0xFF, 0xFF, 0xFF];
#[test]
fn ct_eq() {
use super::ct_eq;
assert_eq!(ct_eq(&A.into(), &A.into()).unwrap_u8(), 1);
assert_eq!(ct_eq(&B.into(), &B.into()).unwrap_u8(), 1);
assert_eq!(ct_eq(&C.into(), &C.into()).unwrap_u8(), 1);
assert_eq!(ct_eq(&D.into(), &D.into()).unwrap_u8(), 1);
assert_eq!(ct_eq(&E.into(), &E.into()).unwrap_u8(), 1);
assert_eq!(ct_eq(&F.into(), &F.into()).unwrap_u8(), 1);
assert_eq!(ct_eq(&A.into(), &B.into()).unwrap_u8(), 0);
assert_eq!(ct_eq(&C.into(), &D.into()).unwrap_u8(), 0);
assert_eq!(ct_eq(&E.into(), &F.into()).unwrap_u8(), 0);
}
#[test]
fn ct_lt() {
use super::ct_lt;
assert_eq!(ct_lt(&A.into(), &A.into()).unwrap_u8(), 0);
assert_eq!(ct_lt(&B.into(), &B.into()).unwrap_u8(), 0);
assert_eq!(ct_lt(&C.into(), &C.into()).unwrap_u8(), 0);
assert_eq!(ct_lt(&D.into(), &D.into()).unwrap_u8(), 0);
assert_eq!(ct_lt(&E.into(), &E.into()).unwrap_u8(), 0);
assert_eq!(ct_lt(&F.into(), &F.into()).unwrap_u8(), 0);
assert_eq!(ct_lt(&A.into(), &B.into()).unwrap_u8(), 1);
assert_eq!(ct_lt(&A.into(), &C.into()).unwrap_u8(), 1);
assert_eq!(ct_lt(&B.into(), &A.into()).unwrap_u8(), 0);
assert_eq!(ct_lt(&C.into(), &A.into()).unwrap_u8(), 0);
assert_eq!(ct_lt(&B.into(), &C.into()).unwrap_u8(), 1);
assert_eq!(ct_lt(&B.into(), &D.into()).unwrap_u8(), 1);
assert_eq!(ct_lt(&C.into(), &B.into()).unwrap_u8(), 0);
assert_eq!(ct_lt(&D.into(), &B.into()).unwrap_u8(), 0);
assert_eq!(ct_lt(&C.into(), &D.into()).unwrap_u8(), 1);
assert_eq!(ct_lt(&C.into(), &E.into()).unwrap_u8(), 1);
assert_eq!(ct_lt(&D.into(), &C.into()).unwrap_u8(), 0);
assert_eq!(ct_lt(&E.into(), &C.into()).unwrap_u8(), 0);
assert_eq!(ct_lt(&E.into(), &F.into()).unwrap_u8(), 1);
assert_eq!(ct_lt(&F.into(), &E.into()).unwrap_u8(), 0);
}
}
+34 -24
View File
@@ -12,39 +12,46 @@
//! See also: the documentation for the [`generate_k`] function.
//!
//! ```
//! use crypto_bigint::{ArrayEncoding, U256};
//! use hex_literal::hex;
//! use rfc6979::consts::U32;
//! use sha2::{Digest, Sha256};
//!
//! // NIST P-256 field modulus
//! const NIST_P256_MODULUS: U256 =
//! U256::from_be_hex("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551");
//! const NIST_P256_MODULUS: [u8; 32] =
//! hex!("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551");
//!
//! // Public key for RFC6979 NIST P256/SHA256 test case
//! const RFC6979_KEY: U256 =
//! U256::from_be_hex("C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721");
//! const RFC6979_KEY: [u8; 32] =
//! hex!("C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721");
//!
//! // Test message for RFC6979 NIST P256/SHA256 test case
//! const RFC6979_MSG: &[u8; 6] = b"sample";
//!
//! // Expected K for RFC6979 NIST P256/SHA256 test case
//! const RFC6979_EXPECTED_K: U256 =
//! U256::from_be_hex("A6E3C57DD01ABE90086538398355DD4C3B17AA873382B0F24D6129493D8AAD60");
//! const RFC6979_EXPECTED_K: [u8; 32] =
//! hex!("A6E3C57DD01ABE90086538398355DD4C3B17AA873382B0F24D6129493D8AAD60");
//!
//! let h = Sha256::digest(RFC6979_MSG);
//! let aad = b"";
//! let k = rfc6979::generate_k::<Sha256, U256>(&RFC6979_KEY, &NIST_P256_MODULUS, &h, aad);
//! assert_eq!(&k.to_be_byte_array(), &RFC6979_EXPECTED_K.to_be_byte_array());
//! let k = rfc6979::generate_k::<Sha256, U32>(&RFC6979_KEY.into(), &NIST_P256_MODULUS.into(), &h, aad);
//! assert_eq!(k.as_slice(), &RFC6979_EXPECTED_K);
//! ```
use crypto_bigint::{ArrayEncoding, ByteArray, Integer};
mod ct_cmp;
pub use hmac::digest::generic_array::typenum::consts;
use hmac::{
digest::{
core_api::BlockSizeUser, generic_array::GenericArray, Digest, FixedOutput,
FixedOutputReset, Mac,
core_api::BlockSizeUser,
generic_array::{ArrayLength, GenericArray},
Digest, FixedOutput, FixedOutputReset, Mac,
},
SimpleHmac,
};
use zeroize::{Zeroize, Zeroizing};
/// Array of bytes representing a scalar serialized as a big endian integer.
pub type ByteArray<Size> = GenericArray<u8, Size>;
/// Deterministically generate ephemeral scalar `k`.
///
@@ -55,22 +62,25 @@ use zeroize::{Zeroize, Zeroizing};
/// - `h`: hash/digest of input message: must be reduced modulo `n` in advance
/// - `data`: additional associated data, e.g. CSRNG output used as added entropy
#[inline]
pub fn generate_k<D, I>(x: &I, n: &I, h: &ByteArray<I>, data: &[u8]) -> Zeroizing<I>
pub fn generate_k<D, N>(
x: &ByteArray<N>,
n: &ByteArray<N>,
h: &ByteArray<N>,
data: &[u8],
) -> ByteArray<N>
where
D: Digest + BlockSizeUser + FixedOutput<OutputSize = I::ByteSize> + FixedOutputReset,
I: ArrayEncoding + Integer + Zeroize,
D: Digest + BlockSizeUser + FixedOutput<OutputSize = N> + FixedOutputReset,
N: ArrayLength<u8>,
{
let mut x = x.to_be_byte_array();
let mut hmac_drbg = HmacDrbg::<D>::new(&x, h, data);
x.zeroize();
let mut hmac_drbg = HmacDrbg::<D>::new(x, h, data);
loop {
let mut bytes = ByteArray::<I>::default();
hmac_drbg.fill_bytes(&mut bytes);
let k = I::from_be_byte_array(bytes);
let mut k = ByteArray::<N>::default();
hmac_drbg.fill_bytes(&mut k);
if (!k.is_zero() & k.ct_lt(n)).into() {
return Zeroizing::new(k);
let k_is_zero = ct_cmp::ct_eq(&k, &ByteArray::default());
if (!k_is_zero & ct_cmp::ct_lt(&k, n)).into() {
return k;
}
}
}