diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..79571cc --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,131 @@ +name: Rust + +on: + push: + branches: [ '*' ] + pull_request: + branches: [ main, develop ] + +env: + CARGO_TERM_COLOR: always + +jobs: + test-u32: + name: Test u32 backend + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - uses: actions-rs/cargo@v1 + with: + command: test + args: --no-default-features --features "std u32_backend" + + test-u64: + name: Test u64 backend + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - uses: actions-rs/cargo@v1 + with: + command: test + args: --no-default-features --features "std u64_backend" + + test-simd: + name: Test simd backend (nightly) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly + override: true + - uses: actions-rs/cargo@v1 + with: + command: test + args: --no-default-features --features "std nightly simd_backend" + + test-defaults-serde: + name: Test default feature selection and serde + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - uses: actions-rs/cargo@v1 + with: + command: test + args: --features "serde" + + test-alloc-u32: + name: Test no_std+alloc with u32 backend + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - uses: actions-rs/cargo@v1 + with: + command: test + args: --lib --no-default-features --features "alloc u32_backend" + + test-batch-deterministic: + name: Test deterministic batch verification + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - uses: actions-rs/cargo@v1 + with: + command: test + args: --features "batch_deterministic" + + msrv: + name: Current MSRV is 1.41 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: 1.41 + override: true + - uses: actions-rs/cargo@v1 + with: + command: build + + bench: + name: Check that benchmarks compile + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - uses: actions-rs/cargo@v1 + with: + command: bench + # This filter selects no benchmarks, so we don't run any, only build them. + args: --features "batch" "DONTRUNBENCHMARKS" diff --git a/Cargo.toml b/Cargo.toml index d71ef4d..d01172d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ rand_core = { version = "0.5", default-features = false, optional = true } serde_crate = { package = "serde", version = "1.0", default-features = false, optional = true } serde_bytes = { version = "0.11", optional = true } sha2 = { version = "0.9", default-features = false } -zeroize = { version = "1", default-features = false } +zeroize = { version = "~1.3", default-features = false } [dev-dependencies] hex = "^0.4" diff --git a/README.md b/README.md index 49766fb..fabd832 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ However, if you require this, please see the documentation for the `verify_strict()` function, which does the full checks for the group elements. This functionality is available by default. -If for some reason—although we strongely advise you not to—you need to conform +If for some reason—although we strongly advise you not to—you need to conform to the original specification of ed25519 signatures as in the excerpt from the paper above, you can disable scalar malleability checking via `--features='legacy_compatibility'`. **WE STRONGLY ADVISE AGAINST THIS.** diff --git a/src/lib.rs b/src/lib.rs index 88dfc93..c8ee87d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -235,8 +235,7 @@ #![warn(future_incompatible)] #![deny(missing_docs)] // refuse to compile if documentation is missing -#![cfg(not(test))] -#![forbid(unsafe_code)] +#![cfg_attr(not(test), forbid(unsafe_code))] #[cfg(any(feature = "std", test))] #[macro_use] diff --git a/tests/ed25519.rs b/tests/ed25519.rs index 696e287..0a403be 100644 --- a/tests/ed25519.rs +++ b/tests/ed25519.rs @@ -28,7 +28,10 @@ use sha2::Sha512; #[cfg(test)] mod vectors { + use curve25519_dalek::{edwards::EdwardsPoint, scalar::Scalar}; use ed25519::signature::Signature as _; + use sha2::{digest::Digest, Sha512}; + use std::convert::TryFrom; use std::io::BufReader; use std::io::BufRead; @@ -112,6 +115,77 @@ mod vectors { assert!(keypair.verify_prehashed(prehash_for_verifying, None, &sig2).is_ok(), "Could not verify ed25519ph signature!"); } + + // Taken from curve25519_dalek::constants::EIGHT_TORSION[4] + const EIGHT_TORSION_4: [u8; 32] = [ + 236, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127, + ]; + + fn compute_hram(message: &[u8], pub_key: &EdwardsPoint, signature_r: &EdwardsPoint) -> Scalar { + let k_bytes = Sha512::default() + .chain(&signature_r.compress().as_bytes()) + .chain(&pub_key.compress().as_bytes()[..]) + .chain(&message); + let mut k_output = [0u8; 64]; + k_output.copy_from_slice(k_bytes.finalize().as_slice()); + Scalar::from_bytes_mod_order_wide(&k_output) + } + + fn serialize_signature(r: &EdwardsPoint, s: &Scalar) -> Vec { + [&r.compress().as_bytes()[..], &s.as_bytes()[..]].concat() + } + + #[test] + fn repudiation() { + use curve25519_dalek::traits::IsIdentity; + use std::ops::Neg; + + let message1 = b"Send 100 USD to Alice"; + let message2 = b"Send 100000 USD to Alice"; + + // Pick a random Scalar + fn non_null_scalar() -> Scalar { + let mut rng = rand::rngs::OsRng; + let mut s_candidate = Scalar::random(&mut rng); + while s_candidate == Scalar::zero() { + s_candidate = Scalar::random(&mut rng); + } + s_candidate + } + let mut s: Scalar = non_null_scalar(); + + fn pick_r_and_pubkey(s: Scalar) -> (EdwardsPoint, EdwardsPoint) { + let r0 = s * curve25519_dalek::constants::ED25519_BASEPOINT_POINT; + // Pick a torsion point of order 2 + let pub_key = curve25519_dalek::edwards::CompressedEdwardsY(EIGHT_TORSION_4) + .decompress() + .unwrap(); + let r = r0 + pub_key.neg(); + (r, pub_key) + } + + let (mut r, mut pub_key) = pick_r_and_pubkey(s); + + while !(pub_key.neg() + compute_hram(message1, &pub_key, &r) * pub_key).is_identity() + || !(pub_key.neg() + compute_hram(message2, &pub_key, &r) * pub_key).is_identity() + { + s = non_null_scalar(); + let key = pick_r_and_pubkey(s); + r = key.0; + pub_key = key.1; + } + + let signature = serialize_signature(&r, &s); + let pk = PublicKey::from_bytes(&pub_key.compress().as_bytes()[..]).unwrap(); + let sig = Signature::try_from(&signature[..]).unwrap(); + // The same signature verifies for both messages + assert!(pk.verify(message1, &sig).is_ok() && pk.verify(message2, &sig).is_ok()); + // But not with a strict signature: verify_strict refuses small order keys + assert!( + pk.verify_strict(message1, &sig).is_err() || pk.verify_strict(message2, &sig).is_err() + ); + } } #[cfg(test)]