diff --git a/.envrc b/.envrc deleted file mode 100644 index 809b5a9..0000000 --- a/.envrc +++ /dev/null @@ -1,3 +0,0 @@ -# to use this, install [direnv](https://direnv.net/) -source venv/bin/activate -unset PS1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8575ab7..5ddcba7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ env: jobs: test: # want QEMU >=4 - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Install build dependencies @@ -32,27 +32,23 @@ jobs: toolchain: stable override: true - - name: Check that all crates build without warning - run: RUSTFLAGS='--deny warnings' cargo check --all - shell: bash + - uses: actions-rs/toolchain@v1 + name: Install Rust stable/thumbv7em-none-eabi + with: + profile: minimal + toolchain: stable + target: thumbv7em-none-eabi - # - name: Check clippy lints - # run: cargo clippy --all-features --all-targets -- --deny warnings + - name: Check that all crates check without warning + run: make check - - name: Check formatting - run: cargo fmt -- --check + - name: Check formatting + lints + run: make lint - name: Build PC run: cargo build --release shell: bash - - uses: actions-rs/toolchain@v1 - name: Install Rust stable/thumbv7em-none-eabi - with: - profile: minimal - toolchain: stable - target: thumbv7em-none-eabi - override: true - name: Build Cortex-M4 run: cargo build --release --target thumbv7em-none-eabi shell: bash @@ -70,6 +66,7 @@ jobs: toolchain: stable target: thumbv8m.main-none-eabi override: true + - name: Run all of the tests, including QEMU tests run: make test shell: bash diff --git a/.gitignore b/.gitignore index b6e0701..434b641 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,6 @@ /target **/*.rs.bk Cargo.lock -/venv **/*.o **/*.map **/.gdb_history -/tests/x25519_test.json -/tests/eddsa_test.json diff --git a/Cargo.toml b/Cargo.toml index 620a2fc..2aca1c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,31 +1,77 @@ -[package] -name = "salty" -version = "0.2.1" -authors = ["Nicolas Stalder "] +[workspace] +members = [ + ".", + "c-api", + "qemu-tests", + "wycheproof", + "wycheproof/macros", + "wycheproof/types", +] + +[workspace.package] +authors = ["The Salty Engineers"] edition = "2021" -description = "Small, sweet, swift Ed25519 signatures for microcontrollers" -homepage = "https://salty.rs" -repository = "https://github.com/nickray/salty" +homepage = "https://github.com/ycrypto/salty" license = "Apache-2.0 OR MIT" readme = "README.md" +repository = "https://github.com/ycrypto/salty" +version = "0.2.1" + +[package] +name = "salty" +description = "Small, sweet, swift Ed25519 signatures for microcontrollers" keywords = ["no-std", "NaCl", "Ed25519", "cryptography", "signatures"] +version.workspace = true +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +readme.workspace = true +repository.workspace = true -[dependencies] -cosey = { version = "0.3.0", optional = true } +[workspace.dependencies] +salty = { path = "." } +wycheproof = { path = "wycheproof" } +wycheproof-macros = { path = "wycheproof/macros" } +wycheproof-types = { path = "wycheproof/types" } + +cosey = { version = "0.3.0" } +ed25519 = { version = "2.2", default-features = false } +hex = "0.4" +hex-literal = "0.4" subtle = { version = "2.4.0", default-features = false } zeroize = { version = "1.2.0", default-features = false } -ed25519 = { version = "2.2", default-features = false, optional = true } + +[dependencies] +subtle.workspace = true +zeroize.workspace = true + +cosey = { workspace = true, optional = true} +ed25519 = { workspace = true, optional = true} [dev-dependencies] -hex = "0.4" -hex-literal = "0.4" -wycheproof = { path = "wycheproof" } -wycheproof-gen = { path = "wycheproof/wycheproof-gen" } +hex.workspace = true +hex-literal.workspace = true +wycheproof-macros.workspace = true +wycheproof-types.workspace = true [features] default = ["rustcrypto"] slow-motion = [] cose = ["cosey"] rustcrypto = ["ed25519"] -# # doubt this will ever finish xD -# very-long-x25519-test = [] + +[profile.release.package.salty-c-api] +codegen-units = 1 +debug = true +# using `lto = true` leads to +# warning: Linking globals named 'salty_public_key': symbol multiply defined! +# error: failed to load bc of "salty.2a057q60-cgu.0": +# lto = "thin" +opt-level = "s" + +[profile.release.package.qemu-tests] +codegen-units = 1 +debug = true +# lto = true +opt-level = "z" diff --git a/Makefile b/Makefile index 3079d55..3d3e8ae 100644 --- a/Makefile +++ b/Makefile @@ -1,46 +1,52 @@ -TARGET ?= thumbv7em-none-eabihf -WYCHEPROOF_EDDSA_TEST_JSON_URL ?= https://raw.githubusercontent.com/google/wycheproof/master/testvectors/eddsa_test.json -WYCHEPROOF_X25519_TEST_JSON_URL ?= https://raw.githubusercontent.com/google/wycheproof/master/testvectors/x25519_test.json - -build build-release: +build: cargo build --release cargo build --release --target thumbv7em-none-eabi - cargo build --release --features slow-motion --target thumbv7em-none-eabi - -build-debug: - cargo build - -local-docs: - cargo doc --document-private-items - -fmt: - cargo fmt -rustup: - rustup target add $(TARGET) - rustup component add rustfmt - -tests/eddsa_test.json: - curl -sSf "$(WYCHEPROOF_EDDSA_TEST_JSON_URL)" -o $@ - -tests/x25519_test.json: - curl -sSf "$(WYCHEPROOF_X25519_TEST_JSON_URL)" -o $@ - -test: tests/eddsa_test.json tests/x25519_test.json +test: + # Test on PC cargo test + # Test on QEMU make -C qemu-tests test -.PHONY: venv -# re-run as necessary -venv: - python3 -m venv venv - venv/bin/pip install -U pip - venv/bin/pip install -U -r requirements.txt +fmt: + cargo fmt --all + +fix: fmt + cargo clippy --fix --workspace --allow-staged + +# used in CI +check: + # cargo check --all + cargo check -p salty + cargo check -p salty-c-api --target thumbv7em-none-eabi + cargo check -p qemu-tests + cargo check -p wycheproof + cargo check -p wycheproof-macros + cargo check -p wycheproof-types + +# used in CI +lint: + cargo fmt --check --all + # cargo clippy --workspace + cargo clippy -p salty + cargo clippy -p salty-c-api --target thumbv7em-none-eabi + cargo clippy -p qemu-tests + cargo clippy -p wycheproof + cargo clippy -p wycheproof-macros + cargo clippy -p wycheproof-types -watch: - cargo watch -x 'build --release' +local-docs: + cargo doc --document-private-items -clean: - rm -f tests/eddsa_test.json - rm -f tests/x25519_test.json - cargo clean +rustup-targets: + rustup target add thumbv7em-none-eabi + rustup target add thumbv8m.main-none-eabi + +WP_VECTOR_SOURCE = https://raw.githubusercontent.com/google/wycheproof/master/testvectors +WP_SCHEMA_SOURCE = https://raw.githubusercontent.com/google/wycheproof/master/schemas +WP_DATA = wycheproof/data +update-wycheproof-data: + curl -sSf $(WP_VECTOR_SOURCE)/eddsa_test.json -o $(WP_DATA)/eddsa_test.json + curl -sSf $(WP_SCHEMA_SOURCE)/eddsa_verify_schema.json -o $(WP_DATA)/eddsa_verify_schema.json + curl -sSf $(WP_VECTOR_SOURCE)/x25519_test.json -o $(WP_DATA)/x25519_test.json + curl -sSf $(WP_SCHEMA_SOURCE)/xdh_comp_schema.json -o $(WP_DATA)/xdh_comp_schema.json diff --git a/c-api/.cargo/config b/c-api/.cargo/config new file mode 100644 index 0000000..cc80e05 --- /dev/null +++ b/c-api/.cargo/config @@ -0,0 +1,2 @@ +[build] +target = "thumbv7em-none-eabi" diff --git a/c-api/.gitignore b/c-api/.gitignore index e0d7499..4927315 100644 --- a/c-api/.gitignore +++ b/c-api/.gitignore @@ -1,2 +1 @@ -/target libsalty-debug.a diff --git a/c-api/Cargo.toml b/c-api/Cargo.toml index dc9fc55..39efa57 100644 --- a/c-api/Cargo.toml +++ b/c-api/Cargo.toml @@ -1,29 +1,29 @@ [package] name = "salty-c-api" -version = "0.2.0" -authors = ["Nicolas Stalder "] -edition = "2021" description = "Small, sweet, swift Ed25519 signatures for microcontrollers" -repository = "https://github.com/nickray/salty" -license = "Apache-2.0 OR MIT" -readme = "README.md" keywords = ["no-std", "NaCl", "cryptography", "signatures"] +authors.workspace = true +edition.workspace = true +license.workspace = true +readme.workspace = true +repository.workspace = true +version.workspace = true [lib] crate-type = ["staticlib"] [dependencies] -salty = { path = ".." } +salty.workspace = true panic-halt = "0.2" [features] slow-motion = ["salty/slow-motion"] -[profile.release] -codegen-units = 1 -debug = true -# using `lto = true` leads to -# warning: Linking globals named 'salty_public_key': symbol multiply defined! -# error: failed to load bc of "salty.2a057q60-cgu.0": -lto = "thin" -opt-level = "s" +# [profile.release] +# codegen-units = 1 +# debug = true +# # using `lto = true` leads to +# # warning: Linking globals named 'salty_public_key': symbol multiply defined! +# # error: failed to load bc of "salty.2a057q60-cgu.0": +# lto = "thin" +# opt-level = "s" diff --git a/c-api/Makefile b/c-api/Makefile index 41bb2ab..384d5e0 100644 --- a/c-api/Makefile +++ b/c-api/Makefile @@ -1,4 +1,4 @@ -TARGET = thumbv7em-none-eabihf +TARGET = thumbv7em-none-eabi build: cargo build --release --target $(TARGET) cp target/$(TARGET)/release/build/salty-*/out/libsalty-asm.a libsalty-asm.a diff --git a/c-api/src/lib.rs b/c-api/src/lib.rs index 541560a..e30aa5d 100644 --- a/c-api/src/lib.rs +++ b/c-api/src/lib.rs @@ -1,14 +1,13 @@ #![no_std] +// This is about `Error` as return type not being FFI-safe +// due to it being non-exhaustive +#![allow(improper_ctypes_definitions)] extern crate panic_halt; pub use salty::Error; -use salty::{ - Keypair, - PublicKey, - Signature, -}; +use salty::{Keypair, PublicKey, Signature}; // these are skipped instead of converted to defines // if we `pub use salty::constants::{...}`. @@ -20,6 +19,8 @@ pub const SHA512_LENGTH: usize = 64; #[no_mangle] /// Generates a public key from a secret seed. Use to verify signatures. +/// # Safety +/// These are C-bindings pub unsafe extern "C" fn salty_public_key( seed: &[u8; SECRETKEY_SEED_LENGTH], public_key: &mut [u8; PUBLICKEY_SERIALIZED_LENGTH], @@ -30,6 +31,8 @@ pub unsafe extern "C" fn salty_public_key( #[no_mangle] /// Signs the data, based on the keypair generated from the secret seed. +/// # Safety +/// These are C-bindings pub unsafe extern "C" fn salty_sign( seed: &[u8; SECRETKEY_SEED_LENGTH], data_ptr: *const u8, @@ -39,14 +42,14 @@ pub unsafe extern "C" fn salty_sign( let keypair = Keypair::from(seed); let data = core::slice::from_raw_parts(data_ptr, data_len); - signature.copy_from_slice( - &keypair.sign(data).to_bytes() - ); + signature.copy_from_slice(&keypair.sign(data).to_bytes()); } #[no_mangle] /// Signs the data for a given context, based on the keypair generated /// from the secret seed. +/// # Safety +/// These are C-bindings pub unsafe extern "C" fn salty_sign_with_context( seed: &[u8; SECRETKEY_SEED_LENGTH], data_ptr: *const u8, @@ -62,16 +65,15 @@ pub unsafe extern "C" fn salty_sign_with_context( let data = core::slice::from_raw_parts(data_ptr, data_len); let context = core::slice::from_raw_parts(context_ptr, context_len); - signature.copy_from_slice( - &keypair.sign_with_context(data, context) - .to_bytes() - ); + signature.copy_from_slice(&keypair.sign_with_context(data, context).to_bytes()); Error::NoError } #[no_mangle] /// Signs the prehashed data, based on the keypair generated from the secret seed. /// An optional context can also be passed (this is recommended). +/// # Safety +/// These are C-bindings pub unsafe extern "C" fn salty_sign_prehashed( seed: &[u8; SECRETKEY_SEED_LENGTH], prehashed_data: &[u8; SHA512_LENGTH], @@ -86,8 +88,9 @@ pub unsafe extern "C" fn salty_sign_prehashed( let context = core::slice::from_raw_parts(context_ptr, context_len); signature.copy_from_slice( - &keypair.sign_prehashed(prehashed_data, Some(context)) - .to_bytes() + &keypair + .sign_prehashed(prehashed_data, Some(context)) + .to_bytes(), ); Error::NoError @@ -95,6 +98,8 @@ pub unsafe extern "C" fn salty_sign_prehashed( #[no_mangle] /// Verify a presumed signature on the given data. +/// # Safety +/// These are C-bindings pub unsafe extern "C" fn salty_verify( public_key: &[u8; PUBLICKEY_SERIALIZED_LENGTH], data_ptr: *const u8, @@ -103,7 +108,7 @@ pub unsafe extern "C" fn salty_verify( ) -> Error { let maybe_public_key = PublicKey::try_from(public_key); if maybe_public_key.is_err() { - return maybe_public_key.err().unwrap() + return maybe_public_key.err().unwrap(); } let public_key = maybe_public_key.unwrap(); @@ -112,13 +117,15 @@ pub unsafe extern "C" fn salty_verify( let verification = public_key.verify(data, &signature); if verification.is_err() { - return verification.err().unwrap() + return verification.err().unwrap(); } - return Error::NoError; + Error::NoError } #[no_mangle] /// Verify a presumed signature on the given data. +/// # Safety +/// These are C-bindings pub unsafe extern "C" fn salty_verify_with_context( public_key: &[u8; PUBLICKEY_SERIALIZED_LENGTH], data_ptr: *const u8, @@ -132,7 +139,7 @@ pub unsafe extern "C" fn salty_verify_with_context( } let maybe_public_key = PublicKey::try_from(public_key); if maybe_public_key.is_err() { - return maybe_public_key.err().unwrap() + return maybe_public_key.err().unwrap(); } let public_key = maybe_public_key.unwrap(); @@ -141,13 +148,15 @@ pub unsafe extern "C" fn salty_verify_with_context( let context = core::slice::from_raw_parts(context_ptr, context_len); let verification = public_key.verify_with_context(data, &signature, context); if verification.is_err() { - return verification.err().unwrap() + return verification.err().unwrap(); } - return Error::NoError; + Error::NoError } #[no_mangle] /// Verify a presumed signature on the given data. +/// # Safety +/// These are C-bindings pub unsafe extern "C" fn salty_verify_prehashed( public_key: &[u8; PUBLICKEY_SERIALIZED_LENGTH], prehashed_data: &[u8; SHA512_LENGTH], @@ -160,20 +169,22 @@ pub unsafe extern "C" fn salty_verify_prehashed( } let maybe_public_key = PublicKey::try_from(public_key); if maybe_public_key.is_err() { - return maybe_public_key.err().unwrap() + return maybe_public_key.err().unwrap(); } let public_key = maybe_public_key.unwrap(); let signature = Signature::from(signature); let context = core::slice::from_raw_parts(context_ptr, context_len); let verification = public_key.verify_prehashed(prehashed_data, &signature, Some(context)); if verification.is_err() { - return verification.err().unwrap() + return verification.err().unwrap(); } - return Error::NoError; + Error::NoError } #[no_mangle] /// Perform X25519 key agreement. +/// # Safety +/// These are C-bindings pub unsafe extern "C" fn salty_agree( scalar: &[u8; SECRETKEY_SEED_LENGTH], input_u: &[u8; FIELD_ELEMENT_LENGTH], @@ -182,4 +193,3 @@ pub unsafe extern "C" fn salty_agree( let shared_secret = salty::agreement::x25519(*scalar, *input_u); output_u.copy_from_slice(&shared_secret); } - diff --git a/qemu-tests/memory.x b/memory.x similarity index 100% rename from qemu-tests/memory.x rename to memory.x diff --git a/netlify.toml b/netlify.toml deleted file mode 100644 index 4df4eb6..0000000 --- a/netlify.toml +++ /dev/null @@ -1,7 +0,0 @@ -[build] - command = "curl https://sh.rustup.rs -sSf | sh -s -- -y && source $HOME/.cargo/env && rustup target add thumbv7em-none-eabi && cargo doc --document-private-items --target thumbv7em-none-eabi" - publish = "target/thumbv7em-none-eabi/doc" - -[[redirects]] - from = "/" - to = "/salty" diff --git a/qemu-tests/.gitignore b/qemu-tests/.gitignore deleted file mode 100644 index 1b3c3aa..0000000 --- a/qemu-tests/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -/target -**/*.rs.bk -Cargo.lock -/venv -**/*.o -.gdb_history diff --git a/qemu-tests/Cargo.toml b/qemu-tests/Cargo.toml index 8b1cb44..aee6e0b 100644 --- a/qemu-tests/Cargo.toml +++ b/qemu-tests/Cargo.toml @@ -1,36 +1,26 @@ [package] name = "qemu-tests" -version = "0.0.0" -authors = ["Nicolas Stalder "] -edition = "2021" description = "Test salty using QEMU musca-b1" -license = "Apache-2.0 OR MIT" +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true [dependencies] +hex-literal.workspace = true +salty.workspace = true +wycheproof-macros.workspace = true +wycheproof-types.workspace = true + cortex-m = "0.6.1" cortex-m-rt = "0.6.10" cortex-m-semihosting = "0.3.5" -hex-literal = "0.2.1" panic-semihosting = { version = "0.5.3", features = ["exit"] } -salty = { path = ".." } -# subtle = { version = "2.2", default-features = false } -wycheproof = { version = "0.1", path = "../wycheproof" } -wycheproof-gen = { version = "0.1", path = "../wycheproof/wycheproof-gen" } - -[profile.release] -codegen-units = 1 -debug = true -lto = true -opt-level = "z" - -[[bin]] -name = "qemu-tests" -path = "src/main.rs" [[bin]] -name = "eddsa_verify" -path = "src/bin/eddsa_verify_on_target/main.rs" +name = "ed25519" +path = "src/ed25519.rs" [[bin]] -name = "x25519_comp" -path = "src/bin/x25519_on_target/main.rs" +name = "x25519" +path = "src/x25519.rs" diff --git a/qemu-tests/Makefile b/qemu-tests/Makefile index 88cf890..cca6a56 100644 --- a/qemu-tests/Makefile +++ b/qemu-tests/Makefile @@ -1,59 +1,4 @@ -main-test: - cargo run --bin qemu-tests --release - -../tests/eddsa_test.json: - $(MAKE) -C .. tests/eddsa_test.json - -../tests/x25519_test.json: - $(MAKE) -C .. tests/x25519_test.json - -test: ../tests/eddsa_test.json ../tests/x25519_test.json - @echo "\nTesting Haase field implementation, debug build\n" - time cargo run --bin qemu-tests - @echo "\nTesting Haase field implementation, release build\n" - time cargo run --bin qemu-tests --release - @echo "\nRunnine project Wycheproof EDDSA tests, release build\n" - time cargo run --bin eddsa_verify --release - @echo "\nRunnine project Wycheproof X25519 tests, release build\n" - time cargo run --bin x25519_comp --release - -size: build - arm-none-eabi-size $(KERNEL) - -build: - cargo build - -build-release: - cargo build --release - -size-release: build-release - arm-none-eabi-size $(KERNEL_RELEASE) - arm-none-eabi-size $(KERNEL_RELEASE) -A - -run: - cargo run - -run-release: - # cargo size --features $(IMPL) --release - arm-none-eabi-size $(KERNEL_RELEASE) - cargo run --release - -SEMIHOSTING = -semihosting-config enable=on,target=native -KERNEL = target/thumbv8m.main-none-eabi/debug/qemu-tests -KERNEL_RELEASE = target/thumbv8m.main-none-eabi/release/qemu-tests -CPU = cortex-m33 -MACHINE = musca-b1 -QEMU = ../../qemu/arm-softmmu/qemu-system-arm -# QEMU_DEBUG = -d in_asm,int,exec,cpu,guest_errors,unimp - -qemu: #build - $(QEMU) -cpu $(CPU) -machine $(MACHINE) -nographic $(SEMIHOSTING) -kernel $(KERNEL) $(QEMU_DEBUG) - -qemu-gdb: #build - # Exit with - $(QEMU) -cpu $(CPU) -machine $(MACHINE) -nographic $(SEMIHOSTING) -kernel $(KERNEL) -gdb tcp::1234 -S $(QEMU_DEBUG) - -# assume gcc-arm-none-eabi-8-2019-q3-update -gdb: - arm-none-eabi-gdb -q -x qemu.gdb target/thumbv8m.main-none-eabi/debug/qemu-tests - +# due to .cargo/config, this is run with QEMU +test: + time cargo run --bin ed25519 --release + time cargo run --bin x25519 --release diff --git a/qemu-tests/src/bin/eddsa_verify_on_target/main.rs b/qemu-tests/src/bin/eddsa_verify_on_target/main.rs deleted file mode 100644 index 5a32aca..0000000 --- a/qemu-tests/src/bin/eddsa_verify_on_target/main.rs +++ /dev/null @@ -1,122 +0,0 @@ -#![no_std] -#![no_main] - -extern crate panic_semihosting; -use cortex_m_rt::entry; -use cortex_m_semihosting::{debug, hprintln, hprint}; - -use wycheproof_gen::generate_data; - -use core::convert::TryFrom; -use salty::{Keypair, PublicKey, Signature}; -use salty::constants::{SECRETKEY_SEED_LENGTH, PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; - -use wycheproof::wycheproof::*; - -const THE_TESTS: WycheproofTest = generate_data!("../tests/eddsa_test.json", "eddsa_verify_schema.json"); - -#[entry] -fn main () -> ! { - - hprint!("running tests...\n").ok(); - - for testgroup in THE_TESTS.test_groups { - if let TestGroup::EddsaVerify{key, tests} = testgroup { - for testcase in tests.as_ref() { - run_eddsa_verify(&key, &testcase); - } - } - } - - for testgroup in THE_TESTS.test_groups { - if let TestGroup::EddsaVerify{key, tests} = testgroup { - for testcase in tests.as_ref() { - match testcase.result { - ExpectedResult::Valid => test_eddsa_sign(&key, &testcase), - _ => {}, - } - } - } - } - - hprintln!("done.").ok(); - - debug::exit(debug::EXIT_SUCCESS); - loop { continue; } -} - -fn fail() { - debug::exit(debug::EXIT_FAILURE); - loop { continue; } -} - -fn run_eddsa_verify(test_key: &Key, test_data: &SignatureTestVector) { - - hprint!("EddsaVerify test case {:4}: ", test_data.tc_id).ok(); - - let pk = <[u8; PUBLICKEY_SERIALIZED_LENGTH]>::try_from(test_key.pk); - let sig = <[u8; SIGNATURE_SERIALIZED_LENGTH]>::try_from(test_data.sig); - let valid: bool; - - if pk.is_err() || sig.is_err() { - valid = false; - } else { - let pk = PublicKey::try_from(&pk.unwrap()); - if pk.is_err() { - valid = false; - } else { - let sig = Signature::from(&sig.unwrap()); - let result = pk.unwrap().verify(&test_data.msg, &sig); - valid = result.is_ok(); - } - } - - match test_data.result { - ExpectedResult::Valid => if !valid { - hprintln!("FAIL (expected VALID, but isn't)").ok(); - fail(); - } else { - hprintln!("OK (valid input)").ok(); - } - ExpectedResult::Invalid => if valid { - if test_data.flags.contains(&"SignatureMalleability") { - hprintln!("ALLOW FAIL for SignatureMalleability (expected INVALID, but isn't)").ok(); - } - else - { - hprintln!("FAIL (expected INVALID, but isn't)").ok(); - fail(); - } - } else { - hprintln!("OK (invalid input)").ok(); - } - ExpectedResult::Acceptable => { - hprintln!("ACCEPTABLE in any case").ok(); - }, - } -} - -fn test_eddsa_sign(test_key: &Key, test_data: &SignatureTestVector) { - - hprint!("EddsaVerify test sign {:4}: ", test_data.tc_id).ok(); - - let sk = <[u8; SECRETKEY_SEED_LENGTH]>::try_from(test_key.sk); - let sig = <[u8; SIGNATURE_SERIALIZED_LENGTH]>::try_from(test_data.sig); - let valid: bool; - - if sk.is_err() || sig.is_err() { - valid = false; - } else { - let sk = Keypair::from(&sk.unwrap()); - let testsig = sk.sign(&test_data.msg); - let sig = Signature::from(&sig.unwrap()); - valid = testsig == sig; - } - - if valid { - hprintln!("OK").ok(); - } else { - hprintln!("FAIL signatures do not match").ok(); - fail(); - } -} diff --git a/qemu-tests/src/bin/x25519_on_target/main.rs b/qemu-tests/src/bin/x25519_on_target/main.rs deleted file mode 100644 index cc4d591..0000000 --- a/qemu-tests/src/bin/x25519_on_target/main.rs +++ /dev/null @@ -1,95 +0,0 @@ -#![no_std] -#![no_main] - -extern crate panic_semihosting; -use cortex_m_rt::entry; -use cortex_m_semihosting::{debug, hprintln, hprint}; - -use wycheproof_gen::generate_data; - -use core::convert::TryFrom; -use salty::constants::{SECRETKEY_SEED_LENGTH, PUBLICKEY_SERIALIZED_LENGTH}; -use salty::agreement; - -use wycheproof::wycheproof::*; - -const THE_TESTS: WycheproofTest = generate_data!("../tests/x25519_test.json", "xdh_comp_schema.json"); - -const EXEMPTED_FAILURES: &'static [u32] = &[128, 141, 151]; - -#[entry] -fn main () -> ! { - - hprint!("running tests...\n").ok(); - - let mut known_failures: usize = 0; - for testgroup in THE_TESTS.test_groups { - if let TestGroup::XdhComp{curve, tests} = testgroup { - for testcase in tests.as_ref() { - known_failures += run_x25519_comparison(&curve, &testcase) as usize; - } - } - } - - hprintln!("{} test cases failed among the exemption list {:?}", known_failures, EXEMPTED_FAILURES).ok(); - hprintln!("done.").ok(); - - debug::exit(debug::EXIT_SUCCESS); - loop { continue; } -} - -/// returns `true` on exempted failure, does not return for non-exempted failure -fn fail(tc_id: u32) -> bool { - if EXEMPTED_FAILURES.iter().find(|&&id| id == tc_id).is_none() { - debug::exit(debug::EXIT_FAILURE); - } - hprintln!("NOT FAILING due to exemption - do investigate").ok(); - true -} - -/// Returns `true` on (exempted) failure, `false` on pass -fn run_x25519_comparison(_curve: &str, test_data: &XdhTestVector) -> bool { - - let tc_id = test_data.tc_id; - hprint!("X25519 test case {:4}: ", tc_id).ok(); - - let private = <[u8; SECRETKEY_SEED_LENGTH]>::try_from(test_data.private); - let public = <[u8; PUBLICKEY_SERIALIZED_LENGTH]>::try_from(test_data.public); - let expect = <[u8; 32]>::try_from(test_data.shared); - let valid: bool; - - if private.is_err() || public.is_err() || expect.is_err() { - valid = false; - } else { - let public = agreement::PublicKey::try_from(public.unwrap()); - if public.is_err() { - valid = false; - } else { - let private = agreement::SecretKey::from_seed(&private.unwrap()); - let shared = private.agree(&public.unwrap()); - - valid = shared.to_bytes() == expect.unwrap(); - } - } - - match test_data.result { - ExpectedResult::Valid => if !valid { - hprintln!("FAIL (expected VALID, but isn't)").ok(); - return fail(tc_id); - } else { - hprintln!("OK (valid input)").ok(); - } - ExpectedResult::Invalid => if valid { - hprintln!("FAIL (expected INVALID, but isn't)").ok(); - return fail(tc_id); - } else { - hprintln!("OK (invalid input)").ok(); - } - ExpectedResult::Acceptable => if valid { - hprintln!("ACCEPTABLE (valid)").ok(); - } else { - hprintln!("ACCEPTABLE (invalid)").ok(); - }, - } - false -} diff --git a/qemu-tests/src/ed25519.rs b/qemu-tests/src/ed25519.rs new file mode 100644 index 0000000..c4a72f1 --- /dev/null +++ b/qemu-tests/src/ed25519.rs @@ -0,0 +1,128 @@ +#![no_std] +#![no_main] + +extern crate panic_semihosting; +use cortex_m_rt::entry; +use cortex_m_semihosting::{debug, hprint, hprintln}; + +use wycheproof_macros::generate_data; + +use core::convert::TryFrom; +use salty::constants::{ + PUBLICKEY_SERIALIZED_LENGTH, SECRETKEY_SEED_LENGTH, SIGNATURE_SERIALIZED_LENGTH, +}; +use salty::{Keypair, PublicKey, Signature}; + +use wycheproof_types::*; + +const THE_TESTS: WycheproofTest = generate_data!( + "wycheproof/data/eddsa_test.json", + "wycheproof/data/eddsa_verify_schema.json" +); + +#[entry] +fn main() -> ! { + hprint!("running tests...\n").ok(); + + for testgroup in THE_TESTS.test_groups { + if let TestGroup::EddsaVerify { key, tests } = testgroup { + for testcase in tests.iter() { + run_eddsa_verify(key, testcase); + } + } + } + + for testgroup in THE_TESTS.test_groups { + if let TestGroup::EddsaVerify { key, tests } = testgroup { + for testcase in tests.iter() { + if let ExpectedResult::Valid = testcase.result { + test_eddsa_sign(key, testcase); + } + } + } + } + + hprintln!("done.").ok(); + + debug::exit(debug::EXIT_SUCCESS); + loop { + continue; + } +} + +fn fail() { + debug::exit(debug::EXIT_FAILURE); + loop { + continue; + } +} + +fn run_eddsa_verify(test_key: &Key, test_data: &SignatureTestVector) { + hprint!("EddsaVerify test case {:4}: ", test_data.tc_id).ok(); + + let pk = <[u8; PUBLICKEY_SERIALIZED_LENGTH]>::try_from(test_key.pk); + let sig = <[u8; SIGNATURE_SERIALIZED_LENGTH]>::try_from(test_data.sig); + + let valid = match (pk, sig) { + (Ok(pk), Ok(sig)) => match PublicKey::try_from(&pk) { + Ok(pk) => { + let sig = Signature::from(&sig); + let result = pk.verify(test_data.msg, &sig); + result.is_ok() + } + _ => false, + }, + _ => false, + }; + + match test_data.result { + ExpectedResult::Valid => { + if !valid { + hprintln!("FAIL (expected VALID, but isn't)").ok(); + fail(); + } else { + hprintln!("OK (valid input)").ok(); + } + } + ExpectedResult::Invalid => { + if valid { + if test_data.flags.contains(&"SignatureMalleability") { + hprintln!("ALLOW FAIL for SignatureMalleability (expected INVALID, but isn't)") + .ok(); + } else { + hprintln!("FAIL (expected INVALID, but isn't)").ok(); + fail(); + } + } else { + hprintln!("OK (invalid input)").ok(); + } + } + ExpectedResult::Acceptable => { + hprintln!("ACCEPTABLE in any case").ok(); + } + } +} + +fn test_eddsa_sign(test_key: &Key, test_data: &SignatureTestVector) { + hprint!("EddsaVerify test sign {:4}: ", test_data.tc_id).ok(); + + let sk = <[u8; SECRETKEY_SEED_LENGTH]>::try_from(test_key.sk); + let sig = <[u8; SIGNATURE_SERIALIZED_LENGTH]>::try_from(test_data.sig); + + let valid = match (sk, sig) { + (Ok(sk), Ok(sig)) => { + let sk = Keypair::from(&sk); + let testsig = sk.sign(test_data.msg); + let sig = Signature::from(&sig); + testsig == sig + } + _ => false, + }; + + if valid { + hprintln!("OK").ok(); + } else { + hprintln!("FAIL signatures do not match").ok(); + fail(); + } +} diff --git a/qemu-tests/src/main.rs b/qemu-tests/src/main.rs deleted file mode 100644 index d36fe11..0000000 --- a/qemu-tests/src/main.rs +++ /dev/null @@ -1,17 +0,0 @@ -#![no_std] -#![no_main] - -use cortex_m_semihosting::{debug, hprintln}; -extern crate panic_semihosting; -use cortex_m_rt::entry; - -#[entry] -fn main() -> ! { - - hprintln!("All tests passed").ok(); - - debug::exit(debug::EXIT_SUCCESS); - - loop { continue; } - -} diff --git a/qemu-tests/src/x25519.rs b/qemu-tests/src/x25519.rs new file mode 100644 index 0000000..68312ca --- /dev/null +++ b/qemu-tests/src/x25519.rs @@ -0,0 +1,105 @@ +#![no_std] +#![no_main] + +extern crate panic_semihosting; +use cortex_m_rt::entry; +use cortex_m_semihosting::{debug, hprint, hprintln}; + +use wycheproof_macros::generate_data; + +use core::convert::TryFrom; +use salty::agreement; +use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SECRETKEY_SEED_LENGTH}; + +use wycheproof_types::*; + +const THE_TESTS: WycheproofTest = generate_data!( + "wycheproof/data/x25519_test.json", + "wycheproof/data/xdh_comp_schema.json" +); + +const EXEMPTED_FAILURES: &[u32] = &[128, 141, 151]; + +#[entry] +fn main() -> ! { + hprint!("running tests...\n").ok(); + + let mut known_failures: usize = 0; + for testgroup in THE_TESTS.test_groups { + if let TestGroup::XdhComp { curve, tests } = testgroup { + for testcase in tests.iter() { + known_failures += run_x25519_comparison(curve, testcase) as usize; + } + } + } + + hprintln!( + "{} test cases failed among the exemption list {:?}", + known_failures, + EXEMPTED_FAILURES + ) + .ok(); + hprintln!("done.").ok(); + + debug::exit(debug::EXIT_SUCCESS); + loop { + continue; + } +} + +/// returns `true` on exempted failure, does not return for non-exempted failure +fn fail(tc_id: u32) -> bool { + if !EXEMPTED_FAILURES.iter().any(|&id| id == tc_id) { + debug::exit(debug::EXIT_FAILURE); + } + hprintln!("NOT FAILING due to exemption - do investigate").ok(); + true +} + +/// Returns `true` on (exempted) failure, `false` on pass +fn run_x25519_comparison(_curve: &str, test_data: &XdhTestVector) -> bool { + let tc_id = test_data.tc_id; + hprint!("X25519 test case {:4}: ", tc_id).ok(); + + let private = <[u8; SECRETKEY_SEED_LENGTH]>::try_from(test_data.private); + let public = <[u8; PUBLICKEY_SERIALIZED_LENGTH]>::try_from(test_data.public); + let expect = <[u8; 32]>::try_from(test_data.shared); + + let valid = match (private, public, expect) { + (Ok(private), Ok(public), Ok(expect)) => { + let public = agreement::PublicKey::from(public); + let private = agreement::SecretKey::from_seed(&private); + let shared = private.agree(&public); + + shared.to_bytes() == expect + } + _ => false, + }; + + match test_data.result { + ExpectedResult::Valid => { + if !valid { + hprintln!("FAIL (expected VALID, but isn't)").ok(); + return fail(tc_id); + } else { + hprintln!("OK (valid input)").ok(); + } + } + ExpectedResult::Invalid => { + if valid { + hprintln!("FAIL (expected INVALID, but isn't)").ok(); + return fail(tc_id); + } else { + hprintln!("OK (invalid input)").ok(); + } + } + ExpectedResult::Acceptable => { + if valid { + hprintln!("ACCEPTABLE (valid)").ok(); + } else { + hprintln!("ACCEPTABLE (invalid)").ok(); + } + } + } + false +} diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index e4e42a7..0000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -IPython -ed25519 diff --git a/scripts/format_hexstring_as_u8s.py b/scripts/format_hexstring_as_u8s.py deleted file mode 100755 index 9233f55..0000000 --- a/scripts/format_hexstring_as_u8s.py +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env python3 - -import sys - -PER_LINE = 8 - -string = sys.argv[1] - -entries = [string[i : i + 2] for i in range(0, len(string), 2)] - -for i in range(len(string) // 16): - print(", ".join([f"0x{j}" for j in entries[8 * i : 8 * (i + 1)]]) + ",") diff --git a/src/agreement.rs b/src/agreement.rs index 393e956..e041ff1 100644 --- a/src/agreement.rs +++ b/src/agreement.rs @@ -26,8 +26,7 @@ pub struct SecretKey(pub(crate) Scalar); pub struct SharedSecret(pub(crate) MontgomeryPoint); impl From<[u8; 32]> for PublicKey { - /// Given a byte array, construct a x25519 `PublicKey`. It may fail if some - /// underlying checks fail. + /// Given a byte array, construct a x25519 `PublicKey`. fn from(bytes: [u8; 32]) -> Self { let field_element = FieldElement::from_unreduced_bytes(&bytes); PublicKey(MontgomeryPoint(field_element)) @@ -100,19 +99,6 @@ fn clamp_scalar(mut scalar: [u8; 32]) -> Scalar { Scalar(scalar) } -/// Construct a `Scalar` from the low 255 bits of a 256-bit integer. -/// -/// This function is intended for applications like X25519 which -/// require specific bit-patterns when performing scalar -/// multiplication. -pub fn make_255_bit(bytes: [u8; 32]) -> FieldElement { - let mut bytes = bytes; - // Ensure that s < 2^255 by masking the high bit - bytes[31] &= 0b0111_1111; - - FieldElement::from_unreduced_bytes(&bytes) -} - /// Implementations: /// - MUST mask highest bit in input_u /// - MUST accept non-canonical input_u, reduce modulo base field @@ -122,9 +108,7 @@ pub fn x25519(scalar: [u8; 32], input_u: [u8; 32]) -> [u8; 32] { let scalar = clamp_scalar(scalar); let secret_key = SecretKey(scalar); - let input_u = make_255_bit(input_u); - let input_point = MontgomeryPoint(input_u); - let public_key = PublicKey(input_point); + let public_key = PublicKey::from(input_u); let agreed_secret = secret_key.agree(&public_key); @@ -173,10 +157,7 @@ mod tests { let scalar = clamp_scalar(load_bytes(input_scalar)); let secret_key = SecretKey(scalar); - // see above x25519 function... - let input_u = make_255_bit(load_bytes(input_u)); - let input_point = MontgomeryPoint(input_u); - let public_key = PublicKey(input_point); + let public_key = PublicKey::from(load_bytes(input_u)); let agreed_secret = secret_key.agree(&public_key); diff --git a/src/field/haase.rs b/src/field/haase.rs index b887d10..038b19e 100644 --- a/src/field/haase.rs +++ b/src/field/haase.rs @@ -79,7 +79,7 @@ impl FieldImplementation for FieldElement { fn to_bytes(&self) -> [u8; 32] { // make our own private copy - let mut fe = self.clone(); + let mut fe = *self; FieldElement::reduce_completely(&mut fe); unsafe { core::mem::transmute(fe.0) } } @@ -97,14 +97,14 @@ impl FieldImplementation for FieldElement { fn inverse(&self) -> FieldElement { // TODO: replace by Haase's version in `fe25519_invert.c` - let mut inverse = self.clone(); + let mut inverse = *self; // exponentiate with 2**255 - 21, // which by Fermat's little theorem is the same as inversion for i in (0..=253).rev() { inverse = inverse.squared(); if i != 2 && i != 4 { - inverse = &inverse * &self; + inverse = &inverse * self; } } @@ -123,12 +123,12 @@ impl FieldImplementation for FieldElement { fn pow2523(&self) -> FieldElement { // TODO: replace by Haase's version in `fe25519_pow2523.c` - let mut sqrt = self.clone(); + let mut sqrt = *self; for i in (0..=250).rev() { sqrt = sqrt.squared(); if i != 1 { - sqrt = &sqrt * &self; + sqrt = &sqrt * self; } } @@ -166,7 +166,7 @@ impl<'a, 'b> Add<&'b FieldElement> for &'a FieldElement { impl<'b> AddAssign<&'b FieldElement> for FieldElement { fn add_assign(&mut self, other: &'b FieldElement) { - *self = (self as &FieldElement) + &other; + *self = (self as &FieldElement) + other; } } @@ -174,8 +174,7 @@ impl<'a> Neg for &'a FieldElement { type Output = FieldElement; fn neg(self) -> FieldElement { - let negation = &FieldElement::ZERO - &self; - negation + &FieldElement::ZERO - self } } @@ -197,11 +196,11 @@ impl<'a, 'b> Sub<&'b FieldElement> for &'a FieldElement { // "-1" here accu = ((((accu >> 31) as i32) - 1) * 19) as i64; - for i in 0..7 { + for (i, difference) in difference.iter_mut().take(7).enumerate() { accu += self.0[i] as i64; accu -= other.0[i] as i64; - difference[i] = accu as u32; + *difference = accu as u32; accu >>= 32; } diff --git a/src/lib.rs b/src/lib.rs index 76e74a2..aa5fbc8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![cfg_attr(not(test), no_std)] +#![no_std] /*! Mashup of [TweetNaCl](https://tweetnacl.cr.yp.to/) with [ed25519-dalek](https://lib.rs/crates/ed25519-dalek) diff --git a/tests/wycheproof_eddsa.rs b/tests/ed25519.rs similarity index 88% rename from tests/wycheproof_eddsa.rs rename to tests/ed25519.rs index 6b0fbf9..f483aa1 100644 --- a/tests/wycheproof_eddsa.rs +++ b/tests/ed25519.rs @@ -1,16 +1,18 @@ #[cfg(test)] mod wycheproof { - use wycheproof_gen::test_wycheproof; - - use wycheproof::wycheproof::*; + use wycheproof_macros::test_wycheproof; + use wycheproof_types::*; use salty::constants::{ PUBLICKEY_SERIALIZED_LENGTH, SECRETKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH, }; use salty::{Keypair, PublicKey, SecretKey, Signature}; - #[test_wycheproof("tests/eddsa_test.json", "eddsa_verify_schema.json")] + #[test_wycheproof( + "wycheproof/data/eddsa_test.json", + "wycheproof/data/eddsa_verify_schema.json" + )] fn eddsa_test_case(test_key: &Key, test_data: &SignatureTestVector) { let pk = <[u8; PUBLICKEY_SERIALIZED_LENGTH]>::try_from(test_key.pk) .map(|arr| PublicKey::try_from(&arr)); diff --git a/tests/wycheproof_x25519.rs b/tests/x25519.rs similarity index 62% rename from tests/wycheproof_x25519.rs rename to tests/x25519.rs index 191b62c..e5b82ab 100644 --- a/tests/wycheproof_x25519.rs +++ b/tests/x25519.rs @@ -1,14 +1,16 @@ #[cfg(test)] mod wycheproof { - use wycheproof_gen::test_wycheproof; - - use wycheproof::wycheproof::*; + use wycheproof_macros::test_wycheproof; + use wycheproof_types::*; use salty::agreement; use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SECRETKEY_SEED_LENGTH}; - #[test_wycheproof("tests/x25519_test.json", "xdh_comp_schema.json")] + #[test_wycheproof( + "wycheproof/data/x25519_test.json", + "wycheproof/data/xdh_comp_schema.json" + )] fn x25519_test_case(curve: &str, test_data: &XdhTestVector) { assert!(curve == "curve25519"); @@ -19,15 +21,11 @@ mod wycheproof { match (private, public, shared_expected) { (Ok(private), Ok(public), Ok(shared_expected)) => { - let public = agreement::PublicKey::try_from(public); - if let Ok(public) = public { - let private = agreement::SecretKey::from_seed(&private); - let shared = private.agree(&public); + let public = agreement::PublicKey::from(public); + let private = agreement::SecretKey::from_seed(&private); + let shared = private.agree(&public); - valid = shared.to_bytes() == shared_expected; - } else { - valid = false; - } + valid = shared.to_bytes() == shared_expected; } _ => { valid = false; diff --git a/wycheproof/.gitignore b/wycheproof/.gitignore deleted file mode 100644 index 1b72444..0000000 --- a/wycheproof/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/Cargo.lock -/target diff --git a/wycheproof/Cargo.toml b/wycheproof/Cargo.toml index 8c46f2f..95d4ce1 100644 --- a/wycheproof/Cargo.toml +++ b/wycheproof/Cargo.toml @@ -1,15 +1,14 @@ [package] name = "wycheproof" -version = "0.1.0" authors = ["Enrik Berkhan "] -edition = "2018" -description = "Project Wycheproof tests for salty." -license = "Apache-2.0 OR MIT" +description = "Project Wycheproof tests for salty: JSON parser." +edition.workspace = true +license.workspace = true +version = "0.1.0" [dependencies] -wycheproof-gen = { path = "wycheproof-gen" } - -[build-dependencies] - -[workspace] -members = [ "wycheproof-tests", "wycheproof-gen" ] +hex-serde = { version = "0.1.0" } +proc-macro2 = "1" +quote = "1" +serde = { version = "1", features = ["derive"] } +serde_json = { version = "1" } diff --git a/wycheproof/wycheproof-tests/tests/eddsa_test.json b/wycheproof/data/eddsa_test.json similarity index 100% rename from wycheproof/wycheproof-tests/tests/eddsa_test.json rename to wycheproof/data/eddsa_test.json diff --git a/eddsa_verify_schema.json b/wycheproof/data/eddsa_verify_schema.json similarity index 100% rename from eddsa_verify_schema.json rename to wycheproof/data/eddsa_verify_schema.json diff --git a/wycheproof/wycheproof-tests/tests/x25519_test.json b/wycheproof/data/x25519_test.json similarity index 100% rename from wycheproof/wycheproof-tests/tests/x25519_test.json rename to wycheproof/data/x25519_test.json diff --git a/xdh_comp_schema.json b/wycheproof/data/xdh_comp_schema.json similarity index 100% rename from xdh_comp_schema.json rename to wycheproof/data/xdh_comp_schema.json diff --git a/wycheproof/wycheproof-gen/Cargo.toml b/wycheproof/macros/Cargo.toml similarity index 69% rename from wycheproof/wycheproof-gen/Cargo.toml rename to wycheproof/macros/Cargo.toml index 9dfc76b..2e832d9 100644 --- a/wycheproof/wycheproof-gen/Cargo.toml +++ b/wycheproof/macros/Cargo.toml @@ -1,19 +1,18 @@ [package] -name = "wycheproof-gen" -version = "0.1.0" +name = "wycheproof-macros" authors = ["Enrik Berkhan "] -edition = "2018" description = "Project Wycheproof tests for salty: generator." -license = "Apache-2.0 OR MIT" +edition.workspace = true +license.workspace = true +version = "0.1.0" [lib] proc-macro = true [dependencies] -wycheproof-tests = { path="../wycheproof-tests" } - -serde_json = "1" +wycheproof.workspace = true quote = "1" proc-macro2 = "1" +serde_json = "1" syn = { version = "2", features=["full"] } diff --git a/wycheproof/wycheproof-gen/src/lib.rs b/wycheproof/macros/src/lib.rs similarity index 56% rename from wycheproof/wycheproof-gen/src/lib.rs rename to wycheproof/macros/src/lib.rs index f8eee0c..153ecb7 100644 --- a/wycheproof/wycheproof-gen/src/lib.rs +++ b/wycheproof/macros/src/lib.rs @@ -1,14 +1,11 @@ use proc_macro::TokenStream; -use serde_json; use quote::{quote, ToTokens}; -use syn::{parse_macro_input, Token, LitStr, ItemFn}; use syn::parse::{Parse, ParseStream, Result}; - -use wycheproof_tests; +use syn::{parse_macro_input, ItemFn, LitStr, Token}; struct TestDataArgs { - fname: LitStr, - schema: LitStr, + fname: String, + schema: String, } impl Parse for TestDataArgs { @@ -16,9 +13,9 @@ impl Parse for TestDataArgs { let fname: LitStr = input.parse()?; input.parse::()?; let schema: LitStr = input.parse()?; - Ok(TestDataArgs{ - fname, - schema + Ok(TestDataArgs { + fname: fname.value(), + schema: schema.value(), }) } } @@ -27,14 +24,16 @@ impl Parse for TestDataArgs { pub fn generate_data(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as TestDataArgs); - let testdata = std::fs::read_to_string(input.fname.value()).unwrap(); - let test: wycheproof_tests::WycheproofTest = serde_json::from_str(&testdata).unwrap(); + let testdata = std::fs::read_to_string(&input.fname).unwrap(); + let test: wycheproof::WycheproofTest = serde_json::from_str(&testdata).unwrap(); - if test.schema != input.schema.value() { + if !input.schema.ends_with(&test.schema) { + dbg!(&test.schema); + dbg!(&input.schema); panic!("JSON schemas do not match!"); } - let code = quote!{ + let code = quote! { #test }; code.into() @@ -44,24 +43,24 @@ pub fn generate_data(input: TokenStream) -> TokenStream { pub fn test_wycheproof(args: TokenStream, func: TokenStream) -> TokenStream { let TestDataArgs { fname, schema } = parse_macro_input!(args as TestDataArgs); - let testdata = std::fs::read_to_string(fname.value()).unwrap(); - let testdata: wycheproof_tests::WycheproofTest = serde_json::from_str(&testdata).unwrap(); + let testdata = std::fs::read_to_string(&fname).unwrap(); + let testdata: wycheproof::WycheproofTest = serde_json::from_str(&testdata).unwrap(); - if testdata.schema != schema.value() { + if !schema.ends_with(&testdata.schema) { panic!("JSON schemas do not match!"); } let mut func_copy: proc_macro2::TokenStream = func.clone().into(); - let func_ast: ItemFn = syn::parse(func) - .expect("failed to parse function"); + let func_ast: ItemFn = syn::parse(func).expect("failed to parse function"); let func_ident = func_ast.sig.ident; for testgroup in &testdata.test_groups { match testgroup { - wycheproof_tests::TestGroup::EddsaVerify{key, tests} => { + wycheproof::TestGroup::EddsaVerify { key, tests } => { for testcase in tests { - let test_name = format!("{}_{}", func_ident.to_string(), testcase.tc_id); - let test_ident = proc_macro2::Ident::new(&test_name, proc_macro2::Span::call_site()); + let test_name = format!("{}_{}", func_ident, testcase.tc_id); + let test_ident = + proc_macro2::Ident::new(&test_name, proc_macro2::Span::call_site()); let item = quote! { #[test] fn # test_ident () { @@ -71,12 +70,13 @@ pub fn test_wycheproof(args: TokenStream, func: TokenStream) -> TokenStream { item.to_tokens(&mut func_copy); } - }, + } - wycheproof_tests::TestGroup::XdhComp{curve, tests} => { + wycheproof::TestGroup::XdhComp { curve, tests } => { for testcase in tests { - let test_name = format!("{}_{}", func_ident.to_string(), testcase.tc_id); - let test_ident = proc_macro2::Ident::new(&test_name, proc_macro2::Span::call_site()); + let test_name = format!("{}_{}", func_ident, testcase.tc_id); + let test_ident = + proc_macro2::Ident::new(&test_name, proc_macro2::Span::call_site()); let item = quote! { #[test] fn # test_ident () { @@ -86,7 +86,7 @@ pub fn test_wycheproof(args: TokenStream, func: TokenStream) -> TokenStream { item.to_tokens(&mut func_copy); } - }, + } } } diff --git a/wycheproof/src/lib.rs b/wycheproof/src/lib.rs index f6b6d05..35867cf 100644 --- a/wycheproof/src/lib.rs +++ b/wycheproof/src/lib.rs @@ -1,63 +1,256 @@ -#![no_std] - -pub mod wycheproof { - #[allow(dead_code)] - pub enum ExpectedResult { - Valid, - Invalid, - Acceptable, - } - - #[allow(dead_code)] - pub struct SignatureTestVector<'a> { - pub tc_id: u32, - pub comment: &'a str, - pub msg: &'a [u8], - pub sig: &'a [u8], - pub result: &'a ExpectedResult, - pub flags: &'a [&'a str], - } - - #[allow(dead_code)] - pub struct Key<'a> { - pub curve: &'a str, - pub key_size: i32, - pub pk: &'a [u8], - pub sk: &'a [u8], - pub kind: &'a str, - } - - #[allow(dead_code)] - pub struct XdhTestVector<'a> { - pub tc_id: u32, - pub comment: &'a str, - pub public: &'a [u8], - pub private: &'a [u8], - pub shared: &'a [u8], - pub result: &'a ExpectedResult, - pub flags: &'a [&'a str], - } - - #[allow(dead_code)] - pub enum TestGroup<'a> { - EddsaVerify { - key: &'a Key<'a>, - tests: &'a [&'a SignatureTestVector<'a>], - }, - XdhComp { - curve: &'a str, - tests: &'a [&'a XdhTestVector<'a>], +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; + +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum ExpectedResult { + Valid, + Invalid, + Acceptable, +} + +impl ToTokens for ExpectedResult { + fn to_tokens(&self, tokens: &mut TokenStream) { + let code = match &self { + ExpectedResult::Valid => quote! { ExpectedResult::Valid }, + ExpectedResult::Invalid => quote! { ExpectedResult::Invalid }, + ExpectedResult::Acceptable => quote! { ExpectedResult::Acceptable }, + }; + code.to_tokens(tokens); + } +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SignatureTestVector { + pub tc_id: u32, + pub comment: String, + #[serde(with = "hex_serde")] + pub msg: Vec, + #[serde(with = "hex_serde")] + pub sig: Vec, + pub result: ExpectedResult, + pub flags: Vec, +} + +impl ToTokens for SignatureTestVector { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + tc_id, + comment, + msg, + sig, + result, + flags, + } = self; + + let code = quote! { + SignatureTestVector { + tc_id: #tc_id, + comment: &#comment, + msg: &[ #(#msg),* ], + sig: &[ #(#sig),* ], + result: &#result, + flags: &[ #(#flags), *], + } + }; + code.to_tokens(tokens); + } +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct XdhTestVector { + pub tc_id: u32, + pub comment: String, + #[serde(with = "hex_serde")] + pub public: Vec, + #[serde(with = "hex_serde")] + pub private: Vec, + #[serde(with = "hex_serde")] + pub shared: Vec, + pub result: ExpectedResult, + pub flags: Vec, +} + +impl ToTokens for XdhTestVector { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + tc_id, + comment, + public, + private, + shared, + result, + flags, + } = self; + + let code = quote! { + XdhTestVector { + tc_id: #tc_id, + comment: &#comment, + public: &[ #(#public),* ], + private: &[ #(#private),* ], + shared: &[ #(#shared),* ], + result: &#result, + flags: &[ #(#flags), *], + } + }; + code.to_tokens(tokens); + } +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Key { + pub curve: String, + pub key_size: i32, + #[serde(with = "hex_serde")] + pub pk: Vec, + #[serde(with = "hex_serde")] + pub sk: Vec, + #[serde(rename = "type")] + pub kind: String, +} + +impl ToTokens for Key { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + curve, + key_size, + pk, + sk, + kind, + } = self; + + let code = quote! { + Key { + curve: &#curve, + key_size: #key_size, + pk: &[ #(#pk),* ], + sk: &[ #(#sk),* ], + kind: &#kind, + } + }; + code.to_tokens(tokens); + } +} + +#[derive(Serialize, Deserialize)] +#[serde(tag = "type")] +pub enum TestGroup { + EddsaVerify { + key: Key, + tests: Vec, + }, + XdhComp { + curve: String, + tests: Vec, + }, +} + +impl ToTokens for TestGroup { + fn to_tokens(&self, tokens: &mut TokenStream) { + match &self { + TestGroup::EddsaVerify { key, tests } => { + let code = quote! { + TestGroup::EddsaVerify { + key: &#key, + tests: &[ #(&#tests),* ] + } + }; + code.to_tokens(tokens); + } + + TestGroup::XdhComp { curve, tests } => { + let code = quote! { + TestGroup::XdhComp { + curve: &#curve, + tests: &[ #(&#tests),* ] + } + }; + code.to_tokens(tokens); + } } } +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct WycheproofTest { + pub algorithm: String, + pub generator_version: String, + pub header: Vec, + pub notes: HashMap, + pub number_of_tests: u32, + pub schema: String, + pub test_groups: Vec, +} - #[allow(dead_code)] - pub struct WycheproofTest<'a> { - pub algorithm: &'a str, - pub generator_version: &'a str, - pub header: &'a [&'a str], - pub number_of_tests: u32, - pub schema: &'a str, - pub test_groups: &'a [&'a TestGroup<'a>], +impl ToTokens for WycheproofTest { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + algorithm, + generator_version, + header, + notes: _, + number_of_tests, + schema, + test_groups, + } = self; + + let code = quote! { + WycheproofTest { + algorithm: &#algorithm, + generator_version: &#generator_version, + header: &[ #(&#header),* ], + number_of_tests: #number_of_tests, + schema: &#schema, + test_groups: &[ #(&#test_groups),* ], + } + }; + code.to_tokens(tokens); + } +} + +#[test] +fn read_eddsa_test() { + let contents = std::fs::read_to_string("data/eddsa_test.json").unwrap(); + let test: WycheproofTest = serde_json::from_str(&contents).unwrap(); + + assert_eq!(test.number_of_tests, 145); + + let mut n = 0; + for g in test.test_groups { + if let TestGroup::EddsaVerify { key: _, tests } = g { + for _tc in tests { + n += 1; + } + } + } + + assert_eq!(n, 145); +} + +#[test] +fn read_x25519_test() { + let contents = std::fs::read_to_string("data/x25519_test.json").unwrap(); + let test: WycheproofTest = serde_json::from_str(&contents).unwrap(); + + assert_eq!(test.number_of_tests, 518); + + let mut n = 0; + for g in test.test_groups { + if let TestGroup::XdhComp { curve: _, tests } = g { + for _tc in tests { + n += 1; + } + } } + assert_eq!(n, 518); } diff --git a/wycheproof/types/Cargo.toml b/wycheproof/types/Cargo.toml new file mode 100644 index 0000000..e4806c4 --- /dev/null +++ b/wycheproof/types/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "wycheproof-types" +authors = ["Enrik Berkhan "] +description = "Project Wycheproof no-std types for salty." +edition.workspace = true +license.workspace = true +version = "0.1.0" diff --git a/wycheproof/types/src/lib.rs b/wycheproof/types/src/lib.rs new file mode 100644 index 0000000..3f9b9a8 --- /dev/null +++ b/wycheproof/types/src/lib.rs @@ -0,0 +1,60 @@ +#![no_std] + +#[allow(dead_code)] +pub enum ExpectedResult { + Valid, + Invalid, + Acceptable, +} + +#[allow(dead_code)] +pub struct SignatureTestVector<'a> { + pub tc_id: u32, + pub comment: &'a str, + pub msg: &'a [u8], + pub sig: &'a [u8], + pub result: &'a ExpectedResult, + pub flags: &'a [&'a str], +} + +#[allow(dead_code)] +pub struct Key<'a> { + pub curve: &'a str, + pub key_size: i32, + pub pk: &'a [u8], + pub sk: &'a [u8], + pub kind: &'a str, +} + +#[allow(dead_code)] +pub struct XdhTestVector<'a> { + pub tc_id: u32, + pub comment: &'a str, + pub public: &'a [u8], + pub private: &'a [u8], + pub shared: &'a [u8], + pub result: &'a ExpectedResult, + pub flags: &'a [&'a str], +} + +#[allow(dead_code)] +pub enum TestGroup<'a> { + EddsaVerify { + key: &'a Key<'a>, + tests: &'a [&'a SignatureTestVector<'a>], + }, + XdhComp { + curve: &'a str, + tests: &'a [&'a XdhTestVector<'a>], + }, +} + +#[allow(dead_code)] +pub struct WycheproofTest<'a> { + pub algorithm: &'a str, + pub generator_version: &'a str, + pub header: &'a [&'a str], + pub number_of_tests: u32, + pub schema: &'a str, + pub test_groups: &'a [&'a TestGroup<'a>], +} diff --git a/wycheproof/wycheproof-gen/.gitignore b/wycheproof/wycheproof-gen/.gitignore deleted file mode 100644 index 1b72444..0000000 --- a/wycheproof/wycheproof-gen/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/Cargo.lock -/target diff --git a/wycheproof/wycheproof-tests/.gitignore b/wycheproof/wycheproof-tests/.gitignore deleted file mode 100644 index 1b72444..0000000 --- a/wycheproof/wycheproof-tests/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/Cargo.lock -/target diff --git a/wycheproof/wycheproof-tests/Cargo.toml b/wycheproof/wycheproof-tests/Cargo.toml deleted file mode 100644 index 8cc230a..0000000 --- a/wycheproof/wycheproof-tests/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "wycheproof-tests" -version = "0.1.0" -authors = ["Enrik Berkhan "] -edition = "2018" -description = "Project Wycheproof tests for salty: JSON parser." -license = "Apache-2.0 OR MIT" - -[dependencies] -serde = { version = "1.0.117", features = ["derive"] } -hex-serde = { version = "0.1.0" } -serde_json = { version = "1.0.59" } - -proc-macro2 = "1.0" -quote = "1.0" diff --git a/wycheproof/wycheproof-tests/src/lib.rs b/wycheproof/wycheproof-tests/src/lib.rs deleted file mode 100644 index 22d5e36..0000000 --- a/wycheproof/wycheproof-tests/src/lib.rs +++ /dev/null @@ -1,221 +0,0 @@ -use std::collections::HashMap; - -use serde::{Serialize, Deserialize}; -use hex_serde; - -use proc_macro2::TokenStream; -use quote::{quote,ToTokens}; - -#[derive(Serialize, Deserialize)] -#[serde(rename_all="lowercase")] -pub enum ExpectedResult { - Valid, - Invalid, - Acceptable, -} - -impl ToTokens for ExpectedResult { - fn to_tokens(&self, tokens: &mut TokenStream) { - - let code = match &self { - ExpectedResult::Valid => quote!{ ExpectedResult::Valid }, - ExpectedResult::Invalid => quote!{ ExpectedResult::Invalid }, - ExpectedResult::Acceptable => quote!{ ExpectedResult::Acceptable }, - }; - code.to_tokens(tokens); - } -} - -#[derive(Serialize, Deserialize)] -#[serde(rename_all="camelCase")] -pub struct SignatureTestVector { - pub tc_id: u32, - pub comment: String, - #[serde(with = "hex_serde")] - pub msg: Vec, - #[serde(with = "hex_serde")] - pub sig: Vec, - pub result: ExpectedResult, - pub flags: Vec, -} - -impl ToTokens for SignatureTestVector { - fn to_tokens(&self, tokens: &mut TokenStream) { - let Self { - tc_id, - comment, - msg, - sig, - result, - flags, - } = self; - - let code = quote!{ - SignatureTestVector { - tc_id: #tc_id, - comment: &#comment, - msg: &[ #(#msg),* ], - sig: &[ #(#sig),* ], - result: &#result, - flags: &[ #(#flags), *], - } - }; - code.to_tokens(tokens); - } -} - -#[derive(Serialize, Deserialize)] -#[serde(rename_all="camelCase")] -pub struct XdhTestVector { - pub tc_id: u32, - pub comment: String, - #[serde(with = "hex_serde")] - pub public: Vec, - #[serde(with = "hex_serde")] - pub private: Vec, - #[serde(with = "hex_serde")] - pub shared: Vec, - pub result: ExpectedResult, - pub flags: Vec, -} - -impl ToTokens for XdhTestVector { - fn to_tokens(&self, tokens: &mut TokenStream) { - let Self { - tc_id, - comment, - public, - private, - shared, - result, - flags, - } = self; - - let code = quote!{ - XdhTestVector { - tc_id: #tc_id, - comment: &#comment, - public: &[ #(#public),* ], - private: &[ #(#private),* ], - shared: &[ #(#shared),* ], - result: &#result, - flags: &[ #(#flags), *], - } - }; - code.to_tokens(tokens); - } -} - - -#[derive(Serialize, Deserialize)] -#[serde(rename_all="camelCase")] -pub struct Key { - pub curve: String, - pub key_size: i32, - #[serde(with = "hex_serde")] - pub pk: Vec, - #[serde(with = "hex_serde")] - pub sk: Vec, - #[serde(rename="type")] - pub kind: String, -} - -impl ToTokens for Key { - fn to_tokens(&self, tokens: &mut TokenStream) { - let Self { - curve, - key_size, - pk, - sk, - kind, - } = self; - - let code = quote!{ - Key { - curve: &#curve, - key_size: #key_size, - pk: &[ #(#pk),* ], - sk: &[ #(#sk),* ], - kind: &#kind, - } - }; - code.to_tokens(tokens); - } -} - -#[derive(Serialize, Deserialize)] -#[serde(tag="type")] -pub enum TestGroup { - EddsaVerify { - key: Key, - tests: Vec, - }, - XdhComp { - curve: String, - tests: Vec, - } -} - -impl ToTokens for TestGroup { - fn to_tokens(&self, tokens: &mut TokenStream) { - match &self { - TestGroup::EddsaVerify { key, tests } => { - let code = quote!{ - TestGroup::EddsaVerify { - key: &#key, - tests: &[ #(&#tests),* ] - } - }; - code.to_tokens(tokens); - }, - - TestGroup::XdhComp { curve, tests } => { - let code = quote!{ - TestGroup::XdhComp { - curve: &#curve, - tests: &[ #(&#tests),* ] - } - }; - code.to_tokens(tokens); - } - } - } -} - -#[derive(Serialize, Deserialize)] -#[serde(rename_all="camelCase")] -pub struct WycheproofTest { - pub algorithm: String, - pub generator_version: String, - pub header: Vec, - pub notes: HashMap, - pub number_of_tests: u32, - pub schema: String, - pub test_groups: Vec, -} - -impl ToTokens for WycheproofTest { - fn to_tokens(&self, tokens: &mut TokenStream) { - let Self { - algorithm, - generator_version, - header, - notes: _, - number_of_tests, - schema, - test_groups - } = self; - - let code = quote!{ - WycheproofTest { - algorithm: &#algorithm, - generator_version: &#generator_version, - header: &[ #(&#header),* ], - number_of_tests: #number_of_tests, - schema: &#schema, - test_groups: &[ #(&#test_groups),* ], - } - }; - code.to_tokens(tokens); - } -} diff --git a/wycheproof/wycheproof-tests/tests/reader.rs b/wycheproof/wycheproof-tests/tests/reader.rs deleted file mode 100644 index 3cf39e9..0000000 --- a/wycheproof/wycheproof-tests/tests/reader.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::fs; - -use wycheproof_tests::{WycheproofTest,TestGroup}; - -mod test { - use super::*; - - #[test] - fn read_eddsa_test() { - let contents = fs::read_to_string("tests/eddsa_test.json").unwrap(); - let test: WycheproofTest = serde_json::from_str(&contents).unwrap(); - - assert_eq!(test.number_of_tests, 145); - - let mut n = 0; - for g in test.test_groups { - if let TestGroup::EddsaVerify{key:_, tests} = g { - for _tc in tests { - n += 1; - } - } - } - - assert_eq!(n, 145); - } - - #[test] - fn read_x25519_test() { - let contents = fs::read_to_string("tests/x25519_test.json").unwrap(); - let test: WycheproofTest = serde_json::from_str(&contents).unwrap(); - - assert_eq!(test.number_of_tests, 518); - - let mut n = 0; - for g in test.test_groups { - if let TestGroup::XdhComp{curve: _, tests} = g { - for _tc in tests { - n += 1; - } - } - } - - assert_eq!(n, 518); - } -}