defmodule Slip39 do
@moduledoc """
Elixir implementation of the [SLIP-0039 : Shamir's Secret-Sharing for Mnemonic Codes](https://github.com/satoshilabs/slips/blob/master/slip-0039.md).
This implementation is largely inspired by the [reference implementation](http://github.com/trezor/python-shamir-mnemonic/).
This work is very in progress. Any contribution is welcome.
> #### DISCLAIMER {: .warning}
>
>This module is supplied as is. Since it has not been audited and can
>contains bugs, it should not be used to manipulate valuable secrets. If you decide to
>ignore this warning, use basic OPSEC common sense and, at the very least, use a
>computer booted on a USB key, then taken offline during calls to the functions of this
>module. In addition, each mnemonic generated must never be stored in an electronic format
>and must be carefully re-read. In any case, its/their authors cannot be held responsible
>for any loss of data or its financial consequences resulting from the direct or indirect
>use of this module.
"""
alias Slip39.Shamir
alias Slip39.Cypher
alias Slip39.Utils
@doc """
Split a master secret into mnemonic shares using Shamir's secret sharing scheme.
The supplied Master Secret is encrypted by the passphrase (empty passphrase is used
if none is provided) and split into a set of mnemonic shares.
This function allows a two level group definition (see example).
## Examples
Alice want to backup her secret. She will keep two share for herself, in separate vault,
but in case she loose one of them, she wants to be able to recover her secret, by recovering
any three share she had given to five of her best friends.
Note that since random data is inserted into mnemonics when they are generated you won't have
the same output when running this example.
iex(1)> groups = Slip39.generate_mnemonics!(2, [{1,1}, {1,1}, {3,5}], "Long life to Elixir!", "")
[
["window provide acrobat leader include tidy estate promise holiday earth trip solution cubic math teammate auction bracelet fishing walnut faint minister material endless"],
["window provide beard leader fumes scholar primary elder flavor theory papa insect holiday survive beam cards laundry flexible lunch presence rebuild emerald exhaust"],
["window provide ceramic learn charity industry aviation sack package priority umbrella forbid triumph webcam starting firefly problem vocal improve task sympathy spray herd",
"window provide ceramic lips lecture airport hobo legal declare describe says source quiet golden pitch society genius raisin ceiling admit luxury empty license",
"window provide ceramic luxury diet diploma bumpy cowboy usher building drove leaves rapids trust squeeze salt mule presence pupal disease public rainbow afraid",
"window provide ceramic march mule ending jerky hanger frozen lift beyond antenna unknown intend platform employer focus unknown taught tidy tidy clinic typical",
"window provide ceramic method clay carve stadium laden tenant rhythm river much security isolate fumes axis calcium expect pulse petition miracle senior else"]
]
...(2)> to_be_stored_in_alice_vault1 = groups |> Enum.at(0)
["window provide acrobat leader include tidy estate promise holiday earth trip solution cubic math teammate auction bracelet fishing walnut faint minister material endless"]
...(3)> to_be_stored_in_alice_vault2 = groups |> Enum.at(1)
["window provide beard leader fumes scholar primary elder flavor theory papa insect holiday survive beam cards laundry flexible lunch presence rebuild emerald exhaust"]
...(4)> to_be_given_to_five_friends = groups |> Enum.at(2)
["window provide ceramic learn charity industry aviation sack package priority umbrella forbid triumph webcam starting firefly problem vocal improve task sympathy spray herd",
"window provide ceramic lips lecture airport hobo legal declare describe says source quiet golden pitch society genius raisin ceiling admit luxury empty license",
"window provide ceramic luxury diet diploma bumpy cowboy usher building drove leaves rapids trust squeeze salt mule presence pupal disease public rainbow afraid",
"window provide ceramic march mule ending jerky hanger frozen lift beyond antenna unknown intend platform employer focus unknown taught tidy tidy clinic typical",
"window provide ceramic method clay carve stadium laden tenant rhythm river much security isolate fumes axis calcium expect pulse petition miracle senior else"]
"""
def generate_mnemonics!(
group_threshold,
groups,
master_secret,
passphrase,
iteration_exponent
) do
if not Utils.is_string_only_ascii(passphrase) do
raise RuntimeError,
"The passphrase must contain only printable ASCII characters (code points 32-126)."
end
if bit_size(master_secret) < 128 do
raise("The length of the master secret must be at least 128 bits.")
end
if not is_binary(master_secret) do
raise("The length of the master should be a multiple of 8 bits.")
end
identifier = Shamir.random_identifier()
encrypted_master_secret =
Cypher.encrypt(master_secret, passphrase, iteration_exponent, identifier)
grouped_shares =
Shamir.split_ems!(
group_threshold,
groups,
encrypted_master_secret,
iteration_exponent,
identifier
)
for group <- grouped_shares do
for share <- group do
share |> Slip39.Share.encode!() |> Enum.join(" ")
end
end
end
@doc """
Split a master secret into mnemonic shares using Shamir's secret sharing scheme
using a single threshold/count group and no passphrase encryption.
## Examples
Since random data is inserted into mnemonics when they are generated
you won't have the same output when running this example.
iex(1)> Slip39.generate_mnemonics!(3,5,"Long life to Elixir!")
[
["formal discuss academic acne rebound violence class ultimate float envelope smear costume fumes medical license practice eclipse language auction check coding lamp lecture",
"formal discuss academic agree debut bucket both salt cage home improve purchase burden amuse paces distance acid destroy plan piece alive upstairs being",
"formal discuss academic amazing database devote software peasant either profile junction object disaster velvet aspect regret adapt axle excuse advocate flip eyebrow friar",
"formal discuss academic arcade pitch subject thunder math airport maiden season acquire ladle infant being criminal edge forward trouble loan jacket system smirk",
"formal discuss academic axle overall phrase treat thank branch transfer wireless faint award skin jerky paid twice fake exact echo saver steady ruler"]
]
"""
def generate_mnemonics!(threshold, share_count, master_secret) do
generate_mnemonics!(1, [{threshold, share_count}], master_secret, "", 1)
end
@doc """
Combine mnemonic shares to obtain the master secret which was previously split
using Shamir's secret sharing scheme.
This is the user-friendly method to recover a backed-up secret optionally protected
by a passphrase.
## Examples
iex(1)> ["pajamas lungs academic acid thank snapshot scramble should mason vintage lilac infant rocky dismiss blue spider recover step alcohol that early pregnant guilt","pajamas lungs academic agency firefly order hearing describe forbid welfare emphasis true ting pregnant solution genre source graduate idea alcohol squeeze luxury elegant","pajamas lungs academic always minister evening lunch prospect duke license realize evening armed laser oven warmth helpful slap trial home sunlight trend standard"]
iex(2)> |> Slip39.combine_mnemonics
"Long life to Elixir!"
"""
@spec combine_mnemonics(list(String.t()), String.t()) :: binary()
def combine_mnemonics(mnemonics, passphrase \\ "") do
groups_map = Shamir.decode_mnemonics(mnemonics)
{encrypted_master_secret, common_params} = Shamir.recover_ems(groups_map)
Cypher.decrypt(
encrypted_master_secret,
passphrase,
common_params.iteration_exponent,
common_params.identifier
)
end
end