Sponge operations

The Keccak algorithm family uses a cryptographic sponge construction. A sponge in this context is a block of data, divided in two parts of size "rate" and "capacity", respectively, combined with a transformation function and a padding rule. A sponge can "absorb" input data and output data can be "squeezed" from it. The operations may not be mixed, however. The squeezing phase has to follow the absorption phase, with no further absorbing after the first squeeze.

Both absorption and squeezing are specified in terms of rate-sized blocks. When absorbing a block, it is XORed with the rate part of the sponge data and the transformation function is invoked on the whole sponge data. The padding rule specifies how to cope with input data with a length not divisible by the sponge rate. Squeezing a block is done by reading the rate part from the sponge data and invoking the transformation function on the whole sponge data.

To ease the use, the functions absorb and squeeze allow arbitrary data sizes. The proper mapping to rate-sized blocks is handled internally. Furthermore, padding is not performed by actually modifying the input data; rather, the pad function is invoked on the sponge and immediately absorbs the padding data.

When using the sponge functions, it is important to note that the sponges in Keccak.jl are immutable. Hence, the sponges are not modified in-place. Instead, updated sponges are returned. Thus, computing the 256bit SHA-3 with partitioning both input and output in two blocks each could look like this:

julia> sponge = sha3_256_sponge();

julia> sponge = absorb(sponge, 0x00:0x04); # absorb first data chunk

julia> sponge = absorb(sponge, 0x05:0x09); # absorb another data chunk

julia> sponge = pad(sponge); # absorb appropriate padding

julia> sponge, out1 = squeeze(sponge, Val(16)); # first part of output

julia> sponge, out2 = squeeze(sponge, Val(16)); # second part of output

julia> (out1..., out2...)
(0x60, 0x5a, 0x05, 0x14, 0x05, 0x91, 0x92, 0xe2, 0x6d, 0xbf, 0x06, 0xcf, 0xab, 0x86, 0xf3, 0xe9, 0xbb, 0xb9, 0xa6, 0x93, 0x63, 0xd4, 0xbe, 0x92, 0x5b, 0x22, 0x46, 0xdc, 0xd8, 0x65, 0x9a, 0x95)

julia> sha3_256(0x00:0x09) # same result
(0x60, 0x5a, 0x05, 0x14, 0x05, 0x91, 0x92, 0xe2, 0x6d, 0xbf, 0x06, 0xcf, 0xab, 0x86, 0xf3, 0xe9, 0xbb, 0xb9, 0xa6, 0x93, 0x63, 0xd4, 0xbe, 0x92, 0x5b, 0x22, 0x46, 0xdc, 0xd8, 0x65, 0x9a, 0x95)

The data provided to absorb can be an AbstractVector{UInt8} or a Tuple{Vararg{UInt8}}, or a String of which its codeunits will be used. If the length given to squeeze is a Val (as in the example above), the return value will be a Tuple, if given as a plain integer, it will be a Vector{UInt8}.

Warning

Conceptually, a sponge is either in the absorption phase or the squeezing phase. In the absorption phase, only absorb or pad may be invoked, where the latter transitions the sponge to the squeezing phase. In the squeezing phase, only squeeze may be called. Thus, the only permissible order of operations on a fresh sponge is:

  1. Process the input data with zero or more calls to absorb.
  2. Call pad exactly once.
  3. Produce the output by zero or more calls to squeeze.

Violating this sequence is (at present) technically possible, but the results are unspecified.

SIMD

The sponge operations support SIMD processing if the sponge state is stored as SIMD.Vecs. Such SIMD-capable sponges can be created with sha3_224_sponge(::Val), sha3_256_sponge(::Val), sha3_384_sponge(::Val), sha3_512_sponge(::Val), shake_128_sponge(::Val), or shake_256_sponge(::Val), where the Val-parameter specifies the desired level of SIMD-parallelism. Given a sponge for N-fold parallelism, the sponge operations work as follows:

  • absorb with a single data argument absorbs the same data into all N parallel sponges.
  • absorb with N data arguments absorbs these into the respective parallel sponges. All data arguments have to be of the same length.
  • pad pads all N parallel sponges.
  • squeeze returns an N-tuple of the data squeezed from the respective parallel sponges.

For example, to compute the SHA3-256 hashes of "message #1", "message #2", "message #3", and "message #4", we can do:

julia> sponge = sha3_256_sponge(Val(4));

julia> sponge = absorb(sponge, "message #"); # simultaneously absorb common prefix

julia> sponge = absorb(sponge, "1", "2", "3", "4"); # now with different data

julia> sponge = pad(sponge);

julia> squeeze(sponge, Val(32))[2]
((0x8f, 0xf2, 0xcb, 0xa6, 0x2c, 0x19, 0x93, 0x77, 0x2a, 0x18, 0x8f, 0x8e, 0xf3, 0xa6, 0x13, 0x99, 0x15, 0x06, 0xc1, 0x48, 0x50, 0x3a, 0xd6, 0xdd, 0xbd, 0x15, 0x79, 0x09, 0x87, 0xe3, 0x61, 0x71), (0xe1, 0x44, 0x88, 0x35, 0xd8, 0x78, 0x65, 0x25, 0xa1, 0xbf, 0x5f, 0x2a, 0xfe, 0xec, 0x3b, 0x5a, 0x77, 0x4f, 0xb9, 0xa2, 0xe1, 0x91, 0x54, 0x5d, 0x1c, 0x06, 0x31, 0x94, 0x57, 0x07, 0x9c, 0x90), (0xce, 0xf9, 0x40, 0x8d, 0x2c, 0x57, 0x41, 0x91, 0x8b, 0x40, 0xc2, 0xab, 0x32, 0x42, 0xf1, 0xb5, 0xe7, 0xb2, 0x21, 0x9f, 0xbf, 0x3a, 0x3c, 0xc8, 0xe3, 0x6d, 0x0c, 0x66, 0x32, 0x00, 0x84, 0xa2), (0xf5, 0x59, 0x99, 0xcd, 0xef, 0x1a, 0x96, 0x5c, 0x80, 0x96, 0x0b, 0xe5, 0xda, 0x0b, 0xb9, 0x99, 0xa4, 0x15, 0xcb, 0x36, 0x5a, 0x8b, 0x30, 0x97, 0xfc, 0x3c, 0x5b, 0xa5, 0x59, 0xcc, 0xa0, 0xd6))

julia> sha3_256("message #1"), sha3_256("message #2"), sha3_256("message #3"), sha3_256("message #4") # same result
((0x8f, 0xf2, 0xcb, 0xa6, 0x2c, 0x19, 0x93, 0x77, 0x2a, 0x18, 0x8f, 0x8e, 0xf3, 0xa6, 0x13, 0x99, 0x15, 0x06, 0xc1, 0x48, 0x50, 0x3a, 0xd6, 0xdd, 0xbd, 0x15, 0x79, 0x09, 0x87, 0xe3, 0x61, 0x71), (0xe1, 0x44, 0x88, 0x35, 0xd8, 0x78, 0x65, 0x25, 0xa1, 0xbf, 0x5f, 0x2a, 0xfe, 0xec, 0x3b, 0x5a, 0x77, 0x4f, 0xb9, 0xa2, 0xe1, 0x91, 0x54, 0x5d, 0x1c, 0x06, 0x31, 0x94, 0x57, 0x07, 0x9c, 0x90), (0xce, 0xf9, 0x40, 0x8d, 0x2c, 0x57, 0x41, 0x91, 0x8b, 0x40, 0xc2, 0xab, 0x32, 0x42, 0xf1, 0xb5, 0xe7, 0xb2, 0x21, 0x9f, 0xbf, 0x3a, 0x3c, 0xc8, 0xe3, 0x6d, 0x0c, 0x66, 0x32, 0x00, 0x84, 0xa2), (0xf5, 0x59, 0x99, 0xcd, 0xef, 0x1a, 0x96, 0x5c, 0x80, 0x96, 0x0b, 0xe5, 0xda, 0x0b, 0xb9, 0x99, 0xa4, 0x15, 0xcb, 0x36, 0x5a, 0x8b, 0x30, 0x97, 0xfc, 0x3c, 0x5b, 0xa5, 0x59, 0xcc, 0xa0, 0xd6))

For convenience, sha3_256 accepts multiple arguments (of the same length) directly, so the above can be written as

julia> sha3_256("message #1", "message #2", "message #3", "message #4")
((0x8f, 0xf2, 0xcb, 0xa6, 0x2c, 0x19, 0x93, 0x77, 0x2a, 0x18, 0x8f, 0x8e, 0xf3, 0xa6, 0x13, 0x99, 0x15, 0x06, 0xc1, 0x48, 0x50, 0x3a, 0xd6, 0xdd, 0xbd, 0x15, 0x79, 0x09, 0x87, 0xe3, 0x61, 0x71), (0xe1, 0x44, 0x88, 0x35, 0xd8, 0x78, 0x65, 0x25, 0xa1, 0xbf, 0x5f, 0x2a, 0xfe, 0xec, 0x3b, 0x5a, 0x77, 0x4f, 0xb9, 0xa2, 0xe1, 0x91, 0x54, 0x5d, 0x1c, 0x06, 0x31, 0x94, 0x57, 0x07, 0x9c, 0x90), (0xce, 0xf9, 0x40, 0x8d, 0x2c, 0x57, 0x41, 0x91, 0x8b, 0x40, 0xc2, 0xab, 0x32, 0x42, 0xf1, 0xb5, 0xe7, 0xb2, 0x21, 0x9f, 0xbf, 0x3a, 0x3c, 0xc8, 0xe3, 0x6d, 0x0c, 0x66, 0x32, 0x00, 0x84, 0xa2), (0xf5, 0x59, 0x99, 0xcd, 0xef, 0x1a, 0x96, 0x5c, 0x80, 0x96, 0x0b, 0xe5, 0xda, 0x0b, 0xb9, 0x99, 0xa4, 0x15, 0xcb, 0x36, 0x5a, 0x8b, 0x30, 0x97, 0xfc, 0x3c, 0x5b, 0xa5, 0x59, 0xcc, 0xa0, 0xd6))

In the same fashion, shake_128 and shake_256 can also be applied to multiple data arguments.