Skip to content

Commit f6426cf

Browse files
authored
rfc6979: add K-163 test vector; fix nonaligned use (#781)
RFC6979 Appendix A.1. provides a "Detailed Example" which exercises several edge cases in the protocol: - `bits2int` for an input which is not byte-aligned - Rejecting inputs which exceed the modulus This commit adds what was missing from the previous implementation which assumed inputs were always aligned to the size of the digest output: a constant-time right shift by the number of bits by which the modulus is smaller than a byte-aligned value.
1 parent 32edd0d commit f6426cf

File tree

3 files changed

+148
-113
lines changed

3 files changed

+148
-113
lines changed

rfc6979/src/ct.rs

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
//! Constant-time helpers.
2+
// TODO(tarcieri): replace this with `crypto-bigint`?
3+
4+
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
5+
6+
/// Count the number of leading zeros in constant-time.
7+
#[inline]
8+
pub(crate) fn leading_zeros(n: &[u8]) -> u32 {
9+
n[0].leading_zeros()
10+
}
11+
12+
/// Constant-time bitwise right shift.
13+
#[inline]
14+
pub(crate) fn rshift(n: &mut [u8], shift: u32) {
15+
debug_assert!(shift < 8);
16+
let mask = (1 << shift) - 1;
17+
let mut carry = 0;
18+
19+
for byte in n.iter_mut() {
20+
let new_carry = (*byte & mask) << (8 - shift);
21+
*byte = (*byte >> shift) | carry;
22+
carry = new_carry;
23+
}
24+
}
25+
26+
/// Constant-time test that a given byte slice contains only zeroes.
27+
#[inline]
28+
pub(crate) fn is_zero(n: &[u8]) -> Choice {
29+
let mut ret = Choice::from(1);
30+
31+
for byte in n {
32+
ret.conditional_assign(&Choice::from(0), byte.ct_ne(&0));
33+
}
34+
35+
ret
36+
}
37+
38+
/// Constant-time less than.
39+
///
40+
/// Inputs are interpreted as big endian integers.
41+
#[inline]
42+
pub(crate) fn lt(a: &[u8], b: &[u8]) -> Choice {
43+
debug_assert_eq!(a.len(), b.len());
44+
45+
let mut borrow = 0;
46+
47+
// Perform subtraction with borrow a byte-at-a-time, interpreting a
48+
// no-borrow condition as the less-than case
49+
for (&a, &b) in a.iter().zip(b.iter()).rev() {
50+
let c = (b as u16).wrapping_add(borrow >> (u8::BITS - 1));
51+
borrow = (a as u16).wrapping_sub(c) >> u8::BITS as u8;
52+
}
53+
54+
!borrow.ct_eq(&0)
55+
}
56+
57+
#[cfg(test)]
58+
mod tests {
59+
const A: [u8; 4] = [0, 0, 0, 0];
60+
const B: [u8; 4] = [0, 0, 0, 1];
61+
const C: [u8; 4] = [0xFF, 0, 0, 0];
62+
const D: [u8; 4] = [0xFF, 0, 0, 1];
63+
const E: [u8; 4] = [0xFF, 0xFF, 0xFF, 0xFE];
64+
const F: [u8; 4] = [0xFF, 0xFF, 0xFF, 0xFF];
65+
66+
#[test]
67+
fn ct_is_zero() {
68+
use super::is_zero;
69+
assert_eq!(is_zero(&A).unwrap_u8(), 1);
70+
assert_eq!(is_zero(&B).unwrap_u8(), 0);
71+
}
72+
73+
#[test]
74+
fn ct_lt() {
75+
use super::lt;
76+
77+
assert_eq!(lt(&A, &A).unwrap_u8(), 0);
78+
assert_eq!(lt(&B, &B).unwrap_u8(), 0);
79+
assert_eq!(lt(&C, &C).unwrap_u8(), 0);
80+
assert_eq!(lt(&D, &D).unwrap_u8(), 0);
81+
assert_eq!(lt(&E, &E).unwrap_u8(), 0);
82+
assert_eq!(lt(&F, &F).unwrap_u8(), 0);
83+
84+
assert_eq!(lt(&A, &B).unwrap_u8(), 1);
85+
assert_eq!(lt(&A, &C).unwrap_u8(), 1);
86+
assert_eq!(lt(&B, &A).unwrap_u8(), 0);
87+
assert_eq!(lt(&C, &A).unwrap_u8(), 0);
88+
89+
assert_eq!(lt(&B, &C).unwrap_u8(), 1);
90+
assert_eq!(lt(&B, &D).unwrap_u8(), 1);
91+
assert_eq!(lt(&C, &B).unwrap_u8(), 0);
92+
assert_eq!(lt(&D, &B).unwrap_u8(), 0);
93+
94+
assert_eq!(lt(&C, &D).unwrap_u8(), 1);
95+
assert_eq!(lt(&C, &E).unwrap_u8(), 1);
96+
assert_eq!(lt(&D, &C).unwrap_u8(), 0);
97+
assert_eq!(lt(&E, &C).unwrap_u8(), 0);
98+
99+
assert_eq!(lt(&E, &F).unwrap_u8(), 1);
100+
assert_eq!(lt(&F, &E).unwrap_u8(), 0);
101+
}
102+
}

rfc6979/src/ct_cmp.rs

-81
This file was deleted.

rfc6979/src/lib.rs

+46-32
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
//! assert_eq!(k.as_slice(), &RFC6979_EXPECTED_K);
3838
//! ```
3939
40-
mod ct_cmp;
40+
mod ct;
4141

4242
pub use hmac::digest::array::typenum::consts;
4343

@@ -55,53 +55,34 @@ use hmac::{
5555
/// Accepts the following parameters and inputs:
5656
///
5757
/// - `x`: secret key
58-
/// - `n`: field modulus
58+
/// - `q`: field modulus
5959
/// - `h`: hash/digest of input message: must be reduced modulo `n` in advance
6060
/// - `data`: additional associated data, e.g. CSRNG output used as added entropy
6161
#[inline]
6262
pub fn generate_k<D, N>(
6363
x: &Array<u8, N>,
64-
n: &Array<u8, N>,
64+
q: &Array<u8, N>,
6565
h: &Array<u8, N>,
6666
data: &[u8],
6767
) -> Array<u8, N>
6868
where
6969
D: Digest + BlockSizeUser + FixedOutput + FixedOutputReset,
7070
N: ArraySize,
7171
{
72+
let shift = ct::leading_zeros(q);
7273
let mut k = Array::default();
73-
generate_k_mut::<D>(x, n, h, data, &mut k);
74-
k
75-
}
76-
77-
/// Deterministically generate ephemeral scalar `k` by writing it into the provided output buffer.
78-
///
79-
/// This is an API which accepts dynamically sized inputs intended for use cases where the sizes
80-
/// are determined at runtime, such as the legacy Digital Signature Algorithm (DSA).
81-
///
82-
/// Accepts the following parameters and inputs:
83-
///
84-
/// - `x`: secret key
85-
/// - `n`: field modulus
86-
/// - `h`: hash/digest of input message: must be reduced modulo `n` in advance
87-
/// - `data`: additional associated data, e.g. CSRNG output used as added entropy
88-
#[inline]
89-
pub fn generate_k_mut<D>(x: &[u8], n: &[u8], h: &[u8], data: &[u8], k: &mut [u8])
90-
where
91-
D: Digest + BlockSizeUser + FixedOutput + FixedOutputReset,
92-
{
93-
assert_eq!(k.len(), x.len());
94-
assert_eq!(k.len(), n.len());
95-
assert_eq!(k.len(), h.len());
96-
9774
let mut hmac_drbg = HmacDrbg::<D>::new(x, h, data);
9875

9976
loop {
100-
hmac_drbg.fill_bytes(k);
77+
hmac_drbg.fill_bytes(&mut k);
78+
79+
if shift != 0 {
80+
ct::rshift(&mut k, shift);
81+
}
10182

102-
let k_is_zero = ct_cmp::ct_is_zero(k);
103-
if (!k_is_zero & ct_cmp::ct_lt(k, n)).into() {
104-
return;
83+
let k_is_zero = ct::is_zero(&k);
84+
if (!k_is_zero & ct::lt(&k, q)).into() {
85+
return k;
10586
}
10687
}
10788
}
@@ -154,12 +135,21 @@ where
154135

155136
/// Write the next `HMAC_DRBG` output to the given byte slice.
156137
pub fn fill_bytes(&mut self, out: &mut [u8]) {
157-
for out_chunk in out.chunks_mut(self.v.len()) {
138+
let mut out_chunks = out.chunks_exact_mut(self.v.len());
139+
140+
for out_chunk in &mut out_chunks {
158141
self.k.update(&self.v);
159142
self.v = self.k.finalize_reset().into_bytes();
160143
out_chunk.copy_from_slice(&self.v[..out_chunk.len()]);
161144
}
162145

146+
let out_remainder = out_chunks.into_remainder();
147+
if !out_remainder.is_empty() {
148+
self.k.update(&self.v);
149+
self.v = self.k.finalize_reset().into_bytes();
150+
out_remainder.copy_from_slice(&self.v[..out_remainder.len()]);
151+
}
152+
163153
self.k.update(&self.v);
164154
self.k.update(&[0x00]);
165155
self.k =
@@ -168,3 +158,27 @@ where
168158
self.v = self.k.finalize_reset().into_bytes();
169159
}
170160
}
161+
162+
#[cfg(test)]
163+
mod tests {
164+
use crate::{consts::U21, generate_k};
165+
use hex_literal::hex;
166+
use sha2::Sha256;
167+
168+
/// "Detailed Example" from RFC6979 Appendix A.1.
169+
///
170+
/// Example for ECDSA on the curve K-163 described in FIPS 186-4 (also known as
171+
/// "ansix9t163k1" in X9.62), defined over a field GF(2^163)
172+
#[test]
173+
fn k163_sha256() {
174+
let q = hex!("04000000000000000000020108A2E0CC0D99F8A5EF");
175+
let x = hex!("009A4D6792295A7F730FC3F2B49CBC0F62E862272F");
176+
177+
// Note: SHA-256 digest of "sample" with the output run through `bits2octets` transform
178+
let h2 = hex!("01795EDF0D54DB760F156D0DAC04C0322B3A204224");
179+
180+
let aad = b"";
181+
let k = generate_k::<Sha256, U21>(&x.into(), &q.into(), &h2.into(), aad);
182+
assert_eq!(k, hex!("023AF4074C90A02B3FE61D286D5C87F425E6BDD81B"));
183+
}
184+
}

0 commit comments

Comments
 (0)