Current section
Files
Jump to
Current section
Files
lib/pgp_wordlist.ex
defmodule PgpWordlist do
@words Application.get_env(:pgp_wordlist, :words)
@moduledoc """
Toolset for working with the PGP word list. Allows converting hex to / from words.
The word list itself belongs to PGP Corporation.
"""
@doc """
Converts a 2 digit hex string (with uppercase letters) and even / odd position into a word on the PGP word list.
`pos` is the 0-based index of the hex. There are two words assigned to each hex value with `pos` determining which word to use based on if it is even or odd.
## Examples
iex> PgpWordlist.hex_to_word("00", 0)
"aardvark"
iex> PgpWordlist.hex_to_word("00", 1)
"adroitness"
iex> PgpWordlist.hex_to_word("00", 2)
"aardvark"
iex> PgpWordlist.hex_to_word("FF", 0)
"Zulu"
iex> PgpWordlist.hex_to_word("FF", 1)
"Yucatan"
iex> PgpWordlist.hex_to_word("FF", 33)
"Yucatan"
iex> PgpWordlist.hex_to_word("1", 1)
** (ArgumentError) Hex must be 2 bytes long ('01' instead of '1')
"""
@spec hex_to_word(binary, non_neg_integer) :: binary
def hex_to_word(hex, pos)
def hex_to_word(hex, _pos) when is_binary(hex) and byte_size(hex) == 1 do
raise(ArgumentError, "Hex must be 2 bytes long ('01' instead of '1')")
end
@doc """
Converts a word to a hex based on the PGP word list.
Will handle different cases of words and return hex with uppercase letters.
## Examples
iex> PgpWordlist.word_to_hex("AARDVARK")
"00"
iex> PgpWordlist.word_to_hex("aardvark")
"00"
iex> PgpWordlist.word_to_hex("AardVaRk")
"00"
iex> PgpWordlist.word_to_hex("woodlark")
"FE"
"""
@spec word_to_hex(binary) :: binary
def word_to_hex(word)
# Lower the word just to simplify things
def word_to_hex(word), do: lowered_word_to_hex(String.downcase(word))
# To increase performance and reduce the amount of typing we have to do
# we use the for comprehension to generate different functions for us.
#
# Each function pattern matches a different word / hex and returns the appropriate item.
# Guards run the correct function based on if the position is even or odd.
for {{word_a, word_b}, number} <- Enum.with_index(@words) do
hex =
number
|> Integer.to_string(16)
|> String.pad_leading(2, "0")
def hex_to_word(unquote(hex), pos) when rem(pos, 2) == 0, do: unquote(word_a)
def hex_to_word(unquote(hex), pos) when rem(pos, 2) == 1, do: unquote(word_b)
# To account for a user we only accept the lowered version of the word.
defp lowered_word_to_hex(unquote(String.downcase(word_a))), do: unquote(hex)
defp lowered_word_to_hex(unquote(String.downcase(word_b))), do: unquote(hex)
end
@doc """
Converts an even length hex string to list of words
## Examples
iex> PgpWordlist.hex_to_words("00")
["aardvark"]
iex> PgpWordlist.hex_to_words("E582")
["topmost", "Istanbul"]
iex> PgpWordlist.hex_to_words("82E5")
["miser", "travesty"]
iex> PgpWordlist.hex_to_words("")
[]
iex> PgpWordlist.hex_to_words("00F")
** (ArgumentError) Hex must be an even amount of bytes long
"""
@spec hex_to_words(binary) :: list(binary())
def hex_to_words(hex)
def hex_to_words(hex) when is_binary(hex) and rem(byte_size(hex), 2) == 1 do
raise(ArgumentError, "Hex must be an even amount of bytes long")
end
def hex_to_words(hex) when is_binary(hex) and rem(byte_size(hex), 2) == 0 do
hex
# Split the string into characters
|> String.split("", trim: true)
# Groups of two characters
|> Stream.chunk_every(2)
# We need to join these two characters back together
|> Stream.map(&Enum.join/1)
# Associate each one with an index
|> Stream.with_index()
# Finally call our other function to convert it
|> Enum.map(fn {h, p} -> hex_to_word(h, p) end)
end
@doc """
Converts a list of words to hex based on the PGP word list
## Examples
iex> PgpWordlist.words_to_hex(["topmost", "Istanbul", "Pluto", "vagabond"])
"E58294F2"
iex> PgpWordlist.words_to_hex(["miser", "travesty"])
"82E5"
iex> PgpWordlist.words_to_hex([])
""
"""
@spec words_to_hex(list(binary)) :: binary
def words_to_hex(words)
def words_to_hex(words) when is_list(words) do
words
# Go through each one and convert the word back to a hex value
|> Enum.map(&word_to_hex/1)
# Join all the hexs together
|> Enum.join()
end
end