Skip to content

Commit

Permalink
rfc6979: add generate_k_mut; remove digest bounds
Browse files Browse the repository at this point in the history
Adds an API which writes `k` into an output buffer rather than
allocating and returning it, which also accepts slices as inputs. This
makes it possible to use `rfc6979` to implement the `dsa` crate.

Also removes output size bounds on the underlying digest function, which
aren't actually relevant to the implementation at all since HMAC-DRBG
writes a variable-sized amount of output. This makes it possible to use
`rfc6979` + `ecdsa` in conjunction with `p521`, which has unusually
sized scalars (66-bytes) which don't match the output size of the
underlying digest function (SHA-512, which has a 64-byte output).
  • Loading branch information
tarcieri committed Jan 16, 2024
1 parent 9be9453 commit 0948c21
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 57 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion ecdsa/src/hazmat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ where
) -> Result<(Signature<C>, Option<RecoveryId>)>
where
Self: From<ScalarPrimitive<C>> + Invert<Output = CtOption<Self>>,
D: Digest + BlockSizeUser + FixedOutput<OutputSize = FieldBytesSize<C>> + FixedOutputReset,
D: Digest + BlockSizeUser + FixedOutput + FixedOutputReset,
{
let k = Scalar::<C>::from_repr(rfc6979::generate_k::<D, _>(
&self.to_repr(),
Expand Down
80 changes: 37 additions & 43 deletions rfc6979/src/ct_cmp.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
//! Constant-time comparison helpers for [`ByteArray`].
use crate::{ArraySize, ByteArray};
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};

/// Constant-time equals.
pub(crate) fn ct_eq<N: ArraySize>(a: &ByteArray<N>, b: &ByteArray<N>) -> Choice {
/// Constant-time test that a given byte slice contains only zeroes.
#[inline]
pub(crate) fn ct_is_zero(n: &[u8]) -> 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));
for byte in n {
ret.conditional_assign(&Choice::from(0), byte.ct_ne(&0));
}

ret
Expand All @@ -17,7 +17,10 @@ pub(crate) fn ct_eq<N: ArraySize>(a: &ByteArray<N>, b: &ByteArray<N>) -> Choice
/// Constant-time less than.
///
/// Inputs are interpreted as big endian integers.
pub(crate) fn ct_lt<N: ArraySize>(a: &ByteArray<N>, b: &ByteArray<N>) -> Choice {
#[inline]
pub(crate) fn ct_lt(a: &[u8], b: &[u8]) -> Choice {
debug_assert_eq!(a.len(), b.len());

let mut borrow = 0;

// Perform subtraction with borrow a byte-at-a-time, interpreting a
Expand All @@ -40,48 +43,39 @@ mod tests {
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);
fn ct_is_zero() {
use super::ct_is_zero;
assert_eq!(ct_is_zero(&A).unwrap_u8(), 1);
assert_eq!(ct_is_zero(&B).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);
assert_eq!(ct_lt(&A, &A).unwrap_u8(), 0);
assert_eq!(ct_lt(&B, &B).unwrap_u8(), 0);
assert_eq!(ct_lt(&C, &C).unwrap_u8(), 0);
assert_eq!(ct_lt(&D, &D).unwrap_u8(), 0);
assert_eq!(ct_lt(&E, &E).unwrap_u8(), 0);
assert_eq!(ct_lt(&F, &F).unwrap_u8(), 0);

assert_eq!(ct_lt(&A, &B).unwrap_u8(), 1);
assert_eq!(ct_lt(&A, &C).unwrap_u8(), 1);
assert_eq!(ct_lt(&B, &A).unwrap_u8(), 0);
assert_eq!(ct_lt(&C, &A).unwrap_u8(), 0);

assert_eq!(ct_lt(&B, &C).unwrap_u8(), 1);
assert_eq!(ct_lt(&B, &D).unwrap_u8(), 1);
assert_eq!(ct_lt(&C, &B).unwrap_u8(), 0);
assert_eq!(ct_lt(&D, &B).unwrap_u8(), 0);

assert_eq!(ct_lt(&C, &D).unwrap_u8(), 1);
assert_eq!(ct_lt(&C, &E).unwrap_u8(), 1);
assert_eq!(ct_lt(&D, &C).unwrap_u8(), 0);
assert_eq!(ct_lt(&E, &C).unwrap_u8(), 0);

assert_eq!(ct_lt(&E, &F).unwrap_u8(), 1);
assert_eq!(ct_lt(&F, &E).unwrap_u8(), 0);
}
}
45 changes: 33 additions & 12 deletions rfc6979/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,6 @@ use hmac::{
SimpleHmac,
};

/// Array of bytes representing a scalar serialized as a big endian integer.
pub type ByteArray<Size> = Array<u8, Size>;

/// Deterministically generate ephemeral scalar `k`.
///
/// Accepts the following parameters and inputs:
Expand All @@ -63,24 +60,48 @@ pub type ByteArray<Size> = Array<u8, Size>;
/// - `data`: additional associated data, e.g. CSRNG output used as added entropy
#[inline]
pub fn generate_k<D, N>(
x: &ByteArray<N>,
n: &ByteArray<N>,
h: &ByteArray<N>,
x: &Array<u8, N>,
n: &Array<u8, N>,
h: &Array<u8, N>,
data: &[u8],
) -> ByteArray<N>
) -> Array<u8, N>
where
D: Digest + BlockSizeUser + FixedOutput<OutputSize = N> + FixedOutputReset,
D: Digest + BlockSizeUser + FixedOutput + FixedOutputReset,
N: ArraySize,
{
let mut k = Array::default();
generate_k_mut::<D>(x, n, h, data, &mut k);
k
}

/// Deterministically generate ephemeral scalar `k` by writing it into the provided output buffer.
///
/// This is an API which accepts dynamically sized inputs intended for use cases where the sizes
/// are determined at runtime, such as the legacy Digital Signature Algorithm (DSA).
///
/// Accepts the following parameters and inputs:
///
/// - `x`: secret key
/// - `n`: field modulus
/// - `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_mut<D>(x: &[u8], n: &[u8], h: &[u8], data: &[u8], k: &mut [u8])
where
D: Digest + BlockSizeUser + FixedOutput + FixedOutputReset,
{
assert_eq!(k.len(), x.len());
assert_eq!(k.len(), n.len());
assert_eq!(k.len(), h.len());

let mut hmac_drbg = HmacDrbg::<D>::new(x, h, data);

loop {
let mut k = ByteArray::<N>::default();
hmac_drbg.fill_bytes(&mut k);
hmac_drbg.fill_bytes(k);

let k_is_zero = ct_cmp::ct_eq(&k, &ByteArray::default());
let k_is_zero = ct_cmp::ct_is_zero(&k);
if (!k_is_zero & ct_cmp::ct_lt(&k, n)).into() {
return k;
return;
}
}
}
Expand Down

0 comments on commit 0948c21

Please sign in to comment.