rfc6979: initial crate (#409)

Splits out the RFC6979 deterministic nonce generation so it can
(eventually) be used for DSA as well as ECDSA.

The implementation is generic over a `Digest` as well as a `UInt` type
as defined by `crypto-bigint`.

It's also `no_std` friendly and avoids making heap allocations.
This commit is contained in:
Tony Arcieri
2021-11-21 11:33:16 -07:00
committed by GitHub
parent 7e6295ace4
commit a2c7fa2419
13 changed files with 280 additions and 156 deletions
Generated
+13 -4
View File
@@ -161,9 +161,9 @@ name = "ecdsa"
version = "0.13.0-pre"
dependencies = [
"der 0.5.1",
"elliptic-curve 0.11.0",
"elliptic-curve 0.11.1",
"hex-literal",
"hmac",
"rfc6979",
"sha2",
"signature",
]
@@ -221,9 +221,9 @@ dependencies = [
[[package]]
name = "elliptic-curve"
version = "0.11.0"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be5ff748dcb04505ce82b6447d4bad4ef9cf653a1d811bed2b04083127a44c3f"
checksum = "f73d21281779c2c22cade2a4ab34eee34271869942546a364f7d552d30c62434"
dependencies = [
"crypto-bigint 0.3.2",
"der 0.5.1",
@@ -473,6 +473,15 @@ dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "rfc6979"
version = "0.0.0"
dependencies = [
"crypto-bigint 0.3.2",
"hmac",
"zeroize",
]
[[package]]
name = "ring"
version = "0.16.20"
+5 -1
View File
@@ -1,3 +1,7 @@
[workspace]
resolver = "2"
members = ["ecdsa", "ed25519"]
members = [
"ecdsa",
"ed25519",
"rfc6979"
]
+7 -7
View File
@@ -2,12 +2,12 @@
name = "ecdsa"
version = "0.13.0-pre" # Also update html_root_url in lib.rs when bumping this
description = """
Signature and elliptic curve types providing interoperable support for the
Elliptic Curve Digital Signature Algorithm (ECDSA)
Pure Rust implementation of the Elliptic Curve Digital Signature Algorithm
(ECDSA) as specified in FIPS 186-4 (Digital Signature Standard)
"""
authors = ["RustCrypto Developers"]
license = "Apache-2.0 OR MIT"
repository = "https://github.com/RustCrypto/signatures"
repository = "https://github.com/RustCrypto/signatures/tree/master/ecdsa"
readme = "README.md"
categories = ["cryptography", "no-std"]
keywords = ["crypto", "ecc", "nist", "secp256k1", "signature"]
@@ -15,12 +15,12 @@ edition = "2021"
rust-version = "1.56"
[dependencies]
elliptic-curve = { version = "0.11", default-features = false, features = ["sec1"] }
hmac = { version = "0.11", optional = true, default-features = false }
signature = { version = ">= 1.3.1", default-features = false, features = ["rand-preview"] }
elliptic-curve = { version = "0.11.1", default-features = false, features = ["sec1"] }
signature = { version = ">= 1.3.1, <1.5", default-features = false, features = ["rand-preview"] }
# optional dependencies
der = { version = "0.5", optional = true }
rfc6979 = { version = "0", optional = true, path = "../rfc6979" }
[dev-dependencies]
elliptic-curve = { version = "0.11", default-features = false, features = ["dev"] }
@@ -37,7 +37,7 @@ hazmat = []
pkcs8 = ["elliptic-curve/pkcs8", "der"]
pem = ["elliptic-curve/pem", "pkcs8"]
serde = ["elliptic-curve/serde"]
sign = ["arithmetic", "digest", "hazmat", "hmac"]
sign = ["arithmetic", "digest", "hazmat", "rfc6979"]
std = ["alloc", "elliptic-curve/std", "signature/std"]
verify = ["arithmetic", "digest", "hazmat"]
+7 -3
View File
@@ -1,4 +1,4 @@
# RustCrypto: ECDSA
# [RustCrypto]: ECDSA
[![crate][crate-image]][crate-link]
[![Docs][docs-image]][docs-link]
@@ -57,8 +57,12 @@ dual licensed as above, without any additional terms or conditions.
[rustc-image]: https://img.shields.io/badge/rustc-1.56+-blue.svg
[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg
[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260048-signatures
[build-image]: https://github.com/RustCrypto/signatures/workflows/ecdsa/badge.svg?branch=master&event=push
[build-link]: https://github.com/RustCrypto/signatures/actions?query=workflow%3Aecdsa
[build-image]: https://github.com/RustCrypto/signatures/actions/workflows/ecdsa.yml/badge.svg
[build-link]: https://github.com/RustCrypto/signatures/actions/workflows/ecdsa.yml
[//]: # (links)
[RustCrypto]: https://github.com/RustCrypto
[//]: # (footnotes)
+6 -6
View File
@@ -69,10 +69,6 @@ pub mod dev;
#[cfg_attr(docsrs, doc(cfg(feature = "hazmat")))]
pub mod hazmat;
#[cfg(feature = "sign")]
#[cfg_attr(docsrs, doc(cfg(feature = "sign")))]
pub mod rfc6979;
#[cfg(feature = "sign")]
mod sign;
@@ -87,13 +83,17 @@ pub use elliptic_curve::{self, sec1::EncodedPoint, PrimeCurve};
// Re-export the `signature` crate (and select types)
pub use signature::{self, Error, Result};
#[cfg(feature = "rfc6979")]
#[cfg_attr(docsrs, doc(cfg(feature = "rfc6979")))]
pub use rfc6979;
#[cfg(feature = "sign")]
#[cfg_attr(docsrs, doc(cfg(feature = "sign")))]
pub use sign::SigningKey;
pub use crate::sign::SigningKey;
#[cfg(feature = "verify")]
#[cfg_attr(docsrs, doc(cfg(feature = "verify")))]
pub use verify::VerifyingKey;
pub use crate::verify::VerifyingKey;
use core::{
fmt::{self, Debug},
-128
View File
@@ -1,128 +0,0 @@
//! Support for computing deterministic ECDSA ephemeral scalar (`k`).
//!
//! Implementation of the algorithm described in RFC 6979 (Section 3.2):
//! <https://tools.ietf.org/html/rfc6979#section-3>
use elliptic_curve::{
generic_array::GenericArray,
group::ff::PrimeField,
ops::{Invert, Reduce},
zeroize::{Zeroize, Zeroizing},
FieldBytes, FieldSize, NonZeroScalar, PrimeCurve, ProjectiveArithmetic, Scalar,
};
use hmac::{Hmac, Mac, NewMac};
use signature::digest::{BlockInput, FixedOutput, Reset, Update};
/// Generate ephemeral scalar `k` from the secret scalar and a digest of the
/// input message.
pub fn generate_k<C, D>(
secret_scalar: &NonZeroScalar<C>,
msg_digest: D,
additional_data: &[u8],
) -> Zeroizing<NonZeroScalar<C>>
where
C: PrimeCurve + ProjectiveArithmetic,
D: FixedOutput<OutputSize = FieldSize<C>> + BlockInput + Clone + Default + Reset + Update,
Scalar<C>: Invert<Output = Scalar<C>> + Reduce<C::UInt>,
{
let mut x = secret_scalar.to_repr();
let h1 = Scalar::<C>::from_be_bytes_reduced(msg_digest.finalize_fixed()).to_repr();
let mut hmac_drbg = HmacDrbg::<D>::new(&x, &h1, additional_data);
x.zeroize();
loop {
let mut tmp = FieldBytes::<C>::default();
hmac_drbg.generate_into(&mut tmp);
if let Some(k) = NonZeroScalar::from_repr(tmp).into() {
return Zeroizing::new(k);
}
}
}
/// Internal implementation of `HMAC_DRBG` as described in NIST SP800-90A:
/// <https://csrc.nist.gov/publications/detail/sp/800-90a/rev-1/final>
///
/// This is a HMAC-based deterministic random bit generator used internally
/// to compute a deterministic ECDSA ephemeral scalar `k`.
// TODO(tarcieri): use `hmac-drbg` crate when sorpaas/rust-hmac-drbg#3 is merged
struct HmacDrbg<D>
where
D: BlockInput + FixedOutput + Clone + Default + Reset + Update,
{
/// HMAC key `K` (see RFC 6979 Section 3.2.c)
k: Hmac<D>,
/// Chaining value `V` (see RFC 6979 Section 3.2.c)
v: GenericArray<u8, D::OutputSize>,
}
impl<D> HmacDrbg<D>
where
D: BlockInput + FixedOutput + Clone + Default + Reset + Update,
{
/// Initialize `HMAC_DRBG`
pub fn new(entropy_input: &[u8], nonce: &[u8], additional_data: &[u8]) -> Self {
let mut k = Hmac::new(&Default::default());
let mut v = GenericArray::default();
for b in &mut v {
*b = 0x01;
}
for i in 0..=1 {
k.update(&v);
k.update(&[i]);
k.update(entropy_input);
k.update(nonce);
k.update(additional_data);
k = Hmac::new_from_slice(&k.finalize().into_bytes()).expect("HMAC error");
// Steps 3.2.e,g: v = HMAC_k(v)
k.update(&v);
v = k.finalize_reset().into_bytes();
}
Self { k, v }
}
/// Get the next `HMAC_DRBG` output
pub fn generate_into(&mut self, out: &mut [u8]) {
for out_chunk in out.chunks_mut(self.v.len()) {
self.k.update(&self.v);
self.v = self.k.finalize_reset().into_bytes();
out_chunk.copy_from_slice(&self.v[..out_chunk.len()]);
}
self.k.update(&self.v);
self.k.update(&[0x00]);
self.k = Hmac::new_from_slice(&self.k.finalize_reset().into_bytes()).expect("HMAC error");
self.k.update(&self.v);
self.v = self.k.finalize_reset().into_bytes();
}
}
#[cfg(test)]
mod tests {
use super::generate_k;
use elliptic_curve::{dev::NonZeroScalar, group::ff::PrimeField};
use hex_literal::hex;
use sha2::{Digest, Sha256};
/// Test vector from RFC 6979 Appendix 2.5 (NIST P-256 + SHA-256)
/// <https://tools.ietf.org/html/rfc6979#appendix-A.2.5>
#[test]
fn appendix_2_5_test_vector() {
let x = NonZeroScalar::from_repr(
hex!("c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721").into(),
)
.unwrap();
let digest = Sha256::new().chain("sample");
let k = generate_k(&x, digest, &[]);
assert_eq!(
k.to_repr().as_slice(),
&hex!("a6e3c57dd01abe90086538398355dd4c3b17aa873382b0f24d6129493d8aad60")[..]
);
}
}
+25 -5
View File
@@ -13,7 +13,9 @@ use elliptic_curve::{
ops::{Invert, Reduce},
subtle::{Choice, ConstantTimeEq},
zeroize::Zeroize,
FieldBytes, FieldSize, NonZeroScalar, PrimeCurve, ProjectiveArithmetic, Scalar, SecretKey,
zeroize::Zeroizing,
FieldBytes, FieldSize, NonZeroScalar, PrimeCurve, ProjectiveArithmetic, Scalar, ScalarCore,
SecretKey,
};
use signature::{
digest::{BlockInput, Digest, FixedOutput, Reset, Update},
@@ -176,8 +178,17 @@ where
/// computed using the algorithm described in RFC 6979 (Section 3.2):
/// <https://tools.ietf.org/html/rfc6979#section-3>
fn try_sign_digest(&self, msg_digest: D) -> Result<Signature<C>> {
let k = rfc6979::generate_k(&self.inner, msg_digest.clone(), &[]);
let x = Zeroizing::new(ScalarCore::<C>::from(self.inner));
let msg_scalar = Scalar::<C>::from_be_bytes_reduced(msg_digest.finalize_fixed());
let k = Zeroizing::new(
NonZeroScalar::<C>::from_uint(*rfc6979::generate_k::<D, _>(
x.as_uint(),
&C::ORDER,
&msg_scalar.to_repr(),
&[],
))
.unwrap(),
);
Ok(self.inner.try_sign_prehashed(**k, msg_scalar)?.0)
}
}
@@ -209,11 +220,20 @@ where
mut rng: impl CryptoRng + RngCore,
msg_digest: D,
) -> Result<Signature<C>> {
let mut added_entropy = FieldBytes::<C>::default();
rng.fill_bytes(&mut added_entropy);
let mut entropy = FieldBytes::<C>::default();
rng.fill_bytes(&mut entropy);
let k = rfc6979::generate_k(&self.inner, msg_digest.clone(), &added_entropy);
let x = Zeroizing::new(ScalarCore::<C>::from(self.inner));
let msg_scalar = Scalar::<C>::from_be_bytes_reduced(msg_digest.finalize_fixed());
let k = Zeroizing::new(
NonZeroScalar::<C>::from_uint(*rfc6979::generate_k::<D, _>(
x.as_uint(),
&C::ORDER,
&msg_scalar.to_repr(),
&entropy,
))
.unwrap(),
);
Ok(self.inner.try_sign_prehashed(**k, msg_scalar)?.0)
}
}
+6 -2
View File
@@ -1,4 +1,4 @@
# RustCrypto: Ed25519
# [RustCrypto]: Ed25519
[![crate][crate-image]][crate-link]
[![Docs][docs-image]][docs-link]
@@ -57,7 +57,11 @@ dual licensed as above, without any additional terms or conditions.
[build-image]: https://github.com/RustCrypto/signatures/workflows/ed25519/badge.svg?branch=master&event=push
[build-link]: https://github.com/RustCrypto/signatures/actions?query=workflow%3Aed25519
[//]: # (general links)
[//]: # (links)
[RustCrypto]: https://github.com/RustCrypto
[//]: # (footnotes)
[1]: https://en.wikipedia.org/wiki/EdDSA
[2]: https://tools.ietf.org/html/rfc8032
+5
View File
@@ -0,0 +1,5 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+24
View File
@@ -0,0 +1,24 @@
[package]
name = "rfc6979"
version = "0.0.0" # Also update html_root_url in lib.rs when bumping this
description = """
Pure Rust implementation of RFC6979: Deterministic Usage of the
Digital Signature Algorithm (DSA) and Elliptic Curve Digital Signature Algorithm (ECDSA)
"""
authors = ["RustCrypto Developers"]
license = "Apache-2.0 OR MIT"
repository = "https://github.com/RustCrypto/signatures/tree/master/rfc6979"
readme = "README.md"
categories = ["cryptography", "no-std"]
keywords = ["dsa", "ecdsa", "signature"]
edition = "2021"
rust-version = "1.56"
[dependencies]
crypto-bigint = { version = "0.3", default-features = false, features = ["generic-array", "zeroize"] }
hmac = { version = "0.11", default-features = false }
zeroize = { version = "1", default-features = false }
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
+55
View File
@@ -0,0 +1,55 @@
# [RustCrypto]: RFC6979 Deterministic Signatures
[![crate][crate-image]][crate-link]
[![Docs][docs-image]][docs-link]
![Apache2/MIT licensed][license-image]
![MSRV][rustc-image]
[![Project Chat][chat-image]][chat-link]
[![Build Status][build-image]][build-link]
Pure Rust implementation of RFC6979: Deterministic Usage of the
Digital Signature Algorithm (DSA) and Elliptic Curve Digital Signature Algorithm (ECDSA).
Algorithm described in RFC 6979 § 3.2:
<https://tools.ietf.org/html/rfc6979#section-3>
[Documentation][docs-link]
## Minimum Supported Rust Version
This crate requires **Rust 1.56** at a minimum.
We may change the MSRV in the future, but it will be accompanied by a minor
version bump.
## License
All crates licensed under either of
* [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
* [MIT license](http://opensource.org/licenses/MIT)
at your option.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.
[//]: # (badges)
[crate-image]: https://img.shields.io/crates/v/rfc6979.svg
[crate-link]: https://crates.io/crates/rfc6979
[docs-image]: https://docs.rs/rfc6979/badge.svg
[docs-link]: https://docs.rs/rfc6979/
[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg
[rustc-image]: https://img.shields.io/badge/rustc-1.56+-blue.svg
[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg
[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260048-signatures
[build-image]: https://github.com/RustCrypto/signatures/actions/workflows/rfc6979.yml/badge.svg
[build-link]: https://github.com/RustCrypto/signatures/actions/workflows/rfc6979.yml
[//]: # (links)
[RustCrypto]: https://github.com/RustCrypto
+113
View File
@@ -0,0 +1,113 @@
#![doc = include_str!("../README.md")]
//! ## Usage
//!
//! See the documentation for the [`generate_k`] function.
#![no_std]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![forbid(unsafe_code, clippy::unwrap_used)]
#![warn(missing_docs, rust_2018_idioms)]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg",
html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg",
html_root_url = "https://docs.rs/rfc6979/0.0.0"
)]
use crypto_bigint::{ArrayEncoding, ByteArray, Integer};
use hmac::{
digest::{generic_array::GenericArray, BlockInput, FixedOutput, Reset, Update},
Hmac, Mac, NewMac,
};
use zeroize::{Zeroize, Zeroizing};
/// Deterministically generate ephemeral scalar `k`.
///
/// 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<D, I>(x: &I, n: &I, h: &ByteArray<I>, data: &[u8]) -> Zeroizing<I>
where
D: FixedOutput<OutputSize = I::ByteSize> + BlockInput + Clone + Default + Reset + Update,
I: ArrayEncoding + Integer + Zeroize,
{
let mut x = x.to_be_byte_array();
let mut hmac_drbg = HmacDrbg::<D>::new(&x, h, data);
x.zeroize();
loop {
let mut bytes = ByteArray::<I>::default();
hmac_drbg.fill_bytes(&mut bytes);
let k = I::from_be_byte_array(bytes);
if (!k.is_zero() & k.ct_lt(n)).into() {
return Zeroizing::new(k);
}
}
}
/// Internal implementation of `HMAC_DRBG` as described in NIST SP800-90A.
///
/// <https://csrc.nist.gov/publications/detail/sp/800-90a/rev-1/final>
///
/// This is a HMAC-based deterministic random bit generator used compute a
/// deterministic ephemeral scalar `k`.
pub struct HmacDrbg<D>
where
D: BlockInput + FixedOutput + Clone + Default + Reset + Update,
{
/// HMAC key `K` (see RFC 6979 Section 3.2.c)
k: Hmac<D>,
/// Chaining value `V` (see RFC 6979 Section 3.2.c)
v: GenericArray<u8, D::OutputSize>,
}
impl<D> HmacDrbg<D>
where
D: BlockInput + FixedOutput + Clone + Default + Reset + Update,
{
/// Initialize `HMAC_DRBG`
pub fn new(entropy_input: &[u8], nonce: &[u8], additional_data: &[u8]) -> Self {
let mut k = Hmac::new(&Default::default());
let mut v = GenericArray::default();
for b in &mut v {
*b = 0x01;
}
for i in 0..=1 {
k.update(&v);
k.update(&[i]);
k.update(entropy_input);
k.update(nonce);
k.update(additional_data);
k = Hmac::new_from_slice(&k.finalize().into_bytes()).expect("HMAC error");
// Steps 3.2.e,g: v = HMAC_k(v)
k.update(&v);
v = k.finalize_reset().into_bytes();
}
Self { k, v }
}
/// Write the next `HMAC_DRBG` output to the given byte slice.
pub fn fill_bytes(&mut self, out: &mut [u8]) {
for out_chunk in out.chunks_mut(self.v.len()) {
self.k.update(&self.v);
self.v = self.k.finalize_reset().into_bytes();
out_chunk.copy_from_slice(&self.v[..out_chunk.len()]);
}
self.k.update(&self.v);
self.k.update(&[0x00]);
self.k = Hmac::new_from_slice(&self.k.finalize_reset().into_bytes()).expect("HMAC error");
self.k.update(&self.v);
self.v = self.k.finalize_reset().into_bytes();
}
}
+14
View File
@@ -0,0 +1,14 @@
//! Smoke tests which use `MockCurve`
#![cfg(feature = "dev")]
use elliptic_curve::dev::MockCurve;
type Signature = ecdsa::Signature<MockCurve>;
type SignatureBytes = ecdsa::SignatureBytes<MockCurve>;
#[test]
fn rejects_all_zero_signature() {
let all_zero_bytes = SignatureBytes::default();
assert!(Signature::try_from(all_zero_bytes.as_ref()).is_err());
}