diff --git a/wharrgarbl-neko/SPEC.md b/wharrgarbl-neko/SPEC.md index 33acef8..dbbc11c 100644 --- a/wharrgarbl-neko/SPEC.md +++ b/wharrgarbl-neko/SPEC.md @@ -1,8 +1,8 @@ -# NEKO Specification v0.2.1 +# NEKO Specification v0.3.0 NEKO is inspired by STROBE in that it uses Keccak in a duplex construction to perform encryption and message authentication. It has stripped down the amount of operation flags it uses and simplifies the internals, ridding the need to have "streaming" by instead opting for having different operating modes. -In lieu of streaming, NEKO uses op-stacking to reduce the amount of permutations needed. There are two kinds of operations, NON-PERMUTING and PERMUTING. A permutation is when the internals invoke the Keccak permutation function on the NEKO state, resulting in a new, pseudorandomised state. A permutation resets the buffer, and thus allows more data to be ingested into the cipher. +In lieu of streaming, NEKO uses op-chaining to reduce the amount of permutations needed. There are two kinds of operations, NON-PERMUTING and PERMUTING. A permutation is when the internals invoke the Keccak permutation function on the NEKO state, resulting in a new, pseudorandomised state. A permutation resets the buffer, and thus allows more data to be ingested into the cipher. The pattern is a max of four chained ops, with the first being a permuting one or an INIT/CONT op. Any sequence of more than 3 non-permuting ops that don't incur a permutation during data ingestion, and NEKO will throw a panic. The available operations are as follows: @@ -32,11 +32,11 @@ NEKO internally uses the Keccakp1600 state buffer directly, with its layout of ` NEKO has three security levels, 128-bit, 192-bit and 256-bit security. Each level determines the *rate*, which is how many blocks are available for input, so 128 gives 21 blocks for input with 4 blocks for entropy/*capacity*, while 256 gives 17 blocks and 8 blocks for entropy/*capacity*. The input blocks are separated for data input with the last block reserved for encoding ops. 256 mode will require permuting more often than 128 mode, as it won't have as much input capacity before its input buffer is exhausted. -Ops are tracked with a stack, with a max of 4 ops being "stacked" before a permutation must occur. When a permutation starts, the ops encoding block is selected (position block + 1) and then it is XOR'd with the list of op bits on the \[0..4\] part of the block, going from first op to last, with the \[4..8\] part of the block XOR'd with the ops count, the block position on the first bytes, with then `0x80` & `0` on the last bytes. This creates the start of the padding structure. The padding terminator is then XOR'd on the last byte of the reserved block with a `0x80` value that is rotated by the position counter. If the input fills the buffer completely, the position+1 and reserved block are the same, so the combined XOR'd blocks will form a single block construction. The permutation is then executed with the p1600 function with 12 rounds (KangarooTwelve), after which the block & ops counters are reset along with the ops stack. +Ops are tracked with a counter, with a max of 4 ops being counted before a permutation must occur. Every time an operation occurs, it is XOR'd onto the state at the terminating block (state[BLOCK_RATE]), slot in between \[0..4\] available slots on the last four bytes of the block (\[4..8\]). When a permutation starts, the terminating block is selected and then it is XOR'd with the padding terminator part of the block (\[0..4\]). Zero on the first byte, `0x40` on the second, the ops count on the third, and the block position on the last byte. This creates the termination of the padding structure. The padding start then XOR's `0x80` in little-endian on the position byte on the state. If the input fills the buffer completely, the position byte and reserved block are the same (with the padding start XOR'ing onto the zero first byte of the terminator), so the combined bytes will form a single block construction. The permutation is then executed with the p1600 function with 12 rounds (KangarooTwelve), after which the position & ops counters are reset. -After the permutation concludes, the op which triggered the permutation is then encoded into the stack. There are two ways a permutation is invoked: via a user OP, or from a *continuation* for ingesting more data. In the case of a user op, the counter is set to zero, and the stack fully zeroed so the op can encode its flags onto the first slot when it begins. In the case of a *continuation*, the counter is set to `1`, and the first slot is encoded with the `CONT` flag bits with the rest of the slots zeroed. This ensures that in **every** case, there will always be 3 remaining free slots to form a 4 op chain. +After the permutation concludes, the op which triggered the permutation is then encoded into the first op slot. There are two ways a permutation is invoked: via a user OP, or from a *continuation* for ingesting more data. In the case of a user op, the counter is set to zero, so the op can encode its flags onto the first slot when it begins. In the case of a *continuation*, the counter is set to `1`, and the first slot is encoded with the `CONT` flag bits. This ensures that in **every** case, there will always be 3 remaining free slots to form a 4 op chain. -As such, how an operation works is as follows: User invokes op -> (Permute if required) -> Encode Op to free slot on stack -> Ingest Data -> (Permute if continuation) -> Finish. +As such, how an operation works is as follows: User invokes op -> (Permute if required) -> Encode Op to free slot on state -> Ingest Data -> (Permute if continuation) -> Finish. Internally, each operation *operates* on the data with one of 7 different actions: @@ -86,11 +86,11 @@ ALL valid user ops are defined with a combination of two or three flags. No user ## Initialisation -Creating a new Neko State instance is qualified as an INIT operation. This means that it must be encoded as an operation on the op stack. As such the initial op stack is [INIT, 0, 0, 0] and the op counter must be set to one. The buffer position counter is then set to 0 and the Keccak F1600 state buffer is initialised and zeroed: `[0u64; 25]`; +Creating a new Neko State instance is qualified as an INIT operation. This means that it must be encoded as an operation. As such the initial op is INIT and the op counter must be set to one. The buffer position counter is then set to 0 and the Keccak F1600 state buffer is initialised and zeroed: `[0u64; 25]`. The INIT op is then encoded into the first op slot on the state. Then, we must encode the preamble & NEKO version onto the state buffer. The preamble is defined as `[0x01, RATE, 0x07, 0x60]`. The `RATE` is calculated as `(200 - SecLevel / 4) / 8 - 1` and encoded as a `u8` value. So for `Neko128`, the `RATE` should resolve to `20`, and `Neko256` should resolve to `16`. These represent the max buffer position that input data can be absorbed into before needing to permute. -The NEKO version string is then concatenated to the preamble. For version v0.2.1 of this specification, the string is `NEKOv0.2.1`, and it should be concatenated as a byte string. Then the combined preamble+version bytes should be written to the buffer in an `OVERWRITE` action. +The NEKO version string is then concatenated to the preamble. For version v0.3.0 of this specification, the string is `NEKOv0.3.0`, and it should be concatenated as a byte string. Then the combined preamble+version bytes should be written to the buffer in an `OVERWRITE` action. Additionally, a protocol byte string can be written to the state, following after the preamble+version with its own `OVERWRITE` action. @@ -111,9 +111,9 @@ With an initialised Neko state, the following operations are available to the us - `create_mac`: Applies `MAC` opflag. This doesn't take any input, but creates a tag/MAC via `COPY STATE` action, creating a `Array` that represents a MAC of the processed state. This is a permuting operation, as it requires to process all previous operations into the internal state. - `verify_mac`: Applies `MAC` opflag. This takes a tag reference `&Array` and verifies it by `EXCHANGE` with the internal state. If the tag/MAC resolves to all zeroes, then it is a valid tag, and the actions returns a success result. If not, it returns a failure result. This is a permuting operation, as it requires to process all previous operations into the internal state. -All user operations follow a chain of 4 ops. Non-permuting ops can *stack*, so they add to the stack if their inputs don't cause the state to permute. Permuting ops always *reset* the stack, as they permute the state. When the stack resets due to an op, the stack is cleared and the op that triggered the reset is encoded into the first slot. `INIT` is always the very first op, so `KEY` + `NONCE` + `AD` can be added as ops without triggering a permutation. If another non-permuting op were to be chained at this point (like `CLR`), this would cause a panic. To resolve this, call a permuting op like `ENC` or `RATCHET`, and the stack is reset to be just the permuting op on the stack, with three more free slots. +All user operations follow a chain of 4 ops. Non-permuting ops can *chain*, so they add to the available slots if their inputs don't cause the state to permute. Permuting ops always *reset* the chain, as they permute the state. When the chain resets due to an op, the op that triggered the reset is encoded into the first slot. `INIT` is always the very first op, so `KEY` + `NONCE` + `AD` can be added as ops without triggering a permutation. If another non-permuting op were to be chained at this point (like `CLR`), this would cause a panic. To resolve this, call a permuting op like `ENC` or `RATCHET`, and the chain is reset to be just the permuting op on the first slot, with three more free slots. -A panic is a must, because any occasion that we are going over 4 chained ops is a misuse of the protocol and thus MUST fail quickly. Under normal usage, no sequence of ops should cause a chain of more than 4 to occur, and with large enough payloads, the state would be getting permuted enough to ensure this is not required. +A panic is a must, because any occasion that we are going over 4 chained ops is a misuse of the cipher and thus MUST fail quickly. Under normal usage, no sequence of ops should cause a chain of more than 4 to occur, and with large enough payloads, the state would be getting permuted enough to ensure this is not required. ## Changes @@ -124,3 +124,7 @@ Initial publication and base specification. ### v0.2.1 Changed from f1600 to p1600 with 12 rounds for Keccak permutation function, and introduced Neko192 (192-bit) strength. + +### v0.3.0 + +Changed how op chaining is written to state along with the multi-rate padding, and as a consequence, the construction of the initial state. diff --git a/wharrgarbl-neko/src/kats.rs b/wharrgarbl-neko/src/kats.rs index a0bb08a..3fa3680 100644 --- a/wharrgarbl-neko/src/kats.rs +++ b/wharrgarbl-neko/src/kats.rs @@ -1,7 +1,7 @@ use hybrid_array::Array; use zerocopy::IntoBytes; -use crate::{Neko128, Neko192, Neko256, NekoState}; +use crate::{Neko128, Neko192, Neko256, NekoSec, NekoState}; extern crate alloc; @@ -15,10 +15,11 @@ fn neko_128_init_state() { assert_eq!(&first, b"\x01\x14\x07\x60NEKO"); // Values that don't fill the block entirely leave padding - assert_eq!(&second, b"v0.2.1\0\0"); + assert_eq!(&second, b"v0.3.0\0\0"); assert_eq!(&third[..4], b"test"); - // The rest of the state is zeroed - assert_eq!(&neko.state[3..], &[0; 22]); + // The rest of the state is zeroed, except for the INIT bits in the op stack + #[rustfmt::skip] + assert_eq!(&neko.state[3..], &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42949672960, 0, 0, 0, 0]); } #[test] @@ -31,10 +32,11 @@ fn neko_192_init_state() { assert_eq!(&first, b"\x01\x12\x07\x60NEKO"); // Values that don't fill the block entirely leave padding - assert_eq!(&second, b"v0.2.1\0\0"); + assert_eq!(&second, b"v0.3.0\0\0"); assert_eq!(&third[..4], b"test"); - // The rest of the state is zeroed - assert_eq!(&neko.state[3..], &[0; 22]); + // The rest of the state is zeroed, except for the INIT bits in the op stack + #[rustfmt::skip] + assert_eq!(&neko.state[3..], &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42949672960, 0, 0, 0, 0, 0, 0]); } #[test] @@ -47,10 +49,11 @@ fn neko_256_init_state() { assert_eq!(&first, b"\x01\x10\x07\x60NEKO"); // Values that don't fill the block entirely leave padding - assert_eq!(&second, b"v0.2.1\0\0"); + assert_eq!(&second, b"v0.3.0\0\0"); assert_eq!(&third[..4], b"test"); - // The rest of the state is zeroed - assert_eq!(&neko.state[3..], &[0; 22]); + // The rest of the state is zeroed, except for the INIT bits in the op stack + #[rustfmt::skip] + assert_eq!(&neko.state[3..], &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42949672960, 0, 0, 0, 0, 0, 0, 0, 0]); } #[test] @@ -116,13 +119,12 @@ fn non_cipher_flag_ops_dont_permute_by_default() { assert_eq!(state, b"addz\0\0\0\0"); - let ops = &neko.ops_stack; + let ops = &neko.state[Neko128::BLOCK_RATE].as_bytes()[4..]; // OPs are XOR'd onto the state, but when the state is zeroed, it // is the same as overwriting, so the ops are visible. assert_eq!(ops, &[0x0A, 0x0C, 0x09, 0x0B]); - // The rest of the state is zeroed - assert_eq!(&neko.state[13..22], &[0; 9]); - assert_eq!(&neko.state[23..], &[0; 2]); + // The rest of the writable state is zeroed + assert_eq!(&neko.state[13..Neko128::BLOCK_RATE], &[0; 7]); // We must permute at this point neko.ratchet(); @@ -130,42 +132,52 @@ fn non_cipher_flag_ops_dont_permute_by_default() { let expected_state = [ 0x0000000000000000, 0x0000000000000000, - 0xd3c9627ab3f60112, - 0xf9bc5a3ce86c0308, - 0x1b649c672f8291e7, - 0x348b76389636d1c5, - 0x7e7b242854a89160, - 0xc588e1bab9fff3f3, - 0x6869595e613dce2f, - 0x21aa5dd9c8ace1ed, - 0x976ac27c1542f2e3, - 0x271c24a505720c01, - 0x1cf4dbfd172716ec, - 0x5e338536048f69cd, - 0x198c6a958d8215a6, - 0x7e181ad612b4ec51, - 0xd3c7adc0e13f29fd, - 0x92adbe76b85f49a9, - 0xc05147b39c222c68, - 0xea1f4797623a1431, - 0x4d837d8b8ef79878, - 0x59b03ae6c5f3c16f, - 0x2b5da88884b6b1d9, - 0x04ecf618b84acb0a, - 0x58ae3cfc3b9574c2, + 0xb3d52448eb0c9c2e, + 0xb9fdc21805f4d04e, + 0xa7c4f6c91093a156, + 0x300f35d691766af5, + 0x0668c7ef424d48a7, + 0x6a2d0de28aa75910, + 0x04b046e1175c5430, + 0x1416b95b5250b389, + 0x7d74a300430548c5, + 0xfcf7c8c6e5aa7ed1, + 0x2babcc1a98de2203, + 0x8d88224038a0cb58, + 0xf200884a1dc66684, + 0xf86a4fff63646108, + 0xa75e517842f6bb66, + 0xdeed1ad07e1fb7ff, + 0x4ed28c308ff1cb8c, + 0x8e5d83df3485ac23, + 0x808fffd10282abbf, + 0xd6a3c316bf11a6e1, + 0xfee83b387d9569c7, + 0x2bf4c9508e809e51, + 0xd8da096a99edbd6e, ]; assert_eq!(&neko.state, &expected_state); let orig_state = neko.state[2]; + let orig_perm = neko.state[Neko128::BLOCK_RATE]; // Cleartext will not permute the state unless the text is larger // than the available permuted buffer. neko.cleartext(cleartext); - assert_eq!(&neko.ops_stack, &[0x0D, 0x03, 0, 0]); + let perm = neko.state[Neko128::BLOCK_RATE] ^ orig_perm; + let ops = &perm.as_bytes()[4..]; + + assert_eq!(neko.ops_count, 2); + // What has changed since the original was the encoding of the second + // slot with the CLR op bytes. + assert_eq!(ops, &[0, 0x03, 0, 0]); - for block in neko.state[2..20].iter().map(IntoBytes::as_bytes) { + for block in neko.state[2..Neko128::BLOCK_RATE] + .iter() + .map(IntoBytes::as_bytes) + { // The permuted state means the message is mixed, so it can no longer be "read" // directly. assert_ne!(block, cleartext); @@ -203,31 +215,31 @@ fn cipher_ops_permute_state_by_default() { assert_eq!(neko.state[0..2].as_bytes(), &message); let expected_state = [ - 0x376ff3f6beb5164d, - 0x6fce4cdd41481b09, - 0x62c9980b6ebd4375, - 0x35bebdd63cbae3f2, - 0xe82c6eed7a9482b1, - 0xe3948f44d30e1b63, - 0x64f3fbd74d1485a9, - 0xe3a5c716bab805e0, - 0xdb50cee154ae7634, - 0xc5f16a574d98aaa0, - 0xa701afa0ce5e483a, - 0xffdb8ceedc0e2137, - 0x1ced87c9b390ad78, - 0xc066d80fecc4e712, - 0x4d8d46e5f5972a6d, - 0xa948bf5cb8e4442f, - 0x1f08d0ca81e27400, - 0x2234e5f1e79cc913, - 0x4238eb75cde4fa79, - 0xe163927593ef0bfa, - 0x1665d19a25c4a420, - 0x3ec458a9b98e00cb, - 0xf19ee10721f39c29, - 0x96f715bbe8f84a73, - 0xaec74c90f467207c, + 0xee1bd780e9810b96, + 0x141f855caa76e665, + 0x132b235388d6f9cd, + 0x6f3b7bb36d5aae31, + 0x21e15ea085516f15, + 0xd62c8310fb00ebc2, + 0xbd3dc9a134235b7b, + 0xc34f415bfae88043, + 0x2a913f769550592f, + 0x9e4ade8129203f5f, + 0xef2cfe83e79b31b9, + 0xc9b2f60ab8c45c0c, + 0xea6722e04e6d6f1a, + 0xd146a6b6997fe2f9, + 0x58715838467dcdb2, + 0x6760d93bc507e169, + 0x17449562b97f0c8d, + 0xab9c4a8d75bc4d42, + 0xed2bffba6811f8c5, + 0xa125405284f9128d, + 0x2db7dc29f6aa0901, + 0x702058185ef1056c, + 0x6c44d261f7b7a843, + 0x6133080d946e4af4, + 0xf8866851b40cd8b7, ]; assert_eq!(&neko.state, &expected_state); @@ -239,29 +251,29 @@ fn cipher_ops_permute_state_by_default() { let ratched_expected_state = [ 0x0000000000000000, 0x0000000000000000, - 0x01e23e724c12db57, - 0xe92a5c3fae8b582d, - 0x36454900c0add829, - 0x225646c8528ce4ff, - 0xe0cf79fd5e3e495a, - 0x77aed0a3813ef760, - 0xcbc0aa13baee85be, - 0x3fd706b857c5e671, - 0xfcbe3c0cefddfc6d, - 0xf8d1978f52c0d7e0, - 0x06c874e792fd7180, - 0xa6b978b38814727b, - 0x412d8e5666bde321, - 0x29e578d4ed104740, - 0x17b53fbaf1257b55, - 0x24cc8a75a308d641, - 0x3345c778c83c4882, - 0x1ea9692aff46faf8, - 0x68ce75a36d554dbc, - 0xe1faa52728b78408, - 0xdcabac6ec8e018e4, - 0xc631647d96ed3f06, - 0x8e29b59ab6a8956b, + 0xf8afbfdaf27a241, + 0xa0f798c66ae15778, + 0x6fd614cd3deceac7, + 0x30544fbaff5a98d3, + 0xeaecbf95bb5499f1, + 0xde4593a0b4c46223, + 0xc612abe9730c52b9, + 0x489f217fea714a0d, + 0x3b31bb77a0be0e8f, + 0x49e3a937fc374ddc, + 0x011aa4fee1207fb9, + 0xb639321de6967937, + 0x55e8bbd33106073b, + 0x851a857ed2a69d97, + 0x6433ec8a034fde27, + 0x5db9909b93c710a0, + 0xf50d00c9231ec518, + 0xeb13d284ab4e8fc4, + 0x1f8e616203621604, + 0xecff700e02fb9360, + 0xe9d2c72f599e6427, + 0xba5c1d26bb52cc58, + 0xdc33691a6e2944e6, ]; assert_eq!(&neko.state, &ratched_expected_state); @@ -284,31 +296,31 @@ In scelerisque, ex et porta varius, orci risus malesuada nibh, convallis euismod neko.encrypt(&mut large_encrypted_message); let expected_state = [ - 0x13412b65f7e20af3, - 0x6d3424b086abd336, - 0xcb9fc2f09e70d9a0, - 0x88c998c81951d0c2, - 0x059e869c2f7a61ce, - 0xc3659823ee130ee2, - 0xaa59c8ff1d95842f, - 0x940740fc352b4fae, - 0xdbd1854b5f012452, - 0xe14d604523a7c4b1, - 0x4a1a2305128b2afa, - 0xec94592e37b13bd9, - 0x2acce9aec73ec364, - 0x73324f4034356ff2, - 0x563ae980c6a2e903, - 0xdf6491d84e9a29dc, - 0xd86c5e19f7f243ca, - 0x18e7a1523d271264, - 0x41f808f2bb8d7c2c, - 0xfeea3498ca40ba48, - 0xb304f29eb1df2f33, - 0xce55d8a83bba04a4, - 0x452613733fbc50df, - 0xe137b9f22560f2d0, - 0xb12bb41da4aa156e, + 0xef3b442c03784d33, + 0x047021821d4dd4f7, + 0x19d084ace1e81310, + 0x1020468ccbe359ba, + 0x5bcb408218615519, + 0xc419db9b25449a4e, + 0x1d7bee272ac26ffc, + 0x9f337e907d3c4b82, + 0xc85ad7d990a99ce5, + 0x806cda5442ca6776, + 0xdcffc062b29fbf67, + 0x3d4e97230c9a9751, + 0xf2ad153ebca9a480, + 0x5453d209c8d7939c, + 0xd0fc55cf70d8d184, + 0xac18d328a8d2e252, + 0x892ffd742dbf6e91, + 0xe9212b8f85010259, + 0x4592a0ebf60014da, + 0x594cba49f3f9a2e0, + 0x5b7973a84b4fb0e1, + 0x27354db62233e39f, + 0xf73b5a2e30da0965, + 0x66bad73993f077f7, + 0x592cbb43c40e0b01, ]; assert_eq!(&neko.state, &expected_state); @@ -317,8 +329,8 @@ In scelerisque, ex et porta varius, orci risus malesuada nibh, convallis euismod let tag = neko.create_mac(); let expected_tag: [u8; 16] = [ - 0x9b, 0x89, 0x06, 0xea, 0x70, 0xec, 0x90, 0xc1, 0x9e, 0x47, 0x62, 0xd7, 0x52, 0xcb, 0x0d, - 0xaa, + 0x1f, 0x8a, 0x99, 0x86, 0x30, 0x96, 0xf9, 0xc2, 0x6b, 0x70, 0x21, 0xb8, 0x01, 0x11, 0xe5, + 0x0b, ]; assert_eq!(&tag, &expected_tag); diff --git a/wharrgarbl-neko/src/lib.rs b/wharrgarbl-neko/src/lib.rs index 35d87f6..8754477 100644 --- a/wharrgarbl-neko/src/lib.rs +++ b/wharrgarbl-neko/src/lib.rs @@ -29,7 +29,7 @@ pub type NekoNonce = Array as IvSizeUser>::IvSize>; pub type NekoKey = Array as KeySizeUser>::KeySize>; pub type NekoTag = Array; -pub static NEKO_VERSION: &str = "NEKOv0.2.1"; +pub static NEKO_VERSION: &str = "NEKOv0.3.0"; const U64_CHUNK: usize = core::mem::size_of::(); const MAX_OPS: usize = core::mem::size_of::(); @@ -45,8 +45,7 @@ impl IvSizeUser for NekoState { pub struct NekoState { pub(crate) state: Array, position: usize, - ops_count: usize, - pub(crate) ops_stack: Array, + pub(crate) ops_count: usize, sec: PhantomData, } @@ -55,7 +54,6 @@ impl zeroize::Zeroize for NekoState { self.state.zeroize(); self.position.zeroize(); self.ops_count.zeroize(); - self.ops_stack.zeroize(); } } @@ -76,7 +74,7 @@ impl NekoState { /// /// let neko = NekoState::::new(b"whimsical"); /// - /// assert_eq!(format!("{neko}"), "NEKOv0.2.1/1600-128"); + /// assert_eq!(format!("{neko}"), "NEKOv0.3.0/1600-128"); /// ``` pub fn new(protocol: &[u8]) -> Self { const { @@ -85,16 +83,17 @@ impl NekoState { }; // OPS stack MUST be initialised with the INIT flag as the first op. - let ops_stack = [ops::INIT.bits(), 0, 0, 0]; + let mut state: Array = Array([0u64; keccak::PLEN]); + + state[Sec::BLOCK_RATE].as_mut_bytes()[4] ^= ops::INIT.bits(); let mut neko = Self { // The buffer state is the zeroed buffer layout for KeccakF1600: [u64; 25] - state: Array([0u64; keccak::PLEN]), + state, // No bytes have been loaded into the buffer yet, therefore set to zero position: 0, // INIT op has been loaded into the stack, therefore op count must be 1. ops_count: 1, - ops_stack: Array(ops_stack), sec: PhantomData, }; @@ -173,7 +172,7 @@ impl NekoState { self.ops_count += 1; // Encode the opflags to the available stack slot. - self.ops_stack[op_index] = red_flags.bits(); + self.state[Sec::BLOCK_RATE].as_mut_bytes()[4..][op_index] ^= red_flags.bits(); } fn permutation_p12(&mut self, continuation: OpFlags) { @@ -182,19 +181,21 @@ impl NekoState { assert!(Sec::BLOCK_RATE < u8::MAX as usize); }; - // Last byte is zeroed in case the terminator overlaps - let permuter: Array = - Array([self.ops_count as u8, self.raw_position(), 0x80u8.to_le(), 0]); + let position = self.raw_position(); + + self.state.as_mut_bytes()[position as usize] ^= 0x80u8.to_le(); - let permuter_block = u64::from_ne_bytes(self.ops_stack.concat(permuter).0); + // First byte is zeroed in case the padding start overlaps, with the second byte being the + // padding terminator. There will always be at least one zero between padding start and the + // padding terminator. + let permuter: Array = Array([0, 0x40u8.to_le(), self.ops_count as u8, position]); - // XOR the permuter block into the next block, spilling into the first entropy block - // if required - self.state[self.position.div_ceil(U64_CHUNK) + 1] ^= permuter_block; - // Flip a bit in the last byte of the first entropy block with a 1 to act as the padding terminator. - // The bit is selected via rotating right the value 0x80 (0b1000_0000) by the position counter. - self.state[Sec::BLOCK_RATE].as_mut_bytes()[7] ^= - 0x80u8.to_le().rotate_right(self.position as u32); + // XOR the permuter block into the BLOCK_RATE block, which concats the padding, ops count + // & position, with the encoded ops stack. + self.state[Sec::BLOCK_RATE].as_mut_bytes()[0..4] + .iter_mut() + .zip(permuter) + .for_each(|(state, perm)| *state ^= perm); // The state has been fully prepared, and now can be permuted by the p1600(12) function. keccak::Keccak::new().with_p1600::<12>(|permute| permute(&mut self.state.0)); @@ -203,7 +204,7 @@ impl NekoState { // ops count is set to 1 and the first op slot is encoded with 0x01. self.position = 0; self.ops_count = continuation.bits() as usize; - self.ops_stack = Array([continuation.bits(), 0, 0, 0]); + self.state[Sec::BLOCK_RATE].as_mut_bytes()[4] ^= continuation.bits(); } #[inline] @@ -213,16 +214,16 @@ impl NekoState { assert!(Sec::RATCHET < u8::MAX as usize); }; // Select the amount of bytes to zero, according to Security level - // 128 bits = 16 bytes to zero out to achieve forward secrecy - // 192 bits = 24 bytes to zero out to achieve forward secrecy - // 256 bits = 32 bytes to zero out to achieve forward secrecy - let ratchet_bytes = Sec::RATCHET; + // 128 bits = 16 bytes = 2 blocks to zero out to achieve forward secrecy + // 192 bits = 24 bytes = 3 blocks to zero out to achieve forward secrecy + // 256 bits = 32 bytes = 4 blocks to zero out to achieve forward secrecy + let ratchet_blocks = Sec::RATCHET; - self.state[0..ratchet_bytes].iter_mut().for_each(|block| { + self.state[0..ratchet_blocks].iter_mut().for_each(|block| { *block = 0; }); - self.position += ratchet_bytes * U64_CHUNK; + self.position += ratchet_blocks * U64_CHUNK; } /// Sets a provided key into the cipher state. @@ -381,10 +382,10 @@ mod tests { let display = std::format!("{s}"); let debug = std::format!("{s:?}"); - assert_eq!(&display, "NEKOv0.2.1/1600-128"); + assert_eq!(&display, "NEKOv0.3.0/1600-128"); assert_eq!( &debug, - "NekoState { sec: 128, version: \"NEKOv0.2.1\", .. }" + "NekoState { sec: 128, version: \"NEKOv0.3.0\", .. }" ); } }