From e22bbfeb37dfde8c9912bdf26b7ce00dc6eea31a Mon Sep 17 00:00:00 2001 From: Sachymetsu Date: Fri, 10 Apr 2026 14:04:27 +0000 Subject: [PATCH] Initial commit --- .gitignore | 1 + Cargo.lock | 184 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 18 ++++++++++++++++++ README.md | 7 +++++++ src/basic_kats.rs | 314 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/keccakf.rs | 130 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 21 +++++++++++++++++++++ src/strobe.rs | 473 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/herd_of_kats/mod.rs | 4 ++++ 9 file(s) changed, 1152 insertion(s)(+), 0 deletion(s)(-) diff --git a/.gitignore b/.gitignore new file mode 100644 --- /dev/null +++ b/.gitignore @@ -0,0 +1,1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,184 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "keccak" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" +dependencies = [ + "cfg-if", + "cpufeatures", +] + +[[package]] +name = "libc" +version = "0.2.184" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-big-array" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "wharrgarbl" +version = "0.1.0" +dependencies = [ + "enumflags2", + "hex", + "keccak", + "serde", + "serde-big-array", + "serde_json", + "subtle", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "wharrgarbl" +description = "A whimsical STROBE based encryption protocol" +authors = ["Sachy.dev "] +repository = "https://tangled.org/sachy.dev/wharrgarbl" +version = "0.1.0" +edition = "2024" + +[dependencies] +keccak = "0.2" +enumflags2 = "0.7.12" +subtle = { version = "2.6", default-features = false } + +[dev-dependencies] +serde_json = "1" +hex = "0.4" +serde = { version = "1.0.210", default-features = false, features = ["derive"] } +serde-big-array = { version = "0.5" } diff --git a/README.md b/README.md new file mode 100644 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# WHARRGARBL + +A WIP Strobe+AKE protocol with a whimsical name. Based off the [reference rust implementation here](https://github.com/rozbb/strobe-rs), though with diverging API choices for experimentation. + +## License + +TBA when ready diff --git a/src/basic_kats.rs b/src/basic_kats.rs new file mode 100644 --- /dev/null +++ b/src/basic_kats.rs @@ -0,0 +1,314 @@ +//! ## KAT, YA BASIC +//! +//! This module is for the initial set of KATs (Known Answer Tests) that are hard-coded. +//! Some tests in the original repo are omitted because we can no longer panic from this +//! implementation's public API surface. + +use crate::{ + keccakf::KECCAK_BUFFER_SIZE, + strobe::{SecurityParameter, StrobeState}, +}; + +extern crate std; + +#[test] +fn test_init_128() { + let s = StrobeState::new(b"", SecurityParameter::B128); + + let expected_st: [u8; KECCAK_BUFFER_SIZE] = [ + 0x9c, 0x7f, 0x16, 0x8f, 0xf8, 0xfd, 0x55, 0xda, 0x2a, 0xa7, 0x3c, 0x23, 0x55, 0x65, 0x35, + 0x63, 0xdc, 0x0c, 0x47, 0x5c, 0x55, 0x15, 0x26, 0xf6, 0x73, 0x3b, 0xea, 0x22, 0xf1, 0x6c, + 0xb5, 0x7c, 0xd3, 0x1f, 0x68, 0x2e, 0x66, 0x0e, 0xe9, 0x12, 0x82, 0x4a, 0x77, 0x22, 0x01, + 0xee, 0x13, 0x94, 0x22, 0x6f, 0x4a, 0xfc, 0xb6, 0x2d, 0x33, 0x12, 0x93, 0xcc, 0x92, 0xe8, + 0xa6, 0x24, 0xac, 0xf6, 0xe1, 0xb6, 0x00, 0x95, 0xe3, 0x22, 0xbb, 0xfb, 0xc8, 0x45, 0xe5, + 0xb2, 0x69, 0x95, 0xfe, 0x7d, 0x7c, 0x84, 0x13, 0x74, 0xd1, 0xff, 0x58, 0x98, 0xc9, 0x2e, + 0xe0, 0x63, 0x6b, 0x06, 0x72, 0x73, 0x21, 0xc9, 0x2a, 0x60, 0x39, 0x07, 0x03, 0x53, 0x49, + 0xcc, 0xbb, 0x1b, 0x92, 0xb7, 0xb0, 0x05, 0x7e, 0x8f, 0xa8, 0x7f, 0xce, 0xbc, 0x7e, 0x88, + 0x65, 0x6f, 0xcb, 0x45, 0xae, 0x04, 0xbc, 0x34, 0xca, 0xbe, 0xae, 0xbe, 0x79, 0xd9, 0x17, + 0x50, 0xc0, 0xe8, 0xbf, 0x13, 0xb9, 0x66, 0x50, 0x4d, 0x13, 0x43, 0x59, 0x72, 0x65, 0xdd, + 0x88, 0x65, 0xad, 0xf9, 0x14, 0x09, 0xcc, 0x9b, 0x20, 0xd5, 0xf4, 0x74, 0x44, 0x04, 0x1f, + 0x97, 0xb6, 0x99, 0xdd, 0xfb, 0xde, 0xe9, 0x1e, 0xa8, 0x7b, 0xd0, 0x9b, 0xf8, 0xb0, 0x2d, + 0xa7, 0x5a, 0x96, 0xe9, 0x47, 0xf0, 0x7f, 0x5b, 0x65, 0xbb, 0x4e, 0x6e, 0xfe, 0xfa, 0xa1, + 0x6a, 0xbf, 0xd9, 0xfb, 0xf6, + ]; + + assert_eq!(&s.state.0, &expected_st); +} + +#[test] +fn test_init_256() { + let s = StrobeState::new(b"", SecurityParameter::B256); + + let expected_st: [u8; KECCAK_BUFFER_SIZE] = [ + 0x37, 0xc1, 0x15, 0x06, 0xed, 0x61, 0xe7, 0xda, 0x7c, 0x1a, 0x2f, 0x2c, 0x1f, 0x49, 0x74, + 0xb0, 0x71, 0x66, 0xc2, 0xea, 0x7f, 0x62, 0xec, 0xa6, 0xe0, 0x36, 0xc1, 0x6e, 0xae, 0x39, + 0xb4, 0xdf, 0x3a, 0x06, 0x11, 0xf1, 0x36, 0xc7, 0x33, 0x94, 0x31, 0x13, 0x2c, 0xdb, 0x18, + 0x03, 0x08, 0xc0, 0x53, 0x61, 0xab, 0xf7, 0xb9, 0xc6, 0x89, 0x49, 0xab, 0x1e, 0x5c, 0x0b, + 0xbf, 0xab, 0x0a, 0xb0, 0x66, 0xa0, 0x13, 0x96, 0xdb, 0x8d, 0xb1, 0x26, 0x02, 0x0c, 0xf7, + 0x96, 0xb2, 0x3f, 0x0e, 0xe1, 0xcf, 0x40, 0xda, 0x8f, 0x8b, 0xfc, 0x34, 0x27, 0x34, 0x14, + 0x4a, 0x64, 0x08, 0x29, 0x44, 0x5a, 0x67, 0xab, 0x3e, 0x15, 0x46, 0xc0, 0x97, 0xe3, 0x23, + 0xd3, 0xda, 0xe7, 0xc6, 0x2e, 0x62, 0xd3, 0xdd, 0xae, 0x90, 0x98, 0x31, 0xa1, 0x64, 0x9c, + 0xd8, 0x07, 0x97, 0x7b, 0x5e, 0x44, 0x88, 0xae, 0x42, 0xfc, 0x36, 0xec, 0x2c, 0x5a, 0x78, + 0x0d, 0x52, 0xa3, 0x22, 0xa6, 0xe9, 0xbe, 0xff, 0x73, 0x89, 0xcb, 0x8f, 0xe7, 0x6a, 0xb5, + 0x5d, 0xc6, 0xa0, 0x60, 0xa7, 0x22, 0xb9, 0x64, 0xb6, 0xe8, 0xfe, 0x8b, 0xb5, 0xb9, 0x1a, + 0x9b, 0xbc, 0x61, 0xc0, 0x86, 0x7e, 0x6d, 0xfc, 0x5b, 0x5c, 0x6d, 0xd5, 0xb5, 0xa7, 0x26, + 0xc9, 0x18, 0xe4, 0x0b, 0xe9, 0xb1, 0xcf, 0xa7, 0xef, 0xa6, 0x92, 0xf5, 0x05, 0xdc, 0xac, + 0xde, 0x80, 0x03, 0xe8, 0xbb, + ]; + + assert_eq!(&s.state.0, &expected_st); +} + +#[test] +fn test_metadata() { + // We will accumulate output over 3 operations and 3 meta-operations + let mut s = StrobeState::new(b"metadatatest", SecurityParameter::B256); + let mut output = std::vec::Vec::new(); + + let buf = b"meta1"; + s.meta_send_clr(buf); + output.extend_from_slice(buf); + + // This does not output anything + s.key(b"key"); + + let mut buf = [0u8; 10]; + s.meta_prf(&mut buf); + output.extend_from_slice(&buf[..]); + + // We don't have to re-zero the buffer. Our internal special-casing for PRF does this for us + s.prf(&mut buf); + output.extend_from_slice(&buf[..]); + + let buf = b"meta3"; + s.meta_send_clr(buf); + output.extend(buf); + + let mut buf = b"pt".to_vec(); + s.send_enc(buf.as_mut_slice()); + output.extend(buf); + + let expected_output = [ + 0x6d, 0x65, 0x74, 0x61, 0x31, 0xa7, 0xe5, 0x96, 0xe0, 0x8f, 0x39, 0x19, 0x3c, 0x4f, 0x84, + 0xdb, 0x00, 0xbb, 0xce, 0xbb, 0xf3, 0x7e, 0xc6, 0x33, 0x8b, 0x6d, 0x65, 0x74, 0x61, 0x33, + 0xe9, 0x0b, + ]; + let expected_st = [ + 0xe9, 0x0b, 0x29, 0xad, 0x32, 0x0c, 0x27, 0x53, 0x07, 0x48, 0xcd, 0x38, 0xde, 0xf7, 0x23, + 0xb0, 0x54, 0x21, 0x14, 0xae, 0xd1, 0xfc, 0x55, 0xd0, 0xc6, 0xc2, 0x58, 0x85, 0xaa, 0x5d, + 0x30, 0x30, 0x88, 0xb9, 0x6b, 0x55, 0xf0, 0x01, 0xbd, 0x30, 0xc4, 0xd5, 0x00, 0x72, 0xad, + 0x58, 0xad, 0x08, 0xbc, 0x0c, 0x7b, 0x8f, 0xad, 0xe5, 0x02, 0x57, 0xa9, 0xe4, 0xbe, 0xb0, + 0x1a, 0x96, 0x44, 0xc6, 0x25, 0x9d, 0x58, 0x30, 0x85, 0xf4, 0xee, 0xe0, 0xdd, 0x32, 0x39, + 0x18, 0x8d, 0x46, 0x02, 0xa2, 0x9a, 0x64, 0x3d, 0x7a, 0x4e, 0xd0, 0xaa, 0x57, 0xf1, 0x97, + 0x9e, 0xb5, 0xca, 0x18, 0x6c, 0xd2, 0x2b, 0x4f, 0xb6, 0x78, 0x30, 0x2f, 0xe1, 0xb0, 0x34, + 0x10, 0x21, 0xdc, 0xd6, 0xdd, 0x63, 0x2f, 0x14, 0x23, 0x41, 0x78, 0xe4, 0x98, 0x9c, 0x5c, + 0x8a, 0xae, 0x00, 0x31, 0xb7, 0xa9, 0x90, 0x16, 0x91, 0x41, 0x38, 0x0a, 0xc4, 0xe2, 0x3f, + 0x39, 0x4d, 0x5e, 0xc7, 0x58, 0x59, 0x5d, 0xe5, 0x31, 0xdc, 0x7c, 0x0b, 0x06, 0xca, 0x8b, + 0x95, 0x75, 0x31, 0x70, 0x9a, 0xee, 0x42, 0xf3, 0x2c, 0xef, 0x88, 0x1c, 0x6e, 0xc9, 0x9b, + 0x69, 0xe5, 0xaf, 0xfc, 0x30, 0x93, 0x51, 0x29, 0x17, 0x93, 0x46, 0x21, 0xc5, 0x6c, 0xc9, + 0x95, 0xd4, 0x58, 0x7c, 0xeb, 0x5a, 0x9d, 0x90, 0xa6, 0x7d, 0x5d, 0x36, 0xf6, 0x3f, 0xa6, + 0xde, 0xb2, 0x48, 0xe2, 0x88, + ]; + + assert_eq!(&output, &expected_output); + assert_eq!(&s.state.0, &expected_st[..]); +} + +#[test] +fn test_seq() { + let mut s = StrobeState::new(b"seqtest", SecurityParameter::B256); + + let mut buf = [0u8; 10]; + s.prf(&mut buf[..]); + + s.ad(b"Hello"); + + let mut buf = b"World".to_vec(); + s.send_enc(buf.as_mut_slice()); + + s.send_clr(b"foo"); + s.ratchet(32); + s.recv_clr(b"bar"); + + let mut buf = b"baz".to_vec(); + s.recv_enc(buf.as_mut_slice()); + + for i in 0..100 { + let mut buf = std::vec![b'X'; i]; + s.send_enc(buf.as_mut_slice()); + // For this test, we must disable automatic streaming in order to have + // the same resulting state as the reference, + s.reset_ops(); + } + + let mut buf = [0u8; 123]; + s.prf(&mut buf[..]); + + let mut buf = [0u8; 16]; + s.send_mac(&mut buf[..]); + + let final_st = &s.state.0; + + let expected_st = [ + 0xdf, 0x7a, 0x38, 0x71, 0x06, 0xcc, 0x24, 0x82, 0x11, 0x31, 0x60, 0x43, 0xa9, 0xf0, 0xf5, + 0xd0, 0x49, 0xc2, 0xce, 0xd3, 0x85, 0xfc, 0x9e, 0xa8, 0x0e, 0xc1, 0x46, 0xa4, 0xa1, 0x96, + 0x02, 0x30, 0x78, 0xe6, 0x16, 0x62, 0x50, 0x1b, 0xab, 0x23, 0x5d, 0xcb, 0x85, 0x34, 0x3a, + 0x67, 0xc6, 0x6c, 0xd8, 0x79, 0x45, 0xee, 0x2b, 0xaa, 0xc0, 0x09, 0x45, 0xc7, 0xf6, 0x42, + 0xd9, 0xbc, 0x43, 0xe1, 0xd5, 0x2c, 0x6e, 0x71, 0x6f, 0xfa, 0x9a, 0x39, 0x9d, 0x11, 0xfd, + 0x62, 0xfb, 0x15, 0x04, 0x85, 0xf9, 0xe3, 0xc1, 0x24, 0x95, 0x04, 0x84, 0x95, 0x3c, 0x74, + 0x38, 0x3d, 0x5e, 0x08, 0x87, 0x64, 0xa3, 0x57, 0xdd, 0xb0, 0x40, 0x5b, 0x40, 0x25, 0x93, + 0xb8, 0x3a, 0x75, 0x1d, 0xb7, 0xdf, 0xc4, 0x34, 0x4d, 0xfa, 0x94, 0xc6, 0x98, 0x13, 0xb3, + 0x75, 0xf2, 0xdc, 0xd0, 0xe3, 0xe9, 0x44, 0xba, 0xfd, 0x98, 0x13, 0xc1, 0x59, 0xc7, 0x46, + 0xa7, 0xb0, 0x65, 0x70, 0x20, 0x3d, 0x56, 0xeb, 0x84, 0x18, 0x1c, 0xca, 0x5b, 0x7a, 0xe4, + 0xad, 0x3a, 0x57, 0x6b, 0x40, 0x80, 0x29, 0x0c, 0x63, 0x11, 0xd8, 0x6f, 0x89, 0xb8, 0x32, + 0xf0, 0xb1, 0xde, 0x8c, 0x0a, 0x4f, 0x00, 0x90, 0x16, 0x0d, 0xc1, 0x9f, 0xd4, 0x69, 0x9c, + 0x56, 0xb1, 0xd8, 0x9e, 0xc0, 0x8d, 0x40, 0x7a, 0x36, 0xe3, 0xb3, 0x9c, 0xd4, 0x91, 0x17, + 0xd7, 0xed, 0x4c, 0x4b, 0xa5, + ]; + + assert_eq!(final_st, &expected_st); +} + +#[test] +fn test_enc_correctness() { + let orig_msg = b"Hello there"; + let mut tx = StrobeState::new(b"enccorrectnesstest", SecurityParameter::B256); + let mut rx = StrobeState::new(b"enccorrectnesstest", SecurityParameter::B256); + + tx.key(b"the-combination-on-my-luggage"); + rx.key(b"the-combination-on-my-luggage"); + + // Encrypt and decrypt the original message + let mut buf = orig_msg.to_vec(); + tx.send_enc(buf.as_mut_slice()); + rx.recv_enc(buf.as_mut_slice()); + + assert_eq!(orig_msg, buf.as_slice()); +} + +#[test] +fn test_mac_correctness_and_soundness() { + let mut tx = StrobeState::new(b"mactest", SecurityParameter::B256); + let mut rx = StrobeState::new(b"mactest", SecurityParameter::B256); + + // Just do some stuff with the state + + tx.key(b"secretsauce"); + let mut msg = b"attack at dawn".to_vec(); + tx.send_enc(msg.as_mut_slice()); + + let mut mac = [0u8; 16]; + tx.send_mac(&mut mac[..]); + + rx.key(b"secretsauce"); + rx.recv_enc(&mut msg[..]); + + // Test that valid MACs are accepted + let mut rx_copy = rx.clone(); + let good_res = rx_copy.recv_mac(&mac); + assert!(good_res.is_ok()); + + // Test that invalid MACs are rejected. Flip a bit + let mut bad_mac = mac; + bad_mac[0] ^= 1; + let bad_res = rx.recv_mac(&bad_mac); + assert!(bad_res.is_err()); +} + +#[test] +fn test_long_inputs() { + let mut s = StrobeState::new(b"bigtest", SecurityParameter::B256); + const BIG_N: usize = 9823; + const SMALL_N: usize = 65; + let big_data = [0x34u8; BIG_N]; + let small_data = [0x35u8; SMALL_N]; + + s.meta_ad(&big_data[..]); + s.ad(&big_data[..]); + s.meta_key(&big_data[..]); + s.key(&big_data[..]); + s.meta_send_clr(&big_data[..]); + s.send_clr(&big_data[..]); + s.meta_recv_clr(&big_data[..]); + s.recv_clr(&big_data[..]); + + s.meta_send_enc(big_data.to_vec().as_mut_slice()); + s.send_enc(big_data.to_vec().as_mut_slice()); + s.meta_recv_enc(big_data.to_vec().as_mut_slice()); + s.recv_enc(big_data.to_vec().as_mut_slice()); + let _ = s.meta_recv_mac(&small_data); + let _ = s.recv_mac(&small_data); + + let mut big_buf = [0u8; BIG_N]; + let mut small_buf = [0u8; SMALL_N]; + + s.meta_ratchet(BIG_N); + s.ratchet(BIG_N); + s.meta_prf(&mut big_buf); + s.prf(&mut big_buf); + s.meta_send_mac(&mut small_buf); + s.send_mac(&mut small_buf); + + let expected_st = [ + 0x96, 0x2f, 0xc8, 0x05, 0x48, 0xcd, 0xa3, 0xa8, 0xdf, 0xe7, 0x64, 0xd9, 0xe9, 0x81, 0xe0, + 0x66, 0xdf, 0x2e, 0xd9, 0xc1, 0x74, 0x14, 0xac, 0xf0, 0x96, 0xe2, 0x96, 0xb2, 0x1f, 0x59, + 0x79, 0x58, 0x82, 0xd5, 0x72, 0x22, 0x92, 0xc2, 0xd9, 0x92, 0x6b, 0xa2, 0x3d, 0x0b, 0x2d, + 0xae, 0xf1, 0xff, 0x36, 0xbe, 0xfd, 0x19, 0xdf, 0xd8, 0x47, 0xcb, 0xf4, 0x35, 0x4c, 0x7f, + 0x28, 0x58, 0x8d, 0x6f, 0x4d, 0x07, 0xc7, 0xbd, 0xbd, 0x6b, 0xe8, 0x45, 0xea, 0x56, 0x1e, + 0xe1, 0x8c, 0x21, 0x20, 0x8c, 0x50, 0x1e, 0x0d, 0x75, 0x80, 0xd4, 0x00, 0xcd, 0x48, 0xe7, + 0xf6, 0x23, 0x7b, 0x11, 0x85, 0x10, 0x53, 0x87, 0x28, 0x36, 0x14, 0x11, 0x28, 0x37, 0x1b, + 0x0a, 0xfd, 0x9f, 0x21, 0x72, 0xff, 0x27, 0x4a, 0xc2, 0x7b, 0xfd, 0x86, 0x6d, 0xff, 0x4e, + 0x07, 0x04, 0x2d, 0xd5, 0x3a, 0xbe, 0xeb, 0x43, 0x39, 0xbf, 0x20, 0xf0, 0x28, 0x31, 0x7b, + 0xc2, 0x3f, 0x1b, 0x9b, 0x6d, 0x94, 0x84, 0x92, 0x13, 0x01, 0xf0, 0x04, 0xc7, 0xf3, 0xaa, + 0x68, 0xc5, 0x4a, 0x9b, 0x26, 0x98, 0x37, 0x81, 0x99, 0xcf, 0xe7, 0x2c, 0xa2, 0xd9, 0x35, + 0x0d, 0xac, 0x01, 0xe3, 0x09, 0xa9, 0x3a, 0x8c, 0x1c, 0xeb, 0x9d, 0x4d, 0xb4, 0xc7, 0x0a, + 0x72, 0xf7, 0x14, 0x1a, 0xc5, 0x90, 0xdf, 0x47, 0xab, 0xa3, 0x83, 0xbd, 0x6c, 0xfe, 0xdf, + 0x9b, 0xee, 0x8d, 0xcb, 0xe0, + ]; + + assert_eq!(&s.state.0, &expected_st); +} + +#[test] +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", SecurityParameter::B256); + + s.ad(b"mynonce"); + + let mut buf = b"hello there".to_vec(); + s.recv_enc(buf.as_mut_slice()); + + let mut mac = [0u8; 16]; + s.send_mac(&mut mac[..]); + + s.ratchet(13); + + s.state.0.to_vec() + }; + // Now do the same thing but stream the inputs + let streamed_st: std::vec::Vec = { + let mut s = StrobeState::new(b"streamingtest", SecurityParameter::B256); + + s.ad(b"my"); + s.ad(b"nonce"); + + let mut buf = b"hello".to_vec(); + s.recv_enc(buf.as_mut_slice()); + + let mut buf = b" there".to_vec(); + s.recv_enc(buf.as_mut_slice()); + + let mut mac = [0u8; 16]; + s.send_mac(&mut mac[..10]); + s.send_mac(&mut mac[10..]); + + s.ratchet(10); + s.ratchet(3); + + s.state.0.to_vec() + }; + + assert_eq!(one_shot_st, streamed_st); +} diff --git a/src/keccakf.rs b/src/keccakf.rs new file mode 100644 --- /dev/null +++ b/src/keccakf.rs @@ -0,0 +1,130 @@ +const SIZE: usize = core::mem::size_of::(); + +/// keccak block size in 64-bit words. This is the N parameter in the STROBE spec +pub(crate) const KECCAK_BLOCK_SIZE: usize = 25; + +/// keccak buffer size in bytes. N * 8 (byte size of u64) +pub(crate) const KECCAK_BUFFER_SIZE: usize = SIZE * KECCAK_BLOCK_SIZE; + +/// This is a wrapper around 200-byte buffer that's always 8-byte aligned to make pointers to it +/// safely convertible to a pointer to `[u64; 25]` (since u64 words must be 8-byte aligned) +#[derive(Clone)] +#[repr(align(8))] +pub(crate) struct KeccakF1600(pub(crate) [u8; KECCAK_BUFFER_SIZE]); + +impl KeccakF1600 { + fn copy_into(&self, dst: &mut [u64; KECCAK_BLOCK_SIZE]) { + assert_eq!(self.0.len(), dst.len() * SIZE); + + self.0 + .chunks_exact(SIZE) + .flat_map(TryInto::<[u8; SIZE]>::try_into) + .map(u64::from_le_bytes) + .zip(dst.iter_mut()) + .for_each(|(src, dst)| *dst = src); + } + + fn copy_from(&mut self, src: &[u64; KECCAK_BLOCK_SIZE]) { + assert_eq!(self.0.len(), src.len() * SIZE); + + src.iter() + .zip(self.0.chunks_exact_mut(SIZE)) + .for_each(|(src, dst)| dst.copy_from_slice(&src.to_le_bytes())); + } + + /// Performs the keccakf\[1600\] permutation on an aligned byte buffer + pub(crate) fn permute_f1600(&mut self) { + let mut keccak_block = [0u64; KECCAK_BLOCK_SIZE]; + + // Make a little-endian copy, do the operation, then copy the bytes back. + // This all optimises away so the block is written to directly, confirmed with godbolt. + self.copy_into(&mut keccak_block); + keccak::Keccak::new().with_f1600(|f| f(&mut keccak_block)); + self.copy_from(&keccak_block); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn zeroed_state_keccakf_matches_expected_outputs() { + let mut state = KeccakF1600([0u8; KECCAK_BUFFER_SIZE]); + + // Do one permutation on the KeccakF state + state.permute_f1600(); + + let mut words = [0u64; KECCAK_BLOCK_SIZE]; + + // Byte state should match expected word state + state.copy_into(&mut words); + + assert_eq!( + &words, + &[ + 0xF1258F7940E1DDE7, + 0x84D5CCF933C0478A, + 0xD598261EA65AA9EE, + 0xBD1547306F80494D, + 0x8B284E056253D057, + 0xFF97A42D7F8E6FD4, + 0x90FEE5A0A44647C4, + 0x8C5BDA0CD6192E76, + 0xAD30A6F71B19059C, + 0x30935AB7D08FFC64, + 0xEB5AA93F2317D635, + 0xA9A6E6260D712103, + 0x81A57C16DBCF555F, + 0x43B831CD0347C826, + 0x01F22F1A11A5569F, + 0x05E5635A21D9AE61, + 0x64BEFEF28CC970F2, + 0x613670957BC46611, + 0xB87C5A554FD00ECB, + 0x8C3EE88A1CCF32C8, + 0x940C7922AE3A2614, + 0x1841F924A2C509E4, + 0x16F53526E70465C2, + 0x75F644E97F30A13B, + 0xEAF1FF7B5CECA249, + ] + ); + + // Do another permutation + state.permute_f1600(); + + state.copy_into(&mut words); + + assert_eq!( + &words, + &[ + 0x2D5C954DF96ECB3C, + 0x6A332CD07057B56D, + 0x093D8D1270D76B6C, + 0x8A20D9B25569D094, + 0x4F9C4F99E5E7F156, + 0xF957B9A2DA65FB38, + 0x85773DAE1275AF0D, + 0xFAF4F247C3D810F7, + 0x1F1B9EE6F79A8759, + 0xE4FECC0FEE98B425, + 0x68CE61B6B9CE68A1, + 0xDEEA66C4BA8F974F, + 0x33C43D836EAFB1F5, + 0xE00654042719DBD9, + 0x7CF8A9F009831265, + 0xFD5449A6BF174743, + 0x97DDAD33D8994B40, + 0x48EAD5FC5D0BE774, + 0xE3B8C8EE55B7B03C, + 0x91A0226E649E42E9, + 0x900E3129E7BADD7B, + 0x202A9EC5FAA3CCE8, + 0x5B3402464E1C3DB6, + 0x609F4E62A44C1059, + 0x20D06CD26A8FBF5C, + ] + ) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,21 @@ +#![no_std] +#![forbid(unsafe_code)] + +#[cfg(test)] +mod basic_kats; +#[cfg(test)] +mod herd_of_kats; +mod keccakf; +pub mod strobe; + +/// Version of Strobe that this crate implements. +pub static STROBE_VERSION: &str = "1.0.2"; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct GarbledError; + +impl core::fmt::Display for GarbledError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str("Protocol Failure") + } +} diff --git a/src/strobe.rs b/src/strobe.rs new file mode 100644 --- /dev/null +++ b/src/strobe.rs @@ -0,0 +1,473 @@ +use enumflags2::{BitFlag, BitFlags}; +use subtle::ConstantTimeEq; + +use crate::{ + GarbledError, STROBE_VERSION, + keccakf::{KECCAK_BUFFER_SIZE, KeccakF1600}, +}; + +#[enumflags2::bitflags] +#[repr(u8)] +#[derive(Copy, Clone, Debug, PartialEq)] +enum OpFlags { + /// Is data being moved inbound + Inbound = 0b000001, // 1<<0 + /// Is data being sent to the application + App = 0b000010, // 1<<1 + /// Does this operation use cipher output + Cipher = 0b000100, // 1<<2 + /// Is data being sent for transport + Transport = 0b001000, // 1<<3 + /// Use exclusively for metadata operations + Meta = 0b010000, // 1<<4 + /// Reserved and currently unimplemented. Using this will cause a panic. + KeyTree = 0b100000, // 1<<5 +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Role { + Sender, + Receiver, +} + +#[derive(Debug, Clone, Copy)] +#[repr(usize)] +pub enum SecurityParameter { + B128 = 128, + B256 = 256, +} + +#[derive(Clone)] +pub struct StrobeState { + /// Internal Keccak state + pub(crate) state: KeccakF1600, + /// Security parameter (128 or 256 bits) + sec: SecurityParameter, + /// This is the `R` parameter in the Strobe spec + rate: usize, + /// Index into `state` + position: usize, + /// Index into `state` + start: usize, + /// Represents whether we're a sender or a receiver or uninitialized + role: Option, + /// The last operation performed. This is to verify that the `more` flag is only used across + /// identical operations. + prev_flags: BitFlags, +} + +macro_rules! define_mut_operations { + { $(#[$doc:meta] + pub fn $name:ident($flags:expr); + )+ } => { + $( + #[$doc] + pub fn $name(&mut self, data: &mut [u8]) { + let flags = $flags; + self.operate(flags, data, self.prev_flags == flags); + } + )* + }; +} + +macro_rules! define_non_mut_operations { + { $(#[$doc:meta] + pub fn $name:ident($flags:expr); + )+ } => { + $( + #[$doc] + pub fn $name(&mut self, data: &[u8]) { + let flags = $flags; + self.operate_no_mutate(flags, data, self.prev_flags == flags); + } + )* + }; +} + +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 { + SecurityParameter::B128 => f.write_str("128")?, + SecurityParameter::B256 => f.write_str("256")?, + } + f.write_str("/1600-v")?; + f.write_str(STROBE_VERSION) + } +} + +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("version", &STROBE_VERSION) + .finish_non_exhaustive() + } +} + +impl StrobeState { + /// Makes a new `StrobeTransport` object with a given protocol byte string and security parameter. + pub fn new(protocol: &[u8], sec: SecurityParameter) -> Self { + let rate = KECCAK_BUFFER_SIZE - (sec as usize) / 4 - 2; + assert!((1..254).contains(&rate)); + + // Initialize state: st = F([0x01, R+2, 0x01, 0x00, 0x01, 0x60] + b"STROBEvX.Y.Z") + let mut state_buffer = [0u8; KECCAK_BUFFER_SIZE]; + state_buffer[0..6].copy_from_slice(&[0x01, (rate as u8) + 2, 0x01, 0x00, 0x01, 0x60]); + state_buffer[6..13].copy_from_slice(b"STROBEv"); + state_buffer[13..18].copy_from_slice(STROBE_VERSION.as_bytes()); + + let mut state = KeccakF1600(state_buffer); + + state.permute_f1600(); + + let mut strobe = Self { + state, + sec, + rate, + position: 0, + start: 0, + role: None, + prev_flags: OpFlags::empty(), + }; + + // Mix the protocol into the state + strobe.meta_ad(protocol); + + // Reset operation so meta_ad doesn't stream after initialisation + strobe.reset_ops(); + + strobe + } + + /// Reset the internal operation state. This is to make sure the next call doesn't + /// stream and to always begin as a new operation. This doesn't modify or change the + /// internal Strobe state, only the tracked operation state. + /// + /// 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. + pub fn reset_ops(&mut self) { + // This prevents streaming so to always make the prev_flags == flags + // comparison always fail + self.prev_flags = OpFlags::empty(); + } + + // Runs the permutation function on the internal state + fn permutation_f(&mut self) { + self.state.0[self.position] ^= self.start as u8; + self.state.0[self.position + 1] ^= 0x04; + self.state.0[self.rate + 1] ^= 0x80; + + self.state.permute_f1600(); + + self.position = 0; + self.start = 0; + } + + fn increment_position(&mut self) { + self.position += 1; + + if self.position == self.rate { + self.permutation_f(); + } + } + + /// XORs the given data into the state. This is a special case of the `duplex` code in the + /// STROBE paper. + fn absorb(&mut self, data: &[u8]) { + data.iter().for_each(|&b| { + self.state.0[self.position] ^= b; + + self.increment_position(); + }); + } + + /// XORs the given data into the state, then sets the data equal the state. This is a special + /// case of the `duplex` code in the STROBE paper. + fn absorb_and_set(&mut self, data: &mut [u8]) { + data.iter_mut().for_each(|b| { + let state_byte = &mut self.state.0[self.position]; + *state_byte ^= *b; + *b = *state_byte; + + self.increment_position(); + }); + } + + /// Copies the internal state into the given buffer. This is a special case of `absorb_and_set` + /// where `data` is all zeros. + fn copy_state(&mut self, data: &mut [u8]) { + data.iter_mut().for_each(|b| { + *b = self.state.0[self.position]; + + self.increment_position(); + }); + } + + /// Overwrites the state with the given data while XORing the given data with the old state. + /// This is a special case of the `duplex` code in the STROBE paper. + fn exchange(&mut self, data: &mut [u8]) { + data.iter_mut().for_each(|b| { + let state_byte = &mut self.state.0[self.position]; + *b ^= *state_byte; + *state_byte ^= *b; + + self.increment_position(); + }); + } + + /// Overwrites the state with the given data. This is a special case of `Strobe::exchange`, + /// where we do not want to mutate the input data. + fn overwrite(&mut self, data: &[u8]) { + data.iter().for_each(|&b| { + self.state.0[self.position] = b; + + self.increment_position(); + }); + } + + /// Copies the state into the given buffer and sets the state to 0. This is a special case of + /// `Strobe::exchange`, where `data` is assumed to be the all-zeros string. This is precisely + /// the case when the current operation is PRF. + fn squeeze(&mut self, data: &mut [u8]) { + data.iter_mut().for_each(|b| { + let state_byte = &mut self.state.0[self.position]; + *b = *state_byte; + *state_byte = 0; + + self.increment_position(); + }); + } + + /// Overwrites the state with a specified number of zeros. This is a special case of + /// `Strobe::exchange`. More specifically, it's a special case of `Strobe::overwrite` and + /// `Strobe::squeeze`. It's like `squeeze` in that we assume we've been given all zeros as + /// input, and like `overwrite` in that we do not mutate (or take) any input. + fn zero_state(&mut self, mut bytes_to_zero: usize) { + // Do the zero-writing in chunks + while bytes_to_zero > 0 { + let slice_len = core::cmp::min(self.rate - self.position, bytes_to_zero); + self.state.0[self.position..(self.position + slice_len)].fill(0); + + self.position += slice_len; + bytes_to_zero -= slice_len; + + if self.position == self.rate { + self.permutation_f(); + } + } + } + + /// Mixes the current state index and flags into the state, accounting for whether we are + /// sending or receiving + fn begin_op(&mut self, mut flags: BitFlags) { + if flags.contains(OpFlags::Transport) { + let op_role = if flags.contains(OpFlags::Inbound) { + Role::Receiver + } else { + Role::Sender + }; + + // If uninitialized, take on the direction of the first directional operation we get + if self.role.is_none() { + self.role = Some(op_role); + } + + // So that the sender and receiver agree, toggle the I flag as necessary + // This is equivalent to flags ^= is_receiver + flags.set(OpFlags::Inbound, self.role.unwrap() != op_role); + } + + let old_start = self.start; + self.start = self.position + 1; + + // Mix in the position and flags + self.absorb(&[old_start as u8, flags.bits()]); + + let force_permutation = flags.contains(OpFlags::Cipher) || flags.contains(OpFlags::KeyTree); + if force_permutation && self.position != 0 { + self.permutation_f(); + } + } + + /// 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, flags: BitFlags, 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 { + self.begin_op(flags); + } + + // Meta-ness is only relevant for `begin_op`. Remove it to simplify the below logic. + let flags = flags & !OpFlags::Meta; + + // TODO?: Assert that input is empty under some flag conditions + if flags.contains(OpFlags::Cipher | OpFlags::Transport) && !flags.contains(OpFlags::Inbound) + { + // This is equivalent to the `duplex` operation in the Python implementation, with + // `cafter = True` + if flags == OpFlags::Cipher | OpFlags::Transport { + // This is `send_mac`. Pretend the input is all zeros + self.copy_state(data); + } else { + self.absorb_and_set(data); + } + } else if flags == OpFlags::Inbound | OpFlags::App | OpFlags::Cipher { + // Special case of case below. This is PRF. Use `squeeze` instead of `exchange`. + self.squeeze(data); + } else if flags.contains(OpFlags::Cipher) { + // This is equivalent to the `duplex` operation in the Python implementation, with + // `cbefore = True` + self.exchange(data); + } else { + // This should normally call `absorb`, but `absorb` does not mutate, so the implementor + // should have used operate_no_mutate instead + unreachable!("operate should not be called for operations that do not require mutation") + } + } + + /// 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, flags: BitFlags, 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 { + self.begin_op(flags); + } + + // There are no non-mutating variants of things with flags & (C | T | I) == C | T + if flags.contains(OpFlags::Cipher | OpFlags::Transport) && !flags.contains(OpFlags::Inbound) + { + unreachable!("operate_no_mutate called on something that requires mutation") + } else if flags.contains(OpFlags::Cipher) { + // This is equivalent to a non-mutating form of the `duplex` operation in the Python + // implementation, with `cbefore = True` + self.overwrite(data); + } else { + // This is equivalent to the `duplex` operation in the Python implementation, with + // `cbefore = cafter = False` + self.absorb(data); + } + } + + fn recv_mac_inner( + &mut self, + mac_copy: &mut [u8], + flags: BitFlags, + ) -> Result<(), GarbledError> { + // recv_mac can never be streamed + self.operate(flags, mac_copy, false); + + // Constant-time MAC check. This accumulates the truth values of byte == 0 + let all_zero: bool = mac_copy + .iter() + .fold(subtle::Choice::from(1u8), |all_zero, b| { + all_zero & 0u8.ct_eq(b) + }) + .into(); + + if all_zero { Ok(()) } else { Err(GarbledError) } + } + + pub fn recv_mac(&mut self, mac: &[u8; N]) -> Result<(), GarbledError> { + let mut mac_copy = *mac; + + self.recv_mac_inner( + &mut mac_copy, + OpFlags::Inbound | OpFlags::Cipher | OpFlags::Transport, + ) + } + + pub fn meta_recv_mac(&mut self, mac: &[u8; N]) -> Result<(), GarbledError> { + let mut mac_copy = *mac; + + self.recv_mac_inner( + &mut mac_copy, + OpFlags::Inbound | OpFlags::Cipher | OpFlags::Transport | OpFlags::Meta, + ) + } + + fn ratchet_inner(&mut self, num_bytes_to_zero: usize, more: bool, flags: BitFlags) { + // 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 { + self.begin_op(flags); + } + + self.zero_state(num_bytes_to_zero); + } + + pub fn ratchet(&mut self, num_bytes_to_zero: usize) { + let flags = OpFlags::Cipher.into(); + self.ratchet_inner(num_bytes_to_zero, self.prev_flags == flags, flags); + } + + pub fn meta_ratchet(&mut self, num_bytes_to_zero: usize) { + let flags = OpFlags::Cipher | OpFlags::Meta; + self.ratchet_inner(num_bytes_to_zero, self.prev_flags == flags, flags); + } + + define_mut_operations! { + /// SEND ENC + pub fn send_enc(OpFlags::App | OpFlags::Cipher | OpFlags::Transport); + /// META SEND ENC + pub fn meta_send_enc(OpFlags::App | OpFlags::Cipher | OpFlags::Transport | OpFlags::Meta); + /// RECV ENV + pub fn recv_enc(OpFlags::Inbound | OpFlags::App | OpFlags::Cipher | OpFlags::Transport); + /// META RECV ENC + pub fn meta_recv_enc(OpFlags::Inbound | OpFlags::App | OpFlags::Cipher | OpFlags::Transport | OpFlags::Meta); + /// SEND MAC + pub fn send_mac(OpFlags::Cipher | OpFlags::Transport); + /// META SEND MAC + pub fn meta_send_mac(OpFlags::Cipher | OpFlags::Transport | OpFlags::Meta); + /// PRF + pub fn prf(OpFlags::Inbound | OpFlags::App | OpFlags::Cipher); + /// META PRF + pub fn meta_prf(OpFlags::Inbound | OpFlags::App | OpFlags::Cipher | OpFlags::Meta); + } + + define_non_mut_operations! { + /// AD + pub fn ad(OpFlags::App.into()); + /// META AD + pub fn meta_ad(OpFlags::App | OpFlags::Meta); + /// KEY + pub fn key(OpFlags::App | OpFlags::Cipher); + /// META KEY + pub fn meta_key(OpFlags::App | OpFlags::Cipher | OpFlags::Meta); + /// SEND CLR + pub fn send_clr(OpFlags::App | OpFlags::Transport); + /// META SEND CLR + pub fn meta_send_clr(OpFlags::App | OpFlags::Transport | OpFlags::Meta); + /// RECV CLR + pub fn recv_clr(OpFlags::Inbound | OpFlags::App | OpFlags::Transport); + /// META RECV CLR + pub fn meta_recv_clr(OpFlags::Inbound | OpFlags::App | OpFlags::Transport | OpFlags::Meta); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + extern crate std; + + #[test] + fn version_formatting() { + let s = StrobeState::new(b"", SecurityParameter::B128); + + 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\", .. }"); + } +} diff --git a/src/herd_of_kats/mod.rs b/src/herd_of_kats/mod.rs new file mode 100644 --- /dev/null +++ b/src/herd_of_kats/mod.rs @@ -0,0 +1,4 @@ +//! ## Herding KATs +//! +//! This module contains a bigger test suite to evaluate the implementation of `StrobeState` +//! fully. \ No newline at end of file -- tangled.sh