defmodule BitcoinLib.Key.HD.SeedPhrase.Checksum do
@moduledoc """
Checksum needed to generate a seed phrase
Source: https://raw.githubusercontent.com/bitcoin/bips/master/bip-0039/english.txt
"""
alias BitcoinLib.Crypto
@doc """
Adds checksum at the end of the seed
"""
@spec compute_and_append_to_seed(binary()) :: binary()
def compute_and_append_to_seed(binary_seed) do
nb_checksum_bits =
binary_seed
|> byte_size()
|> div(4)
checksum = compute(binary_seed, nb_checksum_bits)
concatenate(binary_seed, checksum, nb_checksum_bits)
end
@doc """
Computes the checksum, which is the first few bits of a SHA256 hash
## Examples
iex> <<27, 172, 62, 126, 195, 84, 6, 180, 26, 1, 13, 250, 0, 254, 239, 132>>
...> |> BitcoinLib.Key.HD.SeedPhrase.Checksum.compute(4)
2
"""
@spec compute(binary(), integer()) :: integer()
def compute(binary_seed, nb_bits_to_keep) do
binary_seed
|> compute_sha256()
|> keep_first_bits(nb_bits_to_keep)
end
@doc """
Computes the checksum, and verify that it matches to the one received
## Examples
iex> <<5, 235, 104, 86, 249, 249, 27, 246, 234, 99, 13, 18, 209, 116, 50, 248, 35>>
...> |> BitcoinLib.Key.HD.SeedPhrase.Checksum.validate_seed()
true
"""
@spec validate_seed(binary()) :: boolean()
def validate_seed(binary_seed) do
nb_of_checksum_bits = number_of_checksum_bits(binary_seed)
{seed, checksum} = split(binary_seed, nb_of_checksum_bits)
seed
|> compute(nb_of_checksum_bits)
|> Kernel.==(checksum)
end
defp compute_sha256(binary_seed) do
Crypto.sha256(binary_seed)
end
defp keep_first_bits(full_checksum, nb_bits_to_keep) do
<<chunk::size(nb_bits_to_keep), _rest::bitstring>> = full_checksum
chunk
end
defp split(binary, nb_of_checksum_bits) do
binary_size = byte_size(binary) * 8 - nb_of_checksum_bits
<<integer_seed::size(binary_size), checksum::size(nb_of_checksum_bits)>> = binary
{
Binary.from_integer(integer_seed),
checksum
}
end
defp concatenate(binary_seed, checksum, nb_of_checksum_bits) do
<<binary_seed::binary, checksum::size(nb_of_checksum_bits)>>
end
@spec number_of_checksum_bits(binary()) :: integer()
def number_of_checksum_bits(binary_seed) do
bit_size(binary_seed)
|> div(32)
end
end