diff --git a/.gitignore b/.gitignore --- a/.gitignore +++ b/.gitignore @@ -1,1 +1,2 @@ +.cargo/ /target diff --git a/Cargo.lock b/Cargo.lock --- a/Cargo.lock +++ b/Cargo.lock @@ -13,6 +13,36 @@ ] [[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloca" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] name = "anyhow" version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -40,10 +70,118 @@ ] [[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.2.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.11.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c9ed179664f12fd6f155f6dd632edf5f3806d48c228c67ff78366f2a0eb6b5e" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "cipher" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34d8227fe1ba289043aeb13792056ff80fd6de1a9f49137a5f499de8e8c78ea" +dependencies = [ + "block-buffer", + "crypto-common", + "inout", +] + +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "cmov" @@ -61,11 +199,78 @@ ] [[package]] +name = "criterion" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3" +dependencies = [ + "alloca", + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools", + "num-traits", + "oorandom", + "page_size", + "plotters", + "rayon", + "regex", + "serde", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] name = "crypto-common" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" dependencies = [ + "getrandom", "hybrid-array", "rand_core", ] @@ -90,10 +295,22 @@ ] [[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "foldhash" @@ -113,6 +330,17 @@ "rand_core", "wasip2", "wasip3", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", ] [[package]] @@ -181,10 +409,29 @@ ] [[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +dependencies = [ + "once_cell", + "wasm-bindgen", +] [[package]] name = "keccak" @@ -267,6 +514,66 @@ ] [[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "poly1305" +version = "0.9.0-rc.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19feddcbdf17fad33f40041c7f9e768faf19455f32a6d52ba1b8b65ffc7b1cae" +dependencies = [ + "cpufeatures", + "universal-hash", +] + +[[package]] name = "prettyplease" version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -305,6 +612,70 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] [[package]] name = "semver" @@ -375,6 +746,12 @@ ] [[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] name = "syn" version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -383,6 +760,16 @@ "proc-macro2", "quote", "unicode-ident", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", ] [[package]] @@ -404,6 +791,26 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] +name = "universal-hash" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96" +dependencies = [ + "crypto-common", + "ctutils", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] name = "wasip2" version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -419,6 +826,51 @@ checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +dependencies = [ + "unicode-ident", ] [[package]] @@ -456,10 +908,22 @@ ] [[package]] +name = "web-sys" +version = "0.3.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] name = "wharrgarbl" version = "0.1.0" dependencies = [ "aead", + "chacha20poly1305", + "criterion", "ctutils", "getrandom", "hybrid-array", @@ -491,6 +955,52 @@ version = "0.1.0" dependencies = [ "aead", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", ] [[package]] @@ -585,6 +1095,26 @@ "serde_json", "unicode-xid", "wasmparser", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ aead = { version = "0.6.0-rc.10" } ctutils = { version = "0.4.2", default-features = false } zeroize = { version = "1.8.2", default-features = false } +rand_core = { version = "0.10", default-features = false } hybrid-array = { version = "0.4.10" } [package] @@ -23,9 +24,6 @@ version.workspace = true authors.workspace = true -[features] -parallel = ["wharrgarbl-strobe/parallel"] - [dependencies] wharrgarbl-utils = { path = "./wharrgarbl-utils", version = "0.1" } wharrgarbl-strobe = { path = "./wharrgarbl-strobe", version = "0.1" } @@ -33,9 +31,22 @@ zeroize.workspace = true ml-kem = { version = "0.3.0-rc.2", features = ["zeroize"] } hybrid-array = { workspace = true, features = ["alloc"] } -rand_core = { version = "0.10", default-features = false } +rand_core.workspace = true aead.workspace = true [dev-dependencies] getrandom = { version = "0.4.2", features = ["sys_rng"] } aead = { version = "0.6.0-rc.10", features = ["alloc"] } +chacha20poly1305 = "=0.11.0-rc.3" +criterion = { version ="0.8.2", features = ["html_reports"] } + +[[bench]] +name = "garbl_bench" +path = "benches/garbl_bench.rs" +harness = false + +[profile.bench] +lto = true +codegen-units = 1 +debug = false +panic = "abort" diff --git a/benches/garbl_bench.rs b/benches/garbl_bench.rs new file mode 100644 --- /dev/null +++ b/benches/garbl_bench.rs @@ -0,0 +1,63 @@ +use core::hint::black_box; + +use aead::{AeadInOut, KeyInit}; +use criterion::{Criterion, criterion_main}; +use hybrid_array::Array; + +fn generate_text() -> Vec { + let text = "Lorem ipsum dolor sit amet"; + + let text = String::from(text); + + text.repeat(128).as_bytes().to_vec() +} + +fn wharrgarbl_benchmark(c: &mut Criterion) { + use wharrgarbl::transport::AeadStrobe; + use wharrgarbl_strobe::Sec128; + + let key = Array([77u8; 32]); + let nonce = Array([12u8; 16]); + + let transport = AeadStrobe::::new(&key); + + c.bench_function("WHGL TRANSPORT ROUNDTRIP", |b| { + b.iter_batched_ref( + generate_text, + |text| { + let _ = transport.encrypt_in_place(&nonce, b"bench", black_box(text)); + let _ = transport.decrypt_in_place(&nonce, b"bench", black_box(text)); + }, + criterion::BatchSize::LargeInput, + ); + }); +} + +fn chacha20poly1305_benchmark(c: &mut Criterion) { + use chacha20poly1305::{ChaCha20Poly1305, KeyInit}; + + let key = Array([77u8; 32]); + let nonce = Array([12u8; 12]); + + let transport = ChaCha20Poly1305::new(&key); + + c.bench_function("CHACHAPOLY TRANSPORT ROUNDTRIP", |b| { + b.iter_batched_ref( + generate_text, + |text| { + let _ = transport.encrypt_in_place(&nonce, b"bench", black_box(text)); + let _ = transport.decrypt_in_place(&nonce, b"bench", black_box(text)); + }, + criterion::BatchSize::LargeInput, + ); + }); +} + +pub fn benches() { + let mut criterion: Criterion<_> = Criterion::default().configure_from_args(); + + wharrgarbl_benchmark(&mut criterion); + chacha20poly1305_benchmark(&mut criterion); +} + +criterion_main!(benches); diff --git a/src/handshake.rs b/src/handshake.rs --- a/src/handshake.rs +++ b/src/handshake.rs @@ -1,120 +1,117 @@ +use core::marker::PhantomData; + use aead::Buffer; -use hybrid_array::typenum::Unsigned; +use alloc::boxed::Box; +use hybrid_array::{Array, typenum::Unsigned}; +use ml_kem::{Encapsulate, Kem, KeyExport, ParameterSet, TryKeyInit, kem::Decapsulate}; use rand_core::CryptoRng; -use wharrgarbl_strobe::{StrobeRole, StrobeSecurity, StrobeState}; +use wharrgarbl_strobe::{StrobeRole, StrobeState, traits::StrobeSecurity}; use crate::{ WHARRGHARBL_PROTO, - kem::{KemEncap, KemSecurity}, transport::{AeadState, AeadStrobe}, }; -pub struct ClientHandshake { - kem_sec: KemSecurity, - sec_param: StrobeSecurity, - strobe: StrobeState, - decap: Option, +pub struct ClientHandshake { + kem_sec: PhantomData, + strobe: StrobeState, + decap: Option>, } -impl ClientHandshake { - pub fn new(kem_sec: KemSecurity, sec_param: StrobeSecurity, psk: Option<&[u8; 32]>) -> Self { - let mut strobe = - StrobeState::new(WHARRGHARBL_PROTO.as_bytes(), sec_param, StrobeRole::Sender); +impl ClientHandshake +where + K::DecapsulationKey: Decapsulate, +{ + pub fn new(psk: Option<&[u8; 32]>) -> Self { + let mut strobe = StrobeState::::new(WHARRGHARBL_PROTO.as_bytes(), StrobeRole::Sender); if let Some(psk) = psk { strobe.key(psk); } - strobe.meta_ad(&kem_sec.to_bytes()); - strobe.meta_ad(&sec_param.to_bytes()); + strobe.meta_ad(&K::K::to_u8().to_le_bytes()); + strobe.meta_ad(&S::to_bytes()); Self { - kem_sec, - sec_param, + kem_sec: PhantomData, strobe, decap: None, } } pub fn send(&mut self, rng: &mut impl CryptoRng, buf: &mut dyn Buffer) -> aead::Result<()> { - let mut tag: aead::Tag = Default::default(); - let (decap, encap) = self.kem_sec.generate_with_rng(rng); + let mut tag: aead::Tag> = Default::default(); + let (decap, encap) = K::generate_keypair_from_rng(rng); - let written = encap.serialize(buf.as_mut())?; + self.decap = Some(Box::new(decap)); - buf.truncate(written); + buf.extend_from_slice(&encap.to_bytes())?; self.strobe.send_clr(buf.as_ref()); self.strobe.send_mac(&mut tag); - self.strobe.ratchet(self.sec_param.rachet_bytes()); + self.strobe.ratchet(S::ratchet_bytes()); buf.extend_from_slice(&tag)?; - - self.decap = Some(decap); Ok(()) } - pub fn receive(&mut self, ciphertext: &[u8]) -> aead::Result { - let decap = self.decap.as_mut().ok_or(aead::Error)?; + pub fn receive(&mut self, ciphertext: &[u8]) -> aead::Result> { + let decap = self.decap.as_ref().ok_or(aead::Error)?; let tag = ciphertext .len() - .checked_sub(::TagSize::to_usize()) + .checked_sub( as aead::AeadCore>::TagSize::to_usize()) .ok_or(aead::Error)?; let (ciphertext, tag) = ciphertext.split_at(tag); - let tag: aead::Tag = tag.try_into().unwrap(); + let tag: aead::Tag> = tag.try_into().unwrap(); self.strobe.recv_clr(ciphertext); self.strobe.recv_mac(&tag)?; - self.strobe.ratchet(self.sec_param.rachet_bytes()); + self.strobe.ratchet(S::ratchet_bytes()); - let shared = decap.decapsulate(ciphertext)?; + let shared = decap + .decapsulate_slice(ciphertext) + .map_err(|_| aead::Error)?; Ok(self.finish(shared)) } - fn finish(&mut self, shared: ml_kem::SharedKey) -> AeadState { + fn finish(&mut self, shared: Array) -> AeadState { self.strobe.ad(&shared); - let mut key: aead::Key = Default::default(); - let mut inbound: aead::Nonce = Default::default(); - let mut outbound: aead::Nonce = Default::default(); + let mut key: aead::Key> = Default::default(); + let mut inbound: aead::Nonce> = Default::default(); + let mut outbound: aead::Nonce> = Default::default(); self.strobe.prf(&mut key); self.strobe.prf(&mut inbound); self.strobe.prf(&mut outbound); - AeadState::new(key, self.sec_param, outbound, inbound, StrobeRole::Sender) + AeadState::new(key, outbound, inbound, StrobeRole::Sender) } } -pub struct ServerHandshake { - kem_sec: KemSecurity, - sec_param: StrobeSecurity, - strobe: StrobeState, +pub struct ServerHandshake { + kem_sec: PhantomData, + strobe: StrobeState, } -impl ServerHandshake { - pub fn new(kem_sec: KemSecurity, sec_param: StrobeSecurity, psk: Option<&[u8; 32]>) -> Self { - let mut strobe = StrobeState::new( - WHARRGHARBL_PROTO.as_bytes(), - sec_param, - StrobeRole::Receiver, - ); +impl ServerHandshake { + pub fn new(psk: Option<&[u8; 32]>) -> Self { + let mut strobe = StrobeState::::new(WHARRGHARBL_PROTO.as_bytes(), StrobeRole::Receiver); if let Some(psk) = psk { strobe.key(psk); } - strobe.meta_ad(&kem_sec.to_bytes()); - strobe.meta_ad(&sec_param.to_bytes()); + strobe.meta_ad(&K::K::to_u8().to_le_bytes()); + strobe.meta_ad(&S::to_bytes()); Self { - kem_sec, - sec_param, + kem_sec: PhantomData, strobe, } } @@ -123,31 +120,33 @@ &mut self, rng: &mut impl CryptoRng, buf: &mut dyn Buffer, - ) -> aead::Result { + ) -> aead::Result> { let slice = buf.as_ref(); let tag = slice .len() - .checked_sub(::TagSize::to_usize()) + .checked_sub( as aead::AeadCore>::TagSize::to_usize()) .ok_or(aead::Error)?; let (encap, tag) = buf.as_ref().split_at(tag); - let tag: aead::Tag = tag.try_into().unwrap(); + let tag: aead::Tag> = tag.try_into().unwrap(); self.strobe.recv_clr(encap); self.strobe.recv_mac(&tag)?; - self.strobe.ratchet(self.sec_param.rachet_bytes()); + self.strobe.ratchet(S::ratchet_bytes()); - let (cipher, shared) = KemEncap::encapsulate_from_slice(self.kem_sec, encap, rng)?; + let encap = K::EncapsulationKey::new_from_slice(encap).map_err(|_| aead::Error)?; - let mut tag: aead::Tag = Default::default(); + let (cipher, shared) = encap.encapsulate_with_rng(rng); + + let mut tag: aead::Tag> = Default::default(); buf.truncate(0); self.strobe.send_clr(cipher.as_ref()); self.strobe.send_mac(&mut tag); - self.strobe.ratchet(self.sec_param.rachet_bytes()); + self.strobe.ratchet(S::ratchet_bytes()); buf.extend_from_slice(cipher.as_ref())?; buf.extend_from_slice(&tag)?; @@ -155,23 +154,26 @@ Ok(self.finish(shared)) } - fn finish(&mut self, shared: ml_kem::SharedKey) -> AeadState { + fn finish(&mut self, shared: Array) -> AeadState { self.strobe.ad(&shared); - let mut key: aead::Key = Default::default(); - let mut inbound: aead::Nonce = Default::default(); - let mut outbound: aead::Nonce = Default::default(); + let mut key: aead::Key> = Default::default(); + let mut inbound: aead::Nonce> = Default::default(); + let mut outbound: aead::Nonce> = Default::default(); self.strobe.prf(&mut key); self.strobe.prf(&mut inbound); self.strobe.prf(&mut outbound); - AeadState::new(key, self.sec_param, outbound, inbound, StrobeRole::Receiver) + AeadState::new(key, outbound, inbound, StrobeRole::Receiver) } } #[cfg(test)] mod tests { + use ml_kem::{MlKem512, MlKem768}; + use wharrgarbl_strobe::{Sec128, Sec256}; + use crate::utils::BufferSlice; use super::*; @@ -183,8 +185,8 @@ 132, 45, 174, 183, 65, 89, 73, 107, 177, 77, 90, 164, 251, ]; - let mut alice = ClientHandshake::new(KemSecurity::Level1, StrobeSecurity::B128, Some(&psk)); - let mut bob = ServerHandshake::new(KemSecurity::Level1, StrobeSecurity::B128, Some(&psk)); + let mut alice = ClientHandshake::::new(Some(&psk)); + let mut bob = ServerHandshake::::new(Some(&psk)); // BufferSlice acts as our transport across the webz let mut buf = alloc::vec![0u8; 2048]; @@ -192,13 +194,13 @@ let mut rng = rand_core::UnwrapErr(getrandom::SysRng); - alice.send(&mut rng, &mut buf)?; + alice.send(&mut rng, &mut buf).unwrap(); // Pretend to send ek across the webz: client -> server - let bob = bob.respond(&mut rng, &mut buf)?; + let bob = bob.respond(&mut rng, &mut buf).unwrap(); // Pretend to send ciphertext across the webz: server -> client - let alice = alice.receive(buf.as_ref())?; + let alice = alice.receive(buf.as_ref()).unwrap(); assert_eq!(alice.aead.key, bob.aead.key); @@ -220,8 +222,8 @@ 132, 45, 174, 183, 65, 89, 73, 107, 177, 77, 90, 164, 251, ]; - let mut alice = ClientHandshake::new(KemSecurity::Level1, StrobeSecurity::B128, Some(&psk)); - let mut bob = ServerHandshake::new(KemSecurity::Level1, StrobeSecurity::B256, Some(&psk)); + let mut alice = ClientHandshake::::new(Some(&psk)); + let mut bob = ServerHandshake::::new(Some(&psk)); // BufferSlice acts as our transport across the webz let mut buf = alloc::vec![0u8; 2048]; @@ -243,8 +245,8 @@ 132, 45, 174, 183, 65, 89, 73, 107, 177, 77, 90, 164, 251, ]; - let mut alice = ClientHandshake::new(KemSecurity::Level1, StrobeSecurity::B128, Some(&psk)); - let mut bob = ServerHandshake::new(KemSecurity::Level3, StrobeSecurity::B128, Some(&psk)); + let mut alice = ClientHandshake::::new(Some(&psk)); + let mut bob = ServerHandshake::::new(Some(&psk)); // BufferSlice acts as our transport across the webz let mut buf = alloc::vec![0u8; 2048]; @@ -266,8 +268,8 @@ 132, 45, 174, 183, 65, 89, 73, 107, 177, 77, 90, 164, 251, ]; - let mut alice = ClientHandshake::new(KemSecurity::Level1, StrobeSecurity::B128, None); - let mut bob = ServerHandshake::new(KemSecurity::Level1, StrobeSecurity::B128, Some(&psk)); + let mut alice = ClientHandshake::::new(None); + let mut bob = ServerHandshake::::new(Some(&psk)); // BufferSlice acts as our transport across the webz let mut buf = alloc::vec![0u8; 2048]; diff --git a/src/kem.rs b/src/kem.rs deleted file mode 100644 --- a/src/kem.rs +++ /dev/null @@ -1,137 +0,0 @@ -use alloc::boxed::Box; -use ml_kem::{Decapsulate, Encapsulate, Kem, KeyExport, SharedKey, TryKeyInit}; -use rand_core::CryptoRng; -use zeroize::Zeroize; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u8)] -pub enum KemSecurity { - Level1 = 0, - Level3 = 1, -} - -pub enum KemEncap { - Level1(Box), - Level3(Box), -} - -pub struct KemCipher(Box<[u8]>); - -pub enum KemDecap { - Level1(Box), - Level3(Box), -} - -impl KemSecurity { - pub fn generate_with_rng(&self, rng: &mut impl CryptoRng) -> (KemDecap, KemEncap) { - match self { - Self::Level1 => { - let (decap, encap) = ml_kem::MlKem512::generate_keypair_from_rng(rng); - - ( - KemDecap::Level1(Box::new(decap)), - KemEncap::Level1(Box::new(encap)), - ) - } - Self::Level3 => { - let (decap, encap) = ml_kem::MlKem768::generate_keypair_from_rng(rng); - - ( - KemDecap::Level3(Box::new(decap)), - KemEncap::Level3(Box::new(encap)), - ) - } - } - } - - pub const fn to_bytes(self) -> [u8; 1] { - (self as u8).to_le_bytes() - } -} - -impl KemEncap { - pub fn encapsulate_from_slice( - sec: KemSecurity, - buf: &[u8], - rng: &mut impl CryptoRng, - ) -> Result<(KemCipher, ml_kem::SharedKey), aead::Error> { - match sec { - KemSecurity::Level1 => { - let encap = - ml_kem::EncapsulationKey512::new_from_slice(buf).map_err(|_| aead::Error)?; - - let (ct, shared) = encap.encapsulate_with_rng(rng); - - Ok((KemCipher(ct.into()), shared)) - } - KemSecurity::Level3 => { - let encap = - ml_kem::EncapsulationKey768::new_from_slice(buf).map_err(|_| aead::Error)?; - - let (ct, shared) = encap.encapsulate_with_rng(rng); - - Ok((KemCipher(ct.into()), shared)) - } - } - } - - pub fn serialize(&self, buf: &mut [u8]) -> Result { - match self { - Self::Level1(encap) => { - let data = encap.to_bytes(); - let (slice, _) = buf.split_at_mut_checked(data.len()).ok_or(aead::Error)?; - - slice.copy_from_slice(&data); - - Ok(data.len()) - } - Self::Level3(encap) => { - let data = encap.to_bytes(); - let (slice, _) = buf.split_at_mut_checked(data.len()).ok_or(aead::Error)?; - - slice.copy_from_slice(&data); - - Ok(data.len()) - } - } - } -} - -impl AsRef<[u8]> for KemCipher { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } -} - -impl KemCipher { - pub const fn len(&self) -> usize { - self.0.len() - } - - pub const fn is_empty(&self) -> bool { - self.0.is_empty() - } -} - -impl Zeroize for KemCipher { - fn zeroize(&mut self) { - self.0.zeroize(); - } -} - -impl Drop for KemCipher { - fn drop(&mut self) { - self.zeroize(); - } -} - -impl KemDecap { - pub fn decapsulate(&mut self, ciphertext: &[u8]) -> Result { - let key = match self { - Self::Level1(decap) => decap.decapsulate_slice(ciphertext), - Self::Level3(decap) => decap.decapsulate_slice(ciphertext), - }; - - key.map_err(|_| aead::Error) - } -} diff --git a/src/lib.rs b/src/lib.rs --- a/src/lib.rs +++ b/src/lib.rs @@ -1,15 +1,22 @@ #![no_std] #![forbid(unsafe_code)] +use ml_kem::{MlKem512, MlKem768}; +pub use wharrgarbl_strobe::{Sec128, Sec256}; + pub mod handshake; -mod kem; pub mod transport; extern crate alloc; /// Version of WHARRGARBL that this crate implements. -pub static WHARRGHARBL_PROTO: &str = "WGBL-v0.0-STv1.0.2"; +pub static WHARRGHARBL_PROTO: &str = "WGBL-v0.1-STv1.0.2"; pub mod utils { pub use wharrgarbl_utils::BufferSlice; } + +pub type ClientHandshake128 = handshake::ClientHandshake; +pub type ServerHandshake128 = handshake::ServerHandshake; +pub type ClientHandshake256 = handshake::ClientHandshake; +pub type ServerHandshake256 = handshake::ServerHandshake; diff --git a/src/transport.rs b/src/transport.rs --- a/src/transport.rs +++ b/src/transport.rs @@ -1,28 +1,39 @@ +use core::marker::PhantomData; + use aead::{ AeadInOut, Buffer, Key, TagPosition, consts::{U16, U32}, }; use ctutils::{CtEq, CtSelect}; -use wharrgarbl_strobe::{StrobeRole, StrobeSecurity, StrobeState}; +use wharrgarbl_strobe::{StrobeRole, StrobeState, traits::StrobeSecurity}; use crate::WHARRGHARBL_PROTO; -pub struct AeadStrobe { +pub struct AeadStrobe { pub(crate) key: Key, - param: StrobeSecurity, + param: PhantomData, } -impl aead::AeadCore for AeadStrobe { +impl aead::AeadCore for AeadStrobe { type NonceSize = U16; type TagSize = U16; const TAG_POSITION: TagPosition = TagPosition::Postfix; } -impl aead::KeySizeUser for AeadStrobe { +impl aead::KeySizeUser for AeadStrobe { type KeySize = U32; } -impl aead::AeadInOut for AeadStrobe { +impl aead::KeyInit for AeadStrobe { + fn new(key: &Key) -> Self { + Self { + key: *key, + param: PhantomData, + } + } +} + +impl aead::AeadInOut for AeadStrobe { fn encrypt_inout_detached( &self, nonce: &aead::Nonce, @@ -30,11 +41,10 @@ mut buffer: aead::inout::InOutBuf<'_, '_, u8>, ) -> aead::Result> { let mut tag: aead::Tag = Default::default(); - let mut strobe = - StrobeState::new(WHARRGHARBL_PROTO.as_bytes(), self.param, StrobeRole::Sender); + let mut strobe = StrobeState::::new(WHARRGHARBL_PROTO.as_bytes(), StrobeRole::Sender); strobe.key(&self.key); - strobe.meta_ad(&self.param.to_bytes()); + strobe.meta_ad(&S::to_bytes()); strobe.meta_ad(nonce); strobe.ad(associated_data); strobe.send_enc(buffer.get_out()); @@ -51,14 +61,10 @@ mut buffer: aead::inout::InOutBuf<'_, '_, u8>, tag: &aead::Tag, ) -> aead::Result<()> { - let mut strobe = StrobeState::new( - WHARRGHARBL_PROTO.as_bytes(), - self.param, - StrobeRole::Receiver, - ); + let mut strobe = StrobeState::::new(WHARRGHARBL_PROTO.as_bytes(), StrobeRole::Receiver); strobe.key(&self.key); - strobe.meta_ad(&self.param.to_bytes()); + strobe.meta_ad(&S::to_bytes()); strobe.meta_ad(nonce); strobe.ad(associated_data); strobe.recv_enc(buffer.get_out()); @@ -67,20 +73,20 @@ } } -impl zeroize::Zeroize for AeadStrobe { +impl zeroize::Zeroize for AeadStrobe { fn zeroize(&mut self) { self.key.zeroize(); } } -pub struct AeadState { - pub(crate) aead: AeadStrobe, - pub(crate) epstein: aead::Nonce, - pub(crate) trump: aead::Nonce, +pub struct AeadState { + pub(crate) aead: AeadStrobe, + pub(crate) epstein: aead::Nonce>, + pub(crate) trump: aead::Nonce>, handshake_role: StrobeRole, } -impl zeroize::Zeroize for AeadState { +impl zeroize::Zeroize for AeadState { fn zeroize(&mut self) { self.aead.zeroize(); self.epstein.zeroize(); @@ -88,20 +94,19 @@ } } -impl zeroize::ZeroizeOnDrop for AeadState {} +impl zeroize::ZeroizeOnDrop for AeadState {} -impl Drop for AeadState { +impl Drop for AeadState { fn drop(&mut self) { zeroize::Zeroize::zeroize(self); } } -impl AeadState { +impl AeadState { pub(crate) fn new( - key: Key, - sec: StrobeSecurity, - outbound: aead::Nonce, - inbound: aead::Nonce, + key: Key>, + outbound: aead::Nonce>, + inbound: aead::Nonce>, role: StrobeRole, ) -> Self { assert_ne!( @@ -110,20 +115,23 @@ ); Self { - aead: AeadStrobe { key, param: sec }, + aead: AeadStrobe { + key, + param: PhantomData, + }, epstein: outbound, trump: inbound, handshake_role: role, } } - fn select_nonce(&self, sending_role: StrobeRole) -> aead::Nonce { + fn select_nonce(&self, sending_role: StrobeRole) -> aead::Nonce> { let role_context = self.handshake_role ^ sending_role; self.epstein.ct_select(&self.trump, role_context.ct_eq(&1)) } - fn mix_nonce(&self, position: [u8; 8], sending_role: StrobeRole) -> aead::Nonce { + fn mix_nonce(&self, position: [u8; 8], sending_role: StrobeRole) -> aead::Nonce> { let mut nonce = self.select_nonce(sending_role); let mid = nonce.len() - position.len(); @@ -136,7 +144,7 @@ nonce } - pub fn split(&self) -> (SendState<'_>, RecvState<'_>) { + pub fn split(&self) -> (SendState<'_, S>, RecvState<'_, S>) { ( SendState { transport: self, @@ -150,12 +158,12 @@ } } -pub struct SendState<'a> { - transport: &'a AeadState, +pub struct SendState<'a, S: StrobeSecurity> { + transport: &'a AeadState, counter: u64, } -impl SendState<'_> { +impl SendState<'_, S> { pub fn encrypt(&mut self, buffer: &mut dyn Buffer, ad: &[u8]) -> aead::Result<()> { if self.counter.ct_eq(&u64::MAX).into() { return Err(aead::Error); @@ -175,12 +183,12 @@ } } -pub struct RecvState<'a> { - transport: &'a AeadState, +pub struct RecvState<'a, S: StrobeSecurity> { + transport: &'a AeadState, counter: u64, } -impl RecvState<'_> { +impl RecvState<'_, S> { pub fn decrypt(&mut self, buffer: &mut dyn Buffer, ad: &[u8]) -> aead::Result<()> { if self.counter.ct_eq(&u64::MAX).into() { return Err(aead::Error); @@ -203,6 +211,8 @@ #[cfg(test)] mod tests { + use wharrgarbl_strobe::{Sec128, Sec256}; + use super::*; #[test] @@ -216,16 +226,14 @@ let outbound = 123u128.to_ne_bytes(); let inbound = 234u128.to_ne_bytes(); - let alice = AeadState::new( + let alice = AeadState::::new( shared_secret.into(), - StrobeSecurity::B128, outbound.into(), inbound.into(), StrobeRole::Sender, ); - let bob = AeadState::new( + let bob = AeadState::::new( shared_secret.into(), - StrobeSecurity::B128, outbound.into(), inbound.into(), StrobeRole::Receiver, @@ -291,16 +299,14 @@ let outbound = 123u128.to_ne_bytes(); let inbound = 234u128.to_ne_bytes(); - let alice = AeadState::new( + let alice = AeadState::::new( shared_secret.into(), - StrobeSecurity::B128, outbound.into(), inbound.into(), StrobeRole::Sender, ); - let bob = AeadState::new( + let bob = AeadState::::new( shared_secret.into(), - StrobeSecurity::B256, outbound.into(), inbound.into(), StrobeRole::Receiver, diff --git a/wharrgarbl-strobe/Cargo.toml b/wharrgarbl-strobe/Cargo.toml --- a/wharrgarbl-strobe/Cargo.toml +++ b/wharrgarbl-strobe/Cargo.toml @@ -7,15 +7,12 @@ version.workspace = true license.workspace = true -[features] -parallel = ["keccak/parallel"] - [dependencies] wharrgarbl-utils = { path = "../wharrgarbl-utils", version = "0.1" } aead.workspace = true zeroize.workspace = true ctutils.workspace = true -keccak = "0.2" +keccak = { version = "0.2", features = ["parallel"] } hybrid-array.workspace = true [dev-dependencies] diff --git a/.tangled/workflows/test.yml b/.tangled/workflows/test.yml --- a/.tangled/workflows/test.yml +++ b/.tangled/workflows/test.yml @@ -15,6 +15,6 @@ - name: Format check command: cargo fmt --all --check - name: Clippy - command: cargo clippy --locked --no-deps + command: cargo clippy --workspace --locked --no-deps - name: Tests - command: cargo test --locked --no-fail-fast + command: cargo test --workspace --locked --no-fail-fast diff --git a/wharrgarbl-strobe/src/basic_kats.rs b/wharrgarbl-strobe/src/basic_kats.rs --- a/wharrgarbl-strobe/src/basic_kats.rs +++ b/wharrgarbl-strobe/src/basic_kats.rs @@ -7,13 +7,13 @@ use aead::consts::{U16, U65}; use hybrid_array::Array; -use crate::{StrobeRole, StrobeSecurity, keccakf::KECCAK_BUFFER_SIZE, strobe::StrobeState}; +use crate::{Sec128, Sec256, StrobeRole, keccakf::KECCAK_BUFFER_SIZE, strobe::StrobeState}; extern crate std; #[test] fn test_init_128() { - let s = StrobeState::new(b"", StrobeSecurity::B128, StrobeRole::Sender); + let s = StrobeState::::new(b"", StrobeRole::Sender); let expected_st: [u8; KECCAK_BUFFER_SIZE] = [ 0x9c, 0x7f, 0x16, 0x8f, 0xf8, 0xfd, 0x55, 0xda, 0x2a, 0xa7, 0x3c, 0x23, 0x55, 0x65, 0x35, @@ -37,7 +37,7 @@ #[test] fn test_init_256() { - let s = StrobeState::new(b"", StrobeSecurity::B256, StrobeRole::Sender); + let s = StrobeState::::new(b"", StrobeRole::Sender); let expected_st: [u8; KECCAK_BUFFER_SIZE] = [ 0x37, 0xc1, 0x15, 0x06, 0xed, 0x61, 0xe7, 0xda, 0x7c, 0x1a, 0x2f, 0x2c, 0x1f, 0x49, 0x74, @@ -62,7 +62,7 @@ #[test] fn test_metadata() { // We will accumulate output over 3 operations and 3 meta-operations - let mut s = StrobeState::new(b"metadatatest", StrobeSecurity::B256, StrobeRole::Sender); + let mut s = StrobeState::::new(b"metadatatest", StrobeRole::Sender); let mut output = std::vec::Vec::new(); let buf = b"meta1"; @@ -116,7 +116,7 @@ #[test] fn test_seq() { - let mut s = StrobeState::new(b"seqtest", StrobeSecurity::B256, StrobeRole::Sender); + let mut s = StrobeState::::new(b"seqtest", StrobeRole::Sender); let mut buf = [0u8; 10]; s.prf(&mut buf[..]); @@ -172,16 +172,8 @@ #[test] fn test_enc_correctness() { let orig_msg = b"Hello there"; - let mut tx = StrobeState::new( - b"enccorrectnesstest", - StrobeSecurity::B256, - StrobeRole::Sender, - ); - let mut rx = StrobeState::new( - b"enccorrectnesstest", - StrobeSecurity::B256, - StrobeRole::Receiver, - ); + let mut tx = StrobeState::::new(b"enccorrectnesstest", StrobeRole::Sender); + let mut rx = StrobeState::::new(b"enccorrectnesstest", StrobeRole::Receiver); tx.key(b"the-combination-on-my-luggage"); rx.key(b"the-combination-on-my-luggage"); @@ -196,8 +188,8 @@ #[test] fn test_mac_correctness_and_soundness() { - let mut tx = StrobeState::new(b"mactest", StrobeSecurity::B256, StrobeRole::Sender); - let mut rx = StrobeState::new(b"mactest", StrobeSecurity::B256, StrobeRole::Receiver); + let mut tx = StrobeState::::new(b"mactest", StrobeRole::Sender); + let mut rx = StrobeState::::new(b"mactest", StrobeRole::Receiver); // Just do some stuff with the state @@ -225,7 +217,7 @@ #[test] fn test_long_inputs() { - let mut s = StrobeState::new(b"bigtest", StrobeSecurity::B256, StrobeRole::Sender); + let mut s = StrobeState::::new(b"bigtest", StrobeRole::Sender); const BIG_N: usize = 9823; const SMALL_N: usize = 65; let big_data = [0x34u8; BIG_N]; @@ -282,7 +274,7 @@ fn test_streaming_correctness() { // Compute a few things without breaking up their inputs let one_shot_st: std::vec::Vec = { - let mut s = StrobeState::new(b"streamingtest", StrobeSecurity::B256, StrobeRole::Receiver); + let mut s = StrobeState::::new(b"streamingtest", StrobeRole::Receiver); s.ad(b"mynonce"); @@ -298,7 +290,7 @@ }; // Now do the same thing but stream the inputs let streamed_st: std::vec::Vec = { - let mut s = StrobeState::new(b"streamingtest", StrobeSecurity::B256, StrobeRole::Receiver); + let mut s = StrobeState::::new(b"streamingtest", StrobeRole::Receiver); s.ad(b"my"); s.ad(b"nonce"); diff --git a/wharrgarbl-strobe/src/keccakf.rs b/wharrgarbl-strobe/src/keccakf.rs --- a/wharrgarbl-strobe/src/keccakf.rs +++ b/wharrgarbl-strobe/src/keccakf.rs @@ -39,6 +39,7 @@ } /// Performs the keccakf\[1600\] permutation on an aligned byte buffer + #[inline] pub(crate) fn permute_f1600(&mut self) { let mut keccak_block = [0u64; KECCAK_BLOCK_SIZE]; diff --git a/wharrgarbl-strobe/src/lib.rs b/wharrgarbl-strobe/src/lib.rs --- a/wharrgarbl-strobe/src/lib.rs +++ b/wharrgarbl-strobe/src/lib.rs @@ -7,30 +7,18 @@ mod basic_kats; #[cfg(test)] mod herding_kats; +pub mod traits; use core::ops::BitXor; +use aead::consts::{U128, U256}; pub use strobe::StrobeState; /// Version of Strobe that this crate implements. pub static STROBE_VERSION: &str = "1.0.2"; -#[derive(Debug, Clone, Copy)] -#[repr(u16)] -pub enum StrobeSecurity { - B128 = 128, - B256 = 256, -} - -impl StrobeSecurity { - pub const fn rachet_bytes(self) -> usize { - (self as usize) >> 3 - } - - pub const fn to_bytes(self) -> [u8; 2] { - (self as u16).to_le_bytes() - } -} +pub type Sec128 = U128; +pub type Sec256 = U256; #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u8)] diff --git a/wharrgarbl-strobe/src/strobe.rs b/wharrgarbl-strobe/src/strobe.rs --- a/wharrgarbl-strobe/src/strobe.rs +++ b/wharrgarbl-strobe/src/strobe.rs @@ -1,12 +1,15 @@ +use core::marker::PhantomData; + use ctutils::{Choice, CtAssign, CtEq, CtLt, CtSelect}; use hybrid_array::{Array, ArraySize}; use zeroize::Zeroize; use crate::{ - STROBE_VERSION, StrobeRole, StrobeSecurity, + STROBE_VERSION, StrobeRole, keccakf::{KECCAK_BUFFER_SIZE, KeccakF1600}, opflags::OpFlags, ops, + traits::StrobeSecurity, }; /// Private integer representations for Role, to allow for better constant time compat. @@ -16,11 +19,11 @@ } #[derive(Clone)] -pub struct StrobeState { +pub struct StrobeState { /// Internal Keccak state pub(crate) state: KeccakF1600, /// Security parameter (128 or 256 bits) - sec: StrobeSecurity, + sec: PhantomData, /// This is the `R` parameter in the Strobe spec rate: usize, /// Index into `state` @@ -43,7 +46,7 @@ pub fn $name(&mut self, data: &mut [u8]) { let flags = $flags; let prev_flags = self.prev_flags; - let more = prev_flags.ct_eq(&flags); + let more = prev_flags == flags; self.operate(flags, data, more); } )* @@ -59,14 +62,14 @@ pub fn $name(&mut self, data: &[u8]) { let flags = $flags; let prev_flags = self.prev_flags; - let more = prev_flags.ct_eq(&flags); + let more = prev_flags == flags; self.operate_no_mutate(flags, data, more); } )* }; } -impl Zeroize for StrobeState { +impl Zeroize for StrobeState { fn zeroize(&mut self) { self.state.zeroize(); self.rate.zeroize(); @@ -77,41 +80,37 @@ } } -impl zeroize::ZeroizeOnDrop for StrobeState {} +impl zeroize::ZeroizeOnDrop for StrobeState {} -impl Drop for StrobeState { +impl Drop for StrobeState { fn drop(&mut self) { self.zeroize(); } } -impl core::fmt::Display for StrobeState { +impl core::fmt::Display for StrobeState { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_str("Strobe-Keccak-")?; - match self.sec { - StrobeSecurity::B128 => f.write_str("128")?, - StrobeSecurity::B256 => f.write_str("256")?, - } + write!(f, "{}", S::to_usize())?; f.write_str("/1600-v")?; f.write_str(STROBE_VERSION) } } -impl core::fmt::Debug for StrobeState { +impl core::fmt::Debug for StrobeState { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { // Do not reveal internal state of StrobeState, other than its security level f.debug_struct("StrobeState") - .field("sec", &self.sec) + .field("sec", &S::to_usize()) .field("version", &STROBE_VERSION) .finish_non_exhaustive() } } -impl StrobeState { +impl StrobeState { /// Makes a new `StrobeState` object with a given protocol byte string and security parameter. - pub fn new(protocol: &[u8], sec: StrobeSecurity, role: StrobeRole) -> Self { - let rate = KECCAK_BUFFER_SIZE - (sec as usize) / 4 - 2; - assert!((1..254).contains(&rate)); + pub fn new(protocol: &[u8], role: StrobeRole) -> Self { + let rate = S::rate(); // Initialize state: st = F([0x01, R+2, 0x01, 0x00, 0x01, 0x60] + b"STROBEvX.Y.Z") let mut state_buffer = [0u8; KECCAK_BUFFER_SIZE]; @@ -125,7 +124,7 @@ let mut strobe = Self { state, - sec, + sec: PhantomData, rate, position: 0, start: 0, @@ -148,6 +147,7 @@ /// /// This is a modification to the Strobe API surface to prevent misuse of op calls, /// preventing panics/errors so that the compiler can optimise better. + #[inline] pub fn reset_ops(&mut self) { // This prevents streaming so to always make the prev_flags == flags // comparison always fail @@ -155,6 +155,7 @@ } // Runs the permutation function on the internal state + #[inline] fn permutation_f(&mut self) { self.state.0[self.position] ^= self.start as u8; self.state.0[self.position + 1] ^= 0x04; @@ -169,7 +170,7 @@ fn increment_position(&mut self, increment: usize) { self.position += increment; - if self.position.ct_eq(&self.rate).to_bool() { + if self.position == self.rate { self.permutation_f(); } } @@ -269,6 +270,7 @@ /// Mixes the current state index and flags into the state, accounting for whether we are /// sending or receiving + #[inline] fn begin_op(&mut self, mut flags: OpFlags) { if flags.contains(OpFlags::TRANSPORT).to_bool() { let op_role = role::SENDER.ct_select(&role::RECEIVER, flags.contains(OpFlags::INBOUND)); @@ -294,11 +296,12 @@ /// Performs the state / data transformation that corresponds to the given flags. If `more` is /// given, this will treat `data` as a continuation of the data given in the previous /// call to `operate`. - fn operate(&mut self, mut flags: OpFlags, data: &mut [u8], more: Choice) { + #[inline] + fn operate(&mut self, mut flags: OpFlags, data: &mut [u8], more: bool) { self.prev_flags = flags; // If `more` isn't set, this is a new operation. Do the begin_op sequence - if !more.to_bool() { + if !more { self.begin_op(flags); } @@ -311,7 +314,7 @@ debug_assert!(flags != ops::KEY && flags.contains(OpFlags::CIPHER).to_bool()); static SPECIAL_CASES: [OpFlags; 3] = [ops::PRF, ops::SEND_MAC, ops::SEND_ENC]; - static OPS: [fn(&mut StrobeState, data: &mut [u8]); 4] = [ + let ops: [fn(&mut Self, data: &mut [u8]); 4] = [ StrobeState::squeeze, StrobeState::copy_state, StrobeState::absorb_and_set, @@ -329,17 +332,18 @@ }) & 0b11; - OPS[index](self, data); + ops[index](self, data); } /// Performs the state transformation that corresponds to the given flags. If `more` is given, /// this will treat `data` as a continuation of the data given in the previous call to /// `operate`. This uses non-mutating variants of the specializations of the `duplex` function. - fn operate_no_mutate(&mut self, mut flags: OpFlags, data: &[u8], more: Choice) { + #[inline] + fn operate_no_mutate(&mut self, mut flags: OpFlags, data: &[u8], more: bool) { self.prev_flags = flags; // If `more` isn't set, this is a new operation. Do the begin_op sequence - if !more.to_bool() { + if !more { self.begin_op(flags); } @@ -350,17 +354,17 @@ // RATCHET is special cased to never call operate/operate_no_mutate directly debug_assert!(flags == ops::KEY || !flags.contains(OpFlags::CIPHER).to_bool()); - static OPS: [fn(&mut StrobeState, data: &[u8]); 2] = - [StrobeState::absorb, StrobeState::overwrite]; + let ops: [fn(&mut Self, data: &[u8]); 2] = [StrobeState::absorb, StrobeState::overwrite]; let index = (flags.ct_eq(&ops::KEY).to_u8() & 1) as usize; - OPS[index](self, data); + ops[index](self, data); } + #[inline] fn recv_mac_inner(&mut self, flags: OpFlags, mac_copy: &mut [u8]) -> Result<(), aead::Error> { // recv_mac can never be streamed - self.operate(flags, mac_copy, Choice::FALSE); + self.operate(flags, mac_copy, false); // Constant-time MAC check. This accumulates the truth values of byte == 0 let all_zero = mac_copy @@ -386,14 +390,15 @@ self.recv_mac_inner(ops::META_RECV_MAC, &mut mac_copy) } + #[inline] fn ratchet_inner(&mut self, mut flags: OpFlags, num_bytes_to_zero: usize) { - let more = self.prev_flags.ct_eq(&flags); + let more = self.prev_flags == flags; // We don't make an `operate` call, since this is a super special case. That means we have // to make the `begin_op` call manually. self.prev_flags = flags; - if !more.to_bool() { + if !more { self.begin_op(flags); } @@ -451,18 +456,20 @@ #[cfg(test)] mod tests { + use aead::consts::U128; + use super::*; extern crate std; #[test] fn version_formatting() { - let s = StrobeState::new(b"", StrobeSecurity::B128, StrobeRole::Sender); + let s = StrobeState::::new(b"", StrobeRole::Sender); let display = std::format!("{s}"); let debug = std::format!("{s:?}"); assert_eq!(&display, "Strobe-Keccak-128/1600-v1.0.2"); - assert_eq!(&debug, "StrobeState { sec: B128, version: \"1.0.2\", .. }"); + assert_eq!(&debug, "StrobeState { sec: 128, version: \"1.0.2\", .. }"); } } diff --git a/wharrgarbl-strobe/src/traits.rs b/wharrgarbl-strobe/src/traits.rs new file mode 100644 --- /dev/null +++ b/wharrgarbl-strobe/src/traits.rs @@ -0,0 +1,19 @@ +use aead::consts::{U128, U256}; +use hybrid_array::typenum::Unsigned; + +use crate::keccakf::KECCAK_BUFFER_SIZE; + +pub trait StrobeSecurity: Unsigned { + fn to_bytes() -> [u8; 2] { + Self::to_u16().to_le_bytes() + } + fn rate() -> usize { + KECCAK_BUFFER_SIZE - (Self::to_usize()) / 4 - 2 + } + fn ratchet_bytes() -> usize { + Self::to_usize() >> 3 + } +} + +impl StrobeSecurity for U128 {} +impl StrobeSecurity for U256 {} diff --git a/wharrgarbl-utils/src/lib.rs b/wharrgarbl-utils/src/lib.rs --- a/wharrgarbl-utils/src/lib.rs +++ b/wharrgarbl-utils/src/lib.rs @@ -8,13 +8,10 @@ impl<'slice> BufferSlice<'slice> { pub const fn new(buffer: &'slice mut [u8]) -> Self { - Self { - end: buffer.len(), - buffer, - } + Self { end: 0, buffer } } - pub const fn reset(&mut self) { + pub const fn fill(&mut self) { self.end = self.buffer.len(); } } @@ -47,7 +44,7 @@ } fn truncate(&mut self, len: usize) { - self.end = len.min(self.buffer.len()); + self.end = len.min(self.end); } } @@ -59,12 +56,12 @@ extern crate alloc; #[test] - fn defaults_to_full_buffer_size() { + fn defaults_to_empty_buffer_size() { let mut buf = alloc::vec![0u8; 128]; let buf_slice = BufferSlice::new(&mut buf); - assert_eq!(buf_slice.len(), 128); + assert_eq!(buf_slice.len(), 0); } #[test] @@ -73,13 +70,15 @@ let mut buf_slice = BufferSlice::new(&mut buf); + buf_slice.extend_from_slice(&[0; 70]).unwrap(); + buf_slice.truncate(64); assert_eq!(buf_slice.len(), 64); buf_slice.truncate(256); - assert_eq!(buf_slice.len(), 128); + assert_eq!(buf_slice.len(), 64); } #[test] @@ -88,28 +87,22 @@ let mut buf_slice = BufferSlice::new(&mut buf); - assert_eq!(buf_slice.len(), 128); - assert_eq!(buf_slice.extend_from_slice(&[0, 0, 0]), Err(aead::Error)); - - buf_slice.truncate(64); + assert_eq!(buf_slice.len(), 0); + assert_eq!(buf_slice.extend_from_slice(&[0; 129]), Err(aead::Error)); assert_eq!(buf_slice.extend_from_slice(&[0, 0, 0, 0, 0, 0]), Ok(())); - assert_eq!(buf_slice.len(), 70); + assert_eq!(buf_slice.len(), 6); } #[test] - fn reset_sets_length_to_buffer_max_length() { + fn fill_sets_length_to_buffer_max_length() { let mut buf = alloc::vec![0u8; 128]; let mut buf_slice = BufferSlice::new(&mut buf); - assert_eq!(buf_slice.len(), 128); + assert_eq!(buf_slice.len(), 0); - buf_slice.truncate(64); - - assert_eq!(buf_slice.len(), 64); - - buf_slice.reset(); + buf_slice.fill(); assert_eq!(buf_slice.len(), 128); } diff --git a/wharrgarbl-strobe/src/herding_kats/harness.rs b/wharrgarbl-strobe/src/herding_kats/harness.rs --- a/wharrgarbl-strobe/src/herding_kats/harness.rs +++ b/wharrgarbl-strobe/src/herding_kats/harness.rs @@ -2,18 +2,17 @@ use std::{string::String, vec::Vec}; -use aead::consts::U14; +use aead::consts::{U14, U128, U256}; use serde::{Deserialize, Deserializer, de}; -use crate::{StrobeRole, StrobeSecurity, strobe::StrobeState}; +use crate::{StrobeRole, strobe::StrobeState, traits::StrobeSecurity}; /// The harness we will put on our KATs so we can herd them and make them do tests. /// (This is the top-level structure of the JSON we find in the test vectors) #[derive(Deserialize)] struct KatHarness { proto_string: String, - #[serde(deserialize_with = "security_param_from_bits")] - security: StrobeSecurity, + security: u64, operations: Vec, } @@ -38,11 +37,43 @@ Length(usize), } -// Given the name of the operation and its required parameters, run the STROBE operation. -fn run_kat_operation( +enum StrobeKinds { + U128(StrobeState), + U256(StrobeState), +} + +impl StrobeKinds { + fn new(protocol: &[u8], security: u64) -> Self { + match security { + 128 => Self::U128(StrobeState::new(protocol, StrobeRole::Sender)), + 256 => Self::U256(StrobeState::new(protocol, StrobeRole::Sender)), + _ => panic!("Invalid Security parameter"), + } + } + + fn get_state(&self) -> &[u8] { + match self { + Self::U128(s) => &s.state.0, + Self::U256(s) => &s.state.0, + } + } + + fn run_kat_operation(&mut self, op_name: &str, meta: bool, dol: DataOrLength, more: bool) { + match self { + Self::U128(s) => { + exec_kat(s, op_name, meta, dol, more); + } + Self::U256(s) => { + exec_kat(s, op_name, meta, dol, more); + } + } + } +} + +fn exec_kat( + s: &mut StrobeState, op_name: &str, meta: bool, - s: &mut StrobeState, dol: DataOrLength, more: bool, ) { @@ -114,7 +145,7 @@ operations, } = serde_json::from_reader(file).unwrap(); - let mut strobe = StrobeState::new(proto_string.as_bytes(), security, StrobeRole::Sender); + let mut strobe = StrobeKinds::new(proto_string.as_bytes(), security); operations.into_iter().for_each( |KatOperation { @@ -127,7 +158,7 @@ }| match name.as_str() { "init" => { // Check that the initial state matches what is expected in the KAT operation. - assert_eq!(&strobe.state.0[..], expected_state_after.as_slice()); + assert_eq!(strobe.get_state(), expected_state_after.as_slice()); } name => { // RATCHET inputs are given as strings of zeros instead of lengths. So just take the @@ -138,9 +169,9 @@ DataOrLength::Data(input_data.as_mut_slice()) }; - run_kat_operation(name, meta, &mut strobe, input, stream); + strobe.run_kat_operation(name, meta, input, stream); - assert_eq!(&strobe.state.0[..], expected_state_after.as_slice()); + assert_eq!(strobe.get_state(), expected_state_after.as_slice()); // Only test expected output if the test vector has output to test against. if let Some(expected) = expected_output { @@ -152,18 +183,18 @@ ); } -fn security_param_from_bits<'de, D: Deserializer<'de>>( - deserializer: D, -) -> Result { - match u64::deserialize(deserializer)? { - 128 => Ok(StrobeSecurity::B128), - 256 => Ok(StrobeSecurity::B256), - n => Err(de::Error::custom(std::format!( - "Invalid security parameter: {}", - n - ))), - } -} +// fn security_param_from_bits<'de, D: Deserializer<'de>>( +// deserializer: D, +// ) -> Result { +// match u64::deserialize(deserializer)? { +// 128 => Ok(StrobeSecurity::B128), +// 256 => Ok(StrobeSecurity::B256), +// n => Err(de::Error::custom(std::format!( +// "Invalid security parameter: {}", +// n +// ))), +// } +// } fn bytes_from_hex<'de, D>(deserializer: D) -> Result, D::Error> where