defmodule ExSha3 do @moduledoc """ ExSha3 supports the three hash algorithms: * KECCAK1600-f the original pre-fips version as used in Ethereum * SHA3 the fips-202 approved final hash * SHAKE Keccak and SHA3 produce fixed length strings corresponding to their bit length, while shake produces an arbitary length output according to the provided outlen parameter. """ require Record import :erlang, only: [bxor: 2, band: 2] @compile :inline_list_funcs @compile {:inline_unroll, 24} @compile {:inline_effort, 500} @compile {:inline_size, 1000} @compile {:inline, rol: 2, for_n: 4, binary_a64: 2, bnot: 1} @rho {1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44} @pi {10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1} @rc {1, 0x8082, 0x800000000000808A, 0x8000000080008000, 0x808B, 0x80000001, 0x8000000080008081, 0x8000000000008009, 0x8A, 0x88, 0x80008009, 0x8000000A, 0x8000808B, 0x800000000000008B, 0x8000000000008089, 0x8000000000008003, 0x8000000000008002, 0x8000000000000080, 0x800A, 0x800000008000000A, 0x8000000080008081, 0x8000000000008080, 0x80000001, 0x8000000080008008} @zero64 0 @full64 0xFFFFFFFFFFFFFFFF defp rol(x, s) when is_integer(x) and is_integer(s) do x = :erlang.bsl(x, s) y = :erlang.bsr(x, 64) :erlang.band(x, @full64) + y end defp for_n(0, _step, acc, _fun), do: acc defp for_n(n, step, acc, fun) when is_integer(n) and is_integer(step) do n = n - 1 fun.(n * step, for_n(n, step, acc, fun)) end defp binary_a64(<>, tuple) do binary_a64(rest, Tuple.insert_at(tuple, tuple_size(tuple), bin)) end defp binary_a64("", tuple) do tuple end defp a64_binary(tuple) do Tuple.to_list(tuple) |> Enum.map(fn num -> <> end) |> :erlang.iolist_to_binary() end defp bnot(a) when is_integer(a), do: bxor(a, @full64) Record.defrecord(:calc, inbin: {@zero64, @zero64, @zero64, @zero64, @zero64}, t: @zero64, state: nil ) defp keccakf(a) do rho = @rho pi = @pi rc = @rc state = binary_a64(a, {}) # acc = {a, inbin} acc = calc(state: state) for_n(24, 1, acc, fn i, acc when is_integer(i) and is_tuple(acc) -> # // Theta acc = for_n(5, 1, acc, fn x, acc = calc(inbin: inbin) when is_integer(x) and is_tuple(state) and is_tuple(inbin) -> inbin = put_elem(inbin, x, @zero64) acc = calc(acc, inbin: inbin) for_n(5, 5, acc, fn y, acc = calc(state: state, inbin: inbin) when is_integer(y) and is_tuple(state) and is_tuple(inbin) -> ret = bxor(elem(inbin, x), elem(state, x + y)) inbin = put_elem(inbin, x, ret) calc(acc, inbin: inbin) end) end) calc(state: state, inbin: inbin) = for_n(5, 1, acc, fn x, acc when is_integer(x) -> for_n(5, 5, acc, fn y, acc = calc(state: state, inbin: inbin) when is_integer(y) and is_tuple(state) and is_tuple(inbin) -> ret = bxor(elem(inbin, rem(x + 4, 5)), rol(elem(inbin, rem(x + 1, 5)), 1)) |> bxor(elem(state, y + x)) state = put_elem(state, x + y, ret) calc(acc, state: state) end) end) # // Rho and pi acc = calc(t: elem(state, 1), state: state, inbin: inbin) acc = for_n(24, 1, acc, fn x, calc(state: state, inbin: inbin, t: t) when is_integer(x) and is_integer(t) and is_tuple(state) and is_tuple(inbin) -> pix = elem(pi, x) new_t = elem(state, pix) inbin = put_elem(inbin, 0, new_t) state = put_elem(state, pix, rol(t, elem(rho, x))) calc(t: new_t, state: state, inbin: inbin) end) # // Chi acc = calc(state: state) = for_n(5, 5, acc, fn y, acc when is_integer(y) -> acc = for_n(5, 1, acc, fn x, acc = calc(state: state, inbin: inbin) when is_integer(x) and is_tuple(state) and is_tuple(inbin) -> inbin = put_elem(inbin, x, elem(state, y + x)) calc(acc, inbin: inbin) end) for_n(5, 1, acc, fn x, acc = calc(state: state, inbin: inbin) when is_integer(x) and is_tuple(state) and is_tuple(inbin) -> ret = bnot(elem(inbin, rem(x + 1, 5))) |> band(elem(inbin, rem(x + 2, 5))) |> bxor(elem(inbin, x)) state = put_elem(state, y + x, ret) calc(acc, state: state) end) end) # // Iota state = put_elem(state, 0, bxor(elem(state, 0), elem(rc, i))) calc(acc, state: state) end) |> calc(:state) |> a64_binary() end defp xorin(dst, src, offset, len) do new = :crypto.exor(binary_part(src, offset, len), binary_part(dst, 0, len)) dst2 = binary_put(dst, 0, new) {dst2, src} end defp setout(src, dst, offset, len) do new = binary_part(src, 0, len) dst2 = binary_put(dst, offset, new) {src, dst2} end # P*F over the full blocks of an input. defp foldP(a, inbin, len, fun, rate) when len >= rate do {a, inbin} = fun.(a, inbin, byte_size(inbin) - len, rate) a = keccakf(a) foldP(a, inbin, len - rate, fun, rate) end defp foldP(a, inbin, len, _fun, _rate) do {a, inbin, len} end defp binary_put(bin, offset, new) do binary_part(bin, 0, offset) <> new <> binary_part(bin, offset + byte_size(new), byte_size(bin) - (offset + byte_size(new))) end defp binary_new(size) do String.duplicate(<<0>>, size) end defp binary_xor(var, index, value) do index = floor(index) c = :crypto.exor(binary_part(var, index, 1), value) binary_put(var, index, c) end @plen 200 # /** The sponge-based hash construction. **/ defp hash(outlen, source, rate, delim) do outlen = floor(outlen) inlen = floor(byte_size(source)) rate = floor(rate) # // Absorb input. a = binary_new(@plen) {a, _, inlen} = foldP(a, source, inlen, &xorin/4, rate) # // Xor source the DS and pad frame. a = binary_xor(a, inlen, <>) a = binary_xor(a, rate - 1, <<0x80>>) # // Xor source the last block. {a, _source} = xorin(a, source, floor(byte_size(source) - inlen), inlen) # // Apply P a = keccakf(a) # // Squeeze output. out = binary_new(outlen) {a, out, outlen} = foldP(a, out, outlen, &setout/4, rate) {_a, out} = setout(a, out, 0, outlen) out end defp shake(bits, outlen, source), do: hash(outlen, source, 200 - bits / 4, 0x1F) @spec shake_128(binary(), number()) :: binary() def shake_128(source, outlen), do: shake(128, outlen, source) @spec shake_256(binary(), number()) :: binary() def shake_256(source, outlen), do: shake(256, outlen, source) defp sha3(bits, source), do: hash(bits / 8, source, 200 - bits / 4, 0x06) @spec sha3_224(binary()) :: binary() def sha3_224(source), do: sha3(224, source) @spec sha3_256(binary()) :: binary() def sha3_256(source), do: sha3(256, source) @spec sha3_384(binary()) :: binary() def sha3_384(source), do: sha3(384, source) @spec sha3_512(binary()) :: binary() def sha3_512(source), do: sha3(512, source) defp keccak(bits, source), do: hash(bits / 8, source, 200 - bits / 4, 0x01) @spec keccak_224(binary()) :: binary() def keccak_224(source), do: keccak(224, source) @spec keccak_256(binary()) :: binary() def keccak_256(source), do: keccak(256, source) @spec keccak_384(binary()) :: binary() def keccak_384(source), do: keccak(384, source) @spec keccak_512(binary()) :: binary() def keccak_512(source), do: keccak(512, source) end