From 6f23b0a10a776d5026776c67429221433d13b670 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Sat, 26 Feb 2022 10:17:46 -0700 Subject: [PATCH 1/2] crypto-intrinsics Now that inline assembly has been stabilized in Rust 1.59, I thought we could start putting together a crate which provides wrappers for assembly instructions which are useful for cryptography but lack proper `core::arch` wrappers. More importantly, using inline assembly allows us to provide a sort of black box which LLVM will not interfere with, which is problematic using anything else besides ASM. For example, it's otherwise not possible to correctly emit CMOV instructions on x86 platforms with LLVM because the `x86-cmov-conversion` pass which will rewrite them as branches. For more details, see: https://dsprenkels.com/cmov-conversion.html --- .github/workflows/crypto-intrinsics.yml | 69 +++++++++++++++++++++++ .github/workflows/workspace.yml | 2 +- Cargo.lock | 4 ++ Cargo.toml | 1 + crypto-intrinsics/Cargo.toml | 21 +++++++ crypto-intrinsics/README.md | 75 +++++++++++++++++++++++++ crypto-intrinsics/src/lib.rs | 16 ++++++ crypto-intrinsics/src/x86.rs | 62 ++++++++++++++++++++ crypto-intrinsics/src/x86_64.rs | 62 ++++++++++++++++++++ dbl/src/lib.rs | 2 + 10 files changed, 313 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/crypto-intrinsics.yml create mode 100644 crypto-intrinsics/Cargo.toml create mode 100644 crypto-intrinsics/README.md create mode 100644 crypto-intrinsics/src/lib.rs create mode 100644 crypto-intrinsics/src/x86.rs create mode 100644 crypto-intrinsics/src/x86_64.rs diff --git a/.github/workflows/crypto-intrinsics.yml b/.github/workflows/crypto-intrinsics.yml new file mode 100644 index 00000000..8532c048 --- /dev/null +++ b/.github/workflows/crypto-intrinsics.yml @@ -0,0 +1,69 @@ +name: crypto-intrinsics + +on: + pull_request: + paths: + - "crypto-intrinsics/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: crypto-intrinsics + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.59.0 # MSRV + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + override: true + - run: cargo build --target ${{ matrix.target }} --release + + test: + strategy: + matrix: + include: + # 32-bit Linux + - target: i686-unknown-linux-gnu + platform: ubuntu-latest + rust: 1.59.0 # MSRV + deps: sudo apt update && sudo apt install gcc-multilib + + # 64-bit Linux + - target: x86_64-unknown-linux-gnu + platform: ubuntu-latest + rust: 1.59.0 # MSRV + + # 64-bit Windows + - target: x86_64-pc-windows-msvc + platform: windows-latest + rust: 1.59.0 # MSRV + + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + profile: minimal + override: true + - run: ${{ matrix.deps }} + - run: cargo test --release diff --git a/.github/workflows/workspace.yml b/.github/workflows/workspace.yml index c778e5c1..2f230a9d 100644 --- a/.github/workflows/workspace.yml +++ b/.github/workflows/workspace.yml @@ -17,7 +17,7 @@ jobs: - uses: RustCrypto/actions/cargo-cache@master - uses: actions-rs/toolchain@v1 with: - toolchain: 1.51.0 # Highest MSRV in repo + toolchain: 1.59.0 # Highest MSRV in repo components: clippy override: true profile: minimal diff --git a/Cargo.lock b/Cargo.lock index d7a9a8c1..f89addb6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,6 +27,10 @@ dependencies = [ "libc", ] +[[package]] +name = "crypto-intrinsics" +version = "0.0.0" + [[package]] name = "dbl" version = "0.3.2" diff --git a/Cargo.toml b/Cargo.toml index 9c2e9066..297cee7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "block-buffer", "collectable", "cpufeatures", + "crypto-intrinsics", "dbl", "hex-literal", "opaque-debug", diff --git a/crypto-intrinsics/Cargo.toml b/crypto-intrinsics/Cargo.toml new file mode 100644 index 00000000..4a2f2ef6 --- /dev/null +++ b/crypto-intrinsics/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "crypto-intrinsics" +description = """ +High-level wrappers for architecture-specific CPU intrinsics which are relevant +to cryptographic use cases, such as the CMOV family, implemented using stable +inline assembly +""" +version = "0.0.0" +authors = ["RustCrypto Developers"] +license = "Apache-2.0 OR MIT" +repository = "https://github.com/RustCrypto/utils/tree/master/crypto-intrinsics" +categories = ["cryptography"] +keywords = ["crypto", "cmov", "intrinsics"] +readme = "README.md" +edition = "2018" # Can't bump to 2021 due to pre-1.56 MSRV crates in the same workspace +rust-version = "1.59" + +[package.metadata.docs.rs] +all-features = true +all-targets = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/crypto-intrinsics/README.md b/crypto-intrinsics/README.md new file mode 100644 index 00000000..29866a37 --- /dev/null +++ b/crypto-intrinsics/README.md @@ -0,0 +1,75 @@ +# [RustCrypto]: CPU Intrinsics + +[![Crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] +![Apache 2.0/MIT Licensed][license-image] +![MSRV][rustc-image] +[![Build Status][build-image]][build-link] + +High-level wrappers for architecture-specific CPU intrinsics which are not +yet provided via [`core::arch`], implemented using inline assembly. + +[Documentation] + +## About + +Certain CPU instructions which are important for cryptographic applications +are difficult to emit from LLVM due to various complicating factors. + +This crate provides high-level architecture-specific wrappers for these +instructions built on stable inline assembly. No attempts at abstraction +are made, just raw access to specific CPU instructions which are guaranteed +to be emitted verbatim and not optimized away or otherwise rewritten by the +compiler. + +## Supported Instructions + +### `x86` (32-bit) + +- `cmovz` (a.k.a. `cmove`) +- `cmovnz` (a.k.a. `cmovne`) + +### `x86_64` + +- `cmovz` (a.k.a. `cmove`) +- `cmovnz` (a.k.a. `cmovne`) + +## Minimum Supported Rust Version + +Rust **1.59** or newer. + +In the future, we reserve the right to change MSRV (i.e. MSRV is out-of-scope +for this crate's SemVer guarantees), however when we do it will be accompanied by +a minor version bump. + +## License + +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/crypto-intrinsics.svg +[crate-link]: https://crates.io/crates/crypto-intrinsics +[docs-image]: https://docs.rs/crypto-intrinsics/badge.svg +[docs-link]: https://docs.rs/crypto-intrinsics/ +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.51+-blue.svg +[build-image]: https://github.com/RustCrypto/utils/actions/workflows/crypto-intrinsics.yml/badge.svg +[build-link]: https://github.com/RustCrypto/utils/actions/workflows/crypto-intrinsics.yml + +[//]: # (general links) + +[RustCrypto]: https://github.com/RustCrypto +[Documentation]: https://docs.rs/crypto-intrinsics +[`core::arch`]: https://doc.rust-lang.org/core/arch/index.html diff --git a/crypto-intrinsics/src/lib.rs b/crypto-intrinsics/src/lib.rs new file mode 100644 index 00000000..5c302509 --- /dev/null +++ b/crypto-intrinsics/src/lib.rs @@ -0,0 +1,16 @@ +#![no_std] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc = include_str!("../README.md")] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg" +)] +#![warn(missing_docs, rust_2018_idioms, unused_qualifications)] + +#[cfg(target_arch = "x86")] +#[cfg_attr(docsrs, doc(cfg(target_arch = "x86")))] +pub mod x86; + +#[cfg(target_arch = "x86_64")] +#[cfg_attr(docsrs, doc(cfg(target_arch = "x86_64")))] +pub mod x86_64; diff --git a/crypto-intrinsics/src/x86.rs b/crypto-intrinsics/src/x86.rs new file mode 100644 index 00000000..4076023d --- /dev/null +++ b/crypto-intrinsics/src/x86.rs @@ -0,0 +1,62 @@ +//! `x86` intrinsics (32-bit) + +use core::arch::asm; + +/// Move if zero. +/// +/// Uses a `test` instruction to check if the given `condition` value is +/// equal to zero, then calls `cmovz` (a.k.a. `cmove`) to conditionally move +/// `src` to `dst` when `condition` is equal to zero. +#[inline(always)] +pub fn cmovz(condition: u32, src: u32, dst: &mut u32) { + unsafe { + asm! { + "test {0}, {0}", + "cmovz {1}, {2}", + in(reg) condition, + inlateout(reg) *dst, + in(reg) src + }; + } +} + +/// Move if not zero. +/// +/// Uses a `test` instruction to check if the given `condition` value is not +/// equal to zero, then calls `cmovnz` (a.k.a. `cmovne`) to conditionally move +/// `src` to `dst` when `condition` is nonzero. +#[inline(always)] +pub fn cmovnz(condition: u32, src: u32, dst: &mut u32) { + unsafe { + asm! { + "test {0}, {0}", + "cmovnz {1}, {2}", + in(reg) condition, + inlateout(reg) *dst, + in(reg) src + }; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn cmovz_works() { + let mut n = 24; + cmovz(42, 42, &mut n); + assert_eq!(n, 24); + cmovz(0, 42, &mut n); + assert_eq!(n, 42); + } + + #[test] + fn cmovnz_works() { + let mut n = 24; + cmovnz(0, 42, &mut n); + assert_eq!(n, 24); + cmovnz(42, 42, &mut n); + assert_eq!(n, 42); + } +} diff --git a/crypto-intrinsics/src/x86_64.rs b/crypto-intrinsics/src/x86_64.rs new file mode 100644 index 00000000..d8ee8508 --- /dev/null +++ b/crypto-intrinsics/src/x86_64.rs @@ -0,0 +1,62 @@ +//! `x86_64` intrinsics + +use core::arch::asm; + +/// Move if zero. +/// +/// Uses a `test` instruction to check if the given `condition` value is +/// equal to zero, then calls `cmovz` (a.k.a. `cmove`) to conditionally move +/// `src` to `dst` when `condition` is equal to zero. +#[inline(always)] +pub fn cmovz(condition: u64, src: u64, dst: &mut u64) { + unsafe { + asm! { + "test {0}, {0}", + "cmovz {1}, {2}", + in(reg) condition, + inlateout(reg) *dst, + in(reg) src + }; + } +} + +/// Move if not zero. +/// +/// Uses a `test` instruction to check if the given `condition` value is not +/// equal to zero, then calls `cmovnz` (a.k.a. `cmovne`) to conditionally move +/// `src` to `dst` when `condition` is nonzero. +#[inline(always)] +pub fn cmovnz(condition: u64, src: u64, dst: &mut u64) { + unsafe { + asm! { + "test {0}, {0}", + "cmovnz {1}, {2}", + in(reg) condition, + inlateout(reg) *dst, + in(reg) src + }; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn cmovz_works() { + let mut n = 24; + cmovz(42, 42, &mut n); + assert_eq!(n, 24); + cmovz(0, 42, &mut n); + assert_eq!(n, 42); + } + + #[test] + fn cmovnz_works() { + let mut n = 24; + cmovnz(0, 42, &mut n); + assert_eq!(n, 24); + cmovnz(42, 42, &mut n); + assert_eq!(n, 42); + } +} diff --git a/dbl/src/lib.rs b/dbl/src/lib.rs index 185a3ffe..fe77c87c 100644 --- a/dbl/src/lib.rs +++ b/dbl/src/lib.rs @@ -29,12 +29,14 @@ pub trait Dbl { /// `block<<1`, otherwise `(block<<1)^C`, where `C` is the non-leading /// coefficients of the lexicographically first irreducible degree-b binary /// polynomial with the minimal number of ones. + #[must_use] fn dbl(self) -> Self; /// Reverse double block. (alternatively: divbide block by x) /// /// If least significant bit of the block equals to zero will return /// `block>>1`, otherwise `(block>>1)^(1<>1)` + #[must_use] fn inv_dbl(self) -> Self; } From 681a93f802f6cb38e64f35d95532e4d4a2076302 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Sat, 26 Feb 2022 10:25:54 -0700 Subject: [PATCH 2/2] clippy fixups Bumping the clippy version to 1.59 exposed some new warnings --- wycheproof2blb/src/aes_siv.rs | 1 + wycheproof2blb/src/ecdsa.rs | 4 ++++ wycheproof2blb/src/ed25519.rs | 3 +++ wycheproof2blb/src/hkdf.rs | 2 ++ wycheproof2blb/src/main.rs | 2 +- 5 files changed, 11 insertions(+), 1 deletion(-) diff --git a/wycheproof2blb/src/aes_siv.rs b/wycheproof2blb/src/aes_siv.rs index e4d399d5..46b7232b 100644 --- a/wycheproof2blb/src/aes_siv.rs +++ b/wycheproof2blb/src/aes_siv.rs @@ -13,6 +13,7 @@ struct TestSuite { #[derive(Debug, Deserialize)] struct TestGroup { + #[allow(dead_code)] #[serde(flatten)] pub group: wycheproof::Group, #[serde(rename = "keySize")] diff --git a/wycheproof2blb/src/ecdsa.rs b/wycheproof2blb/src/ecdsa.rs index c9877d99..edb69089 100644 --- a/wycheproof2blb/src/ecdsa.rs +++ b/wycheproof2blb/src/ecdsa.rs @@ -13,10 +13,13 @@ struct TestSuite { #[derive(Debug, Deserialize)] struct TestGroup { + #[allow(dead_code)] #[serde(flatten)] pub group: wycheproof::Group, + #[allow(dead_code)] #[serde(rename = "keyDer")] pub key_der: String, + #[allow(dead_code)] #[serde(rename = "keyPem")] pub key_pem: String, pub sha: String, @@ -27,6 +30,7 @@ struct TestGroup { #[derive(Debug, Deserialize)] struct TestKey { curve: String, + #[allow(dead_code)] #[serde(rename = "type")] key_type: String, #[serde(with = "hex_string")] diff --git a/wycheproof2blb/src/ed25519.rs b/wycheproof2blb/src/ed25519.rs index 9ae1e235..89054f33 100644 --- a/wycheproof2blb/src/ed25519.rs +++ b/wycheproof2blb/src/ed25519.rs @@ -13,10 +13,13 @@ struct TestSuite { #[derive(Debug, Deserialize)] struct TestGroup { + #[allow(dead_code)] #[serde(flatten)] pub group: wycheproof::Group, + #[allow(dead_code)] #[serde(rename = "keyDer")] pub key_der: String, + #[allow(dead_code)] #[serde(rename = "keyPem")] pub key_pem: String, pub key: TestKey, diff --git a/wycheproof2blb/src/hkdf.rs b/wycheproof2blb/src/hkdf.rs index 4901d92c..e449b99f 100644 --- a/wycheproof2blb/src/hkdf.rs +++ b/wycheproof2blb/src/hkdf.rs @@ -13,8 +13,10 @@ struct TestSuite { #[derive(Debug, Deserialize)] struct TestGroup { + #[allow(dead_code)] #[serde(flatten)] pub group: wycheproof::Group, + #[allow(dead_code)] #[serde(rename = "keySize")] pub key_size: u32, pub tests: Vec, diff --git a/wycheproof2blb/src/main.rs b/wycheproof2blb/src/main.rs index de1e4c48..11082812 100644 --- a/wycheproof2blb/src/main.rs +++ b/wycheproof2blb/src/main.rs @@ -136,7 +136,7 @@ fn main() { } let mut out_file = std::fs::File::create(out_path).unwrap(); - let blobs: Vec> = infos.into_iter().map(|info| info.data).flatten().collect(); + let blobs: Vec> = infos.into_iter().flat_map(|info| info.data).collect(); let (blb_data, _) = blobby::encode_blobs(&blobs); out_file.write_all(&blb_data).unwrap(); }