diff --git a/Cargo.lock b/Cargo.lock index 248a4ac5..21011518 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,12 +10,33 @@ dependencies = [ "serde", ] +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + [[package]] name = "byteorder" version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cpuid-bool" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" + [[package]] name = "crypto-mac" version = "0.9.1" @@ -40,7 +61,9 @@ name = "ecdsa" version = "0.7.2" dependencies = [ "elliptic-curve", + "hex-literal", "hmac", + "sha2", "signature", ] @@ -56,7 +79,7 @@ dependencies = [ [[package]] name = "elliptic-curve" version = "0.5.0" -source = "git+https://github.com/RustCrypto/traits#fc56c1b4f649f2126b0f42931d2a7d19cbcdbb0e" +source = "git+https://github.com/RustCrypto/traits#abff234bfe0ced9254615dc608ece09619a8db38" dependencies = [ "digest", "generic-array", @@ -75,6 +98,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "hex-literal" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5af1f635ef1bc545d78392b136bfe1c9809e029023c84a3638a864a10b8819c8" + [[package]] name = "hmac" version = "0.9.0" @@ -85,6 +114,12 @@ dependencies = [ "digest", ] +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "rand_core" version = "0.5.1" @@ -97,6 +132,19 @@ version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e54c9a88f2da7238af84b5101443f0c0d0a3bbdc455e34a5c9497b1903ed55d5" +[[package]] +name = "sha2" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2933378ddfeda7ea26f48c555bdad8bb446bf8a3d17832dc83e380d444cfb8c1" +dependencies = [ + "block-buffer", + "cfg-if", + "cpuid-bool", + "digest", + "opaque-debug", +] + [[package]] name = "signature" version = "1.2.2" diff --git a/ecdsa/Cargo.toml b/ecdsa/Cargo.toml index abd853e4..0d0545a9 100644 --- a/ecdsa/Cargo.toml +++ b/ecdsa/Cargo.toml @@ -18,9 +18,13 @@ elliptic-curve = { version = "0.5", default-features = false, features = ["weier hmac = { version = "0.9", optional = true, default-features = false } signature = { version = ">= 1.2.2, < 1.3.0", default-features = false } +[dev-dependencies] +hex-literal = "0.3" +sha2 = { version = "0.9", default-features = false } + [features] default = ["digest"] -dev = [] +dev = ["digest", "zeroize"] digest = ["elliptic-curve/digest", "signature/digest-preview"] hazmat = [] rand = ["elliptic-curve/rand", "signature/rand-preview"] diff --git a/ecdsa/src/dev.rs b/ecdsa/src/dev.rs index 20221de0..b3889297 100644 --- a/ecdsa/src/dev.rs +++ b/ecdsa/src/dev.rs @@ -1,5 +1,7 @@ //! Development-related functionality +pub mod curve; + // TODO(tarcieri): implement full set of tests from ECDSA2VS // diff --git a/ecdsa/src/dev/curve.rs b/ecdsa/src/dev/curve.rs new file mode 100644 index 00000000..1a80823c --- /dev/null +++ b/ecdsa/src/dev/curve.rs @@ -0,0 +1,198 @@ +//! Minimalist example curve implementation for testing. +//! +//! Modeled after NIST P-256. + +use core::{convert::TryInto, ops::Mul}; +use elliptic_curve::{ + consts::U32, + digest::Digest, + ops::Invert, + point::Generator, + subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}, + util::{adc64, sbb64}, + zeroize::Zeroize, + FromBytes, FromDigest, +}; + +/// Example NIST P-256-like elliptic curve. +/// Implements only the features needed for testing the implementation. +#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)] +pub struct ExampleCurve; + +impl elliptic_curve::Curve for ExampleCurve { + type ElementSize = U32; +} + +impl elliptic_curve::weierstrass::Curve for ExampleCurve { + const COMPRESS_POINTS: bool = false; +} + +impl elliptic_curve::Arithmetic for ExampleCurve { + type Scalar = Scalar; + type AffinePoint = AffinePoint; +} + +const LIMBS: usize = 4; + +type U256 = [u64; LIMBS]; + +const MODULUS: U256 = [ + 0xf3b9_cac2_fc63_2551, + 0xbce6_faad_a717_9e84, + 0xffff_ffff_ffff_ffff, + 0xffff_ffff_0000_0000, +]; + +/// Example scalar type +#[derive(Clone, Copy, Debug, Default)] +pub struct Scalar([u64; LIMBS]); + +impl ConditionallySelectable for Scalar { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Scalar([ + u64::conditional_select(&a.0[0], &b.0[0], choice), + u64::conditional_select(&a.0[1], &b.0[1], choice), + u64::conditional_select(&a.0[2], &b.0[2], choice), + u64::conditional_select(&a.0[3], &b.0[3], choice), + ]) + } +} + +impl ConstantTimeEq for Scalar { + fn ct_eq(&self, other: &Self) -> Choice { + self.0[0].ct_eq(&other.0[0]) + & self.0[1].ct_eq(&other.0[1]) + & self.0[2].ct_eq(&other.0[2]) + & self.0[3].ct_eq(&other.0[3]) + } +} + +impl FromBytes for Scalar { + type Size = U32; + + fn from_bytes(bytes: &ElementBytes) -> CtOption { + let mut w = [0u64; LIMBS]; + + // Interpret the bytes as a big-endian integer w. + w[3] = u64::from_be_bytes(bytes[0..8].try_into().unwrap()); + w[2] = u64::from_be_bytes(bytes[8..16].try_into().unwrap()); + w[1] = u64::from_be_bytes(bytes[16..24].try_into().unwrap()); + w[0] = u64::from_be_bytes(bytes[24..32].try_into().unwrap()); + + // If w is in the range [0, n) then w - n will overflow, resulting in a borrow + // value of 2^64 - 1. + let (_, borrow) = sbb64(w[0], MODULUS[0], 0); + let (_, borrow) = sbb64(w[1], MODULUS[1], borrow); + let (_, borrow) = sbb64(w[2], MODULUS[2], borrow); + let (_, borrow) = sbb64(w[3], MODULUS[3], borrow); + let is_some = (borrow as u8) & 1; + + CtOption::new(Scalar(w), Choice::from(is_some)) + } +} + +impl From for ElementBytes { + fn from(scalar: Scalar) -> Self { + let mut ret = ElementBytes::default(); + ret[0..8].copy_from_slice(&scalar.0[3].to_be_bytes()); + ret[8..16].copy_from_slice(&scalar.0[2].to_be_bytes()); + ret[16..24].copy_from_slice(&scalar.0[1].to_be_bytes()); + ret[24..32].copy_from_slice(&scalar.0[0].to_be_bytes()); + ret + } +} + +impl FromDigest for Scalar { + fn from_digest(digest: D) -> Self + where + D: Digest, + { + let bytes = digest.finalize(); + + Self::sub_inner( + u64::from_be_bytes(bytes[24..32].try_into().unwrap()), + u64::from_be_bytes(bytes[16..24].try_into().unwrap()), + u64::from_be_bytes(bytes[8..16].try_into().unwrap()), + u64::from_be_bytes(bytes[0..8].try_into().unwrap()), + 0, + MODULUS[0], + MODULUS[1], + MODULUS[2], + MODULUS[3], + 0, + ) + } +} + +impl Invert for Scalar { + type Output = Self; + + fn invert(&self) -> CtOption { + unimplemented!(); + } +} + +impl Zeroize for Scalar { + fn zeroize(&mut self) { + self.0.as_mut().zeroize() + } +} + +impl Scalar { + #[allow(clippy::too_many_arguments)] + const fn sub_inner( + l0: u64, + l1: u64, + l2: u64, + l3: u64, + l4: u64, + r0: u64, + r1: u64, + r2: u64, + r3: u64, + r4: u64, + ) -> Self { + let (w0, borrow) = sbb64(l0, r0, 0); + let (w1, borrow) = sbb64(l1, r1, borrow); + let (w2, borrow) = sbb64(l2, r2, borrow); + let (w3, borrow) = sbb64(l3, r3, borrow); + let (_, borrow) = sbb64(l4, r4, borrow); + + let (w0, carry) = adc64(w0, MODULUS[0] & borrow, 0); + let (w1, carry) = adc64(w1, MODULUS[1] & borrow, carry); + let (w2, carry) = adc64(w2, MODULUS[2] & borrow, carry); + let (w3, _) = adc64(w3, MODULUS[3] & borrow, carry); + + Scalar([w0, w1, w2, w3]) + } +} + +/// Field element bytes; +pub type ElementBytes = elliptic_curve::ElementBytes; + +/// Non-zero scalar value. +pub type NonZeroScalar = elliptic_curve::scalar::NonZeroScalar; + +/// Example affine point type +#[derive(Clone, Copy, Debug)] +pub struct AffinePoint {} + +impl ConditionallySelectable for AffinePoint { + fn conditional_select(_a: &Self, _b: &Self, _choice: Choice) -> Self { + unimplemented!(); + } +} + +impl Mul for AffinePoint { + type Output = AffinePoint; + + fn mul(self, _scalar: NonZeroScalar) -> Self { + unimplemented!(); + } +} + +impl Generator for AffinePoint { + fn generator() -> AffinePoint { + unimplemented!(); + } +} diff --git a/ecdsa/src/signer/rfc6979.rs b/ecdsa/src/signer/rfc6979.rs index df75d549..68a77823 100644 --- a/ecdsa/src/signer/rfc6979.rs +++ b/ecdsa/src/signer/rfc6979.rs @@ -98,3 +98,30 @@ where t } } + +#[cfg(all(feature = "dev", test))] +mod tests { + use super::generate_k; + use crate::dev::curve::NonZeroScalar; + use elliptic_curve::FromBytes; + use hex_literal::hex; + use sha2::{Digest, Sha256}; + + /// Test vector from RFC 6979 Appendix 2.5 (NIST P-256 + SHA-256) + /// + #[test] + fn appendix_2_5_test_vector() { + let x = NonZeroScalar::from_bytes( + &hex!("c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721").into(), + ) + .unwrap(); + + let digest = Sha256::new().chain("sample"); + let k = generate_k(&x, digest, &[]); + + assert_eq!( + k.to_bytes().as_slice(), + &hex!("a6e3c57dd01abe90086538398355dd4c3b17aa873382b0f24d6129493d8aad60")[..] + ); + } +}