diff --git a/core/SConscript.firmware b/core/SConscript.firmware
index 2771a82d71..626e21389c 100644
--- a/core/SConscript.firmware
+++ b/core/SConscript.firmware
@@ -84,6 +84,7 @@ SOURCE_MOD += [
'vendor/trezor-crypto/secp256k1.c',
'vendor/trezor-crypto/sha2.c',
'vendor/trezor-crypto/sha3.c',
+ 'vendor/trezor-crypto/shamir.c',
]
# libsecp256k1-zkp
diff --git a/core/SConscript.unix b/core/SConscript.unix
index 1bb0409c97..d66bdb5d20 100644
--- a/core/SConscript.unix
+++ b/core/SConscript.unix
@@ -82,6 +82,7 @@ SOURCE_MOD += [
'vendor/trezor-crypto/secp256k1.c',
'vendor/trezor-crypto/sha2.c',
'vendor/trezor-crypto/sha3.c',
+ 'vendor/trezor-crypto/shamir.c',
]
# libsecp256k1-zkp
diff --git a/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-shamir.h b/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-shamir.h
new file mode 100644
index 0000000000..a1589af271
--- /dev/null
+++ b/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-shamir.h
@@ -0,0 +1,90 @@
+/*
+ * This file is part of the TREZOR project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "py/obj.h"
+
+#include "embed/extmod/trezorobj.h"
+
+#include "shamir.h"
+
+#define SHAMIR_MAX_SHARE_COUNT 16
+
+/// def interpolate(shares, x) -> bytes:
+/// '''
+/// Returns f(x) given the Shamir shares (x_1, f(x_1)), ... , (x_k, f(x_k)).
+/// :param shares: The Shamir shares.
+/// :type shares: A list of pairs (x_i, y_i), where x_i is an integer and
+/// y_i is an array of bytes representing the evaluations of the
+/// polynomials in x_i.
+/// :param int x: The x coordinate of the result.
+/// :return: Evaluations of the polynomials in x.
+/// :rtype: Array of bytes.
+/// '''
+mp_obj_t mod_trezorcrypto_shamir_interpolate(mp_obj_t shares, mp_obj_t x) {
+ size_t share_count;
+ mp_obj_t *share_items;
+ mp_obj_get_array(shares, &share_count, &share_items);
+ if (share_count < 1 || share_count > SHAMIR_MAX_SHARE_COUNT) {
+ mp_raise_ValueError("Invalid number of shares.");
+ }
+ uint8_t x_uint8 = trezor_obj_get_uint8(x);
+ uint8_t share_indices[SHAMIR_MAX_SHARE_COUNT];
+ const uint8_t *share_values[SHAMIR_MAX_SHARE_COUNT];
+ size_t value_len = 0;
+ for (int i = 0; i < share_count; ++i) {
+ mp_obj_t *share;
+ mp_obj_get_array_fixed_n(share_items[i], 2, &share);
+ share_indices[i] = trezor_obj_get_uint8(share[0]);
+ mp_buffer_info_t value;
+ mp_get_buffer_raise(share[1], &value, MP_BUFFER_READ);
+ if (value_len == 0) {
+ value_len = value.len;
+ if (value_len > SHAMIR_MAX_LEN) {
+ mp_raise_ValueError("Share value exceeds maximum supported length.");
+ }
+ }
+ if (value.len != value_len) {
+ mp_raise_ValueError("All shares must have the same length.");
+ }
+ share_values[i] = value.buf;
+ }
+ vstr_t vstr;
+ vstr_init_len(&vstr, value_len);
+ if (shamir_interpolate((uint8_t *)vstr.buf, x_uint8, share_indices,
+ share_values, share_count, value_len) != true) {
+ vstr_clear(&vstr);
+ mp_raise_ValueError("Share indices must be pairwise distinct.");
+ }
+ return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr);
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorcrypto_shamir_interpolate_obj,
+ mod_trezorcrypto_shamir_interpolate);
+
+STATIC const mp_rom_map_elem_t mod_trezorcrypto_shamir_globals_table[] = {
+ {MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_shamir)},
+ {MP_ROM_QSTR(MP_QSTR_interpolate),
+ MP_ROM_PTR(&mod_trezorcrypto_shamir_interpolate_obj)},
+};
+STATIC MP_DEFINE_CONST_DICT(mod_trezorcrypto_shamir_globals,
+ mod_trezorcrypto_shamir_globals_table);
+
+STATIC const mp_obj_module_t mod_trezorcrypto_shamir_module = {
+ .base = {&mp_type_module},
+ .globals = (mp_obj_dict_t *)&mod_trezorcrypto_shamir_globals,
+};
diff --git a/core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c b/core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c
index 361bc6ed45..4e329dfe7a 100644
--- a/core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c
+++ b/core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c
@@ -50,6 +50,7 @@
#include "modtrezorcrypto-sha3-256.h"
#include "modtrezorcrypto-sha3-512.h"
#include "modtrezorcrypto-sha512.h"
+#include "modtrezorcrypto-shamir.h"
STATIC const mp_rom_map_elem_t mp_module_trezorcrypto_globals_table[] = {
{MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_trezorcrypto)},
@@ -89,6 +90,7 @@ STATIC const mp_rom_map_elem_t mp_module_trezorcrypto_globals_table[] = {
MP_ROM_PTR(&mod_trezorcrypto_Sha3_256_type)},
{MP_ROM_QSTR(MP_QSTR_sha3_512),
MP_ROM_PTR(&mod_trezorcrypto_Sha3_512_type)},
+ {MP_ROM_QSTR(MP_QSTR_shamir), MP_ROM_PTR(&mod_trezorcrypto_shamir_module)},
};
STATIC MP_DEFINE_CONST_DICT(mp_module_trezorcrypto_globals,
mp_module_trezorcrypto_globals_table);
diff --git a/core/src/trezor/crypto/slip39.py b/core/src/trezor/crypto/slip39.py
new file mode 100644
index 0000000000..c09034a495
--- /dev/null
+++ b/core/src/trezor/crypto/slip39.py
@@ -0,0 +1,613 @@
+# Copyright (c) 2018 Andrew R. Kozlik
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to
+# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is furnished to do
+# so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+from trezor.crypto import hashlib, hmac, pbkdf2, random
+from trezor.crypto.slip39_wordlist import wordlist
+from trezorcrypto import shamir
+
+
+class MnemonicError(Exception):
+ pass
+
+
+def bits_to_bytes(n):
+ return (n + 7) // 8
+
+
+def bits_to_words(n):
+ return (n + RADIX_BITS - 1) // RADIX_BITS
+
+
+RADIX_BITS = 10
+"""The length of the radix in bits."""
+
+RADIX = 2 ** RADIX_BITS
+"""The number of words in the wordlist."""
+
+ID_LENGTH_BITS = 15
+"""The length of the random identifier in bits."""
+
+ITERATION_EXP_LENGTH_BITS = 5
+"""The length of the iteration exponent in bits."""
+
+ID_EXP_LENGTH_WORDS = bits_to_words(ID_LENGTH_BITS + ITERATION_EXP_LENGTH_BITS)
+"""The length of the random identifier and iteration exponent in words."""
+
+MAX_SHARE_COUNT = 16
+"""The maximum number of shares that can be created."""
+
+CHECKSUM_LENGTH_WORDS = 3
+"""The length of the RS1024 checksum in words."""
+
+DIGEST_LENGTH_BYTES = 4
+"""The length of the digest of the shared secret in bytes."""
+
+CUSTOMIZATION_STRING = b"shamir"
+"""The customization string used in the RS1024 checksum and in the PBKDF2 salt."""
+
+METADATA_LENGTH_WORDS = ID_EXP_LENGTH_WORDS + 2 + CHECKSUM_LENGTH_WORDS
+"""The length of the mnemonic in words without the share value."""
+
+MIN_STRENGTH_BITS = 128
+"""The minimum allowed entropy of the master secret."""
+
+MIN_MNEMONIC_LENGTH_WORDS = METADATA_LENGTH_WORDS + bits_to_words(MIN_STRENGTH_BITS)
+"""The minimum allowed length of the mnemonic in words."""
+
+BASE_ITERATION_COUNT = 10000
+"""The minimum number of iterations to use in PBKDF2."""
+
+ROUND_COUNT = 4
+"""The number of rounds to use in the Feistel cipher."""
+
+SECRET_INDEX = 255
+"""The index of the share containing the shared secret."""
+
+DIGEST_INDEX = 254
+"""The index of the share containing the digest of the shared secret."""
+
+
+def word_index(word):
+ lo = 0
+ hi = len(wordlist)
+ while hi - lo > 1:
+ mid = (hi + lo) // 2
+ if wordlist[mid] > word:
+ hi = mid
+ else:
+ lo = mid
+ if not wordlist[lo].startswith(word):
+ raise MnemonicError('Invalid mnemonic word "{}".'.format(word))
+ return lo
+
+
+def _rs1024_polymod(values):
+ GEN = (
+ 0xE0E040,
+ 0x1C1C080,
+ 0x3838100,
+ 0x7070200,
+ 0xE0E0009,
+ 0x1C0C2412,
+ 0x38086C24,
+ 0x3090FC48,
+ 0x21B1F890,
+ 0x3F3F120,
+ )
+ chk = 1
+ for v in values:
+ b = chk >> 20
+ chk = (chk & 0xFFFFF) << 10 ^ v
+ for i in range(10):
+ chk ^= GEN[i] if ((b >> i) & 1) else 0
+ return chk
+
+
+def rs1024_create_checksum(data):
+ values = tuple(CUSTOMIZATION_STRING) + data + CHECKSUM_LENGTH_WORDS * (0,)
+ polymod = _rs1024_polymod(values) ^ 1
+ return tuple(
+ (polymod >> 10 * i) & 1023 for i in reversed(range(CHECKSUM_LENGTH_WORDS))
+ )
+
+
+def rs1024_verify_checksum(data):
+ return _rs1024_polymod(tuple(CUSTOMIZATION_STRING) + data) == 1
+
+
+def xor(a, b):
+ return bytes(x ^ y for x, y in zip(a, b))
+
+
+def _int_from_indices(indices):
+ """Converts a list of base 1024 indices in big endian order to an integer value."""
+ value = 0
+ for index in indices:
+ value = (value << RADIX_BITS) + index
+ return value
+
+
+def _int_to_indices(value, length, bits):
+ """Converts an integer value to indices in big endian order."""
+ mask = (1 << bits) - 1
+ return ((value >> (i * bits)) & mask for i in reversed(range(length)))
+
+
+def mnemonic_from_indices(indices):
+ return " ".join(wordlist[i] for i in indices)
+
+
+def mnemonic_to_indices(mnemonic):
+ return (word_index(word.lower()) for word in mnemonic.split())
+
+
+def _round_function(i, passphrase, e, salt, r):
+ """The round function used internally by the Feistel cipher."""
+ return pbkdf2(
+ pbkdf2.HMAC_SHA256,
+ bytes([i]) + passphrase,
+ salt + r,
+ (BASE_ITERATION_COUNT << e) // ROUND_COUNT,
+ ).key()[: len(r)]
+
+
+def _get_salt(identifier):
+ return CUSTOMIZATION_STRING + identifier.to_bytes(
+ bits_to_bytes(ID_LENGTH_BITS), "big"
+ )
+
+
+def _encrypt(master_secret, passphrase, iteration_exponent, identifier):
+ l = master_secret[: len(master_secret) // 2]
+ r = master_secret[len(master_secret) // 2 :]
+ salt = _get_salt(identifier)
+ for i in range(ROUND_COUNT):
+ (l, r) = (
+ r,
+ xor(l, _round_function(i, passphrase, iteration_exponent, salt, r)),
+ )
+ return r + l
+
+
+def decrypt(identifier, iteration_exponent, encrypted_master_secret, passphrase):
+ l = encrypted_master_secret[: len(encrypted_master_secret) // 2]
+ r = encrypted_master_secret[len(encrypted_master_secret) // 2 :]
+ salt = _get_salt(identifier)
+ for i in reversed(range(ROUND_COUNT)):
+ (l, r) = (
+ r,
+ xor(l, _round_function(i, passphrase, iteration_exponent, salt, r)),
+ )
+ return r + l
+
+
+def _create_digest(random_data, shared_secret):
+ return hmac.new(random_data, shared_secret, hashlib.sha256).digest()[
+ :DIGEST_LENGTH_BYTES
+ ]
+
+
+def _split_secret(threshold, share_count, shared_secret):
+ if threshold < 1:
+ raise ValueError(
+ "The requested threshold ({}) must be a positive integer.".format(threshold)
+ )
+
+ if threshold > share_count:
+ raise ValueError(
+ "The requested threshold ({}) must not exceed the number of shares ({}).".format(
+ threshold, share_count
+ )
+ )
+
+ if share_count > MAX_SHARE_COUNT:
+ raise ValueError(
+ "The requested number of shares ({}) must not exceed {}.".format(
+ share_count, MAX_SHARE_COUNT
+ )
+ )
+
+ # If the threshold is 1, then the digest of the shared secret is not used.
+ if threshold == 1:
+ return [(0, shared_secret)]
+
+ random_share_count = threshold - 2
+
+ shares = [(i, random.bytes(len(shared_secret))) for i in range(random_share_count)]
+
+ random_part = random.bytes(len(shared_secret) - DIGEST_LENGTH_BYTES)
+ digest = _create_digest(random_part, shared_secret)
+
+ base_shares = shares + [
+ (DIGEST_INDEX, digest + random_part),
+ (SECRET_INDEX, shared_secret),
+ ]
+
+ for i in range(random_share_count, share_count):
+ shares.append((i, shamir.interpolate(base_shares, i)))
+
+ return shares
+
+
+def _recover_secret(threshold, shares):
+ # If the threshold is 1, then the digest of the shared secret is not used.
+ if threshold == 1:
+ return shares[0][1]
+
+ shared_secret = shamir.interpolate(shares, SECRET_INDEX)
+ digest_share = shamir.interpolate(shares, DIGEST_INDEX)
+ digest = digest_share[:DIGEST_LENGTH_BYTES]
+ random_part = digest_share[DIGEST_LENGTH_BYTES:]
+
+ if digest != _create_digest(random_part, shared_secret):
+ raise MnemonicError("Invalid digest of the shared secret.")
+
+ return shared_secret
+
+
+def _group_prefix(
+ identifier, iteration_exponent, group_index, group_threshold, group_count
+):
+ id_exp_int = (identifier << ITERATION_EXP_LENGTH_BITS) + iteration_exponent
+ return tuple(_int_to_indices(id_exp_int, ID_EXP_LENGTH_WORDS, RADIX_BITS)) + (
+ (group_index << 6) + ((group_threshold - 1) << 2) + ((group_count - 1) >> 2),
+ )
+
+
+def encode_mnemonic(
+ identifier,
+ iteration_exponent,
+ group_index,
+ group_threshold,
+ group_count,
+ member_index,
+ member_threshold,
+ value,
+):
+ """
+ Converts share data to a share mnemonic.
+ :param int identifier: The random identifier.
+ :param int iteration_exponent: The iteration exponent.
+ :param int group_index: The x coordinate of the group share.
+ :param int group_threshold: The number of group shares needed to reconstruct the encrypted master secret.
+ :param int group_count: The total number of groups in existence.
+ :param int member_index: The x coordinate of the member share in the given group.
+ :param int member_threshold: The number of member shares needed to reconstruct the group share.
+ :param value: The share value representing the y coordinates of the share.
+ :type value: Array of bytes.
+ :return: The share mnemonic.
+ :rtype: Array of bytes.
+ """
+
+ # Convert the share value from bytes to wordlist indices.
+ value_word_count = bits_to_words(len(value) * 8)
+ value_int = int.from_bytes(value, "big")
+
+ share_data = (
+ _group_prefix(
+ identifier, iteration_exponent, group_index, group_threshold, group_count
+ )
+ + (
+ (((group_count - 1) & 3) << 8)
+ + (member_index << 4)
+ + (member_threshold - 1),
+ )
+ + tuple(_int_to_indices(value_int, value_word_count, RADIX_BITS))
+ )
+ checksum = rs1024_create_checksum(share_data)
+
+ return mnemonic_from_indices(share_data + checksum)
+
+
+def decode_mnemonic(mnemonic):
+ """Converts a share mnemonic to share data."""
+
+ mnemonic_data = tuple(mnemonic_to_indices(mnemonic))
+
+ if len(mnemonic_data) < MIN_MNEMONIC_LENGTH_WORDS:
+ raise MnemonicError(
+ "Invalid mnemonic length. The length of each mnemonic must be at least {} words.".format(
+ MIN_MNEMONIC_LENGTH_WORDS
+ )
+ )
+
+ padding_len = (RADIX_BITS * (len(mnemonic_data) - METADATA_LENGTH_WORDS)) % 16
+ if padding_len > 8:
+ raise MnemonicError("Invalid mnemonic length.")
+
+ if not rs1024_verify_checksum(mnemonic_data):
+ raise MnemonicError(
+ 'Invalid mnemonic checksum for "{} ...".'.format(
+ " ".join(mnemonic.split()[: ID_EXP_LENGTH_WORDS + 2])
+ )
+ )
+
+ id_exp_int = _int_from_indices(mnemonic_data[:ID_EXP_LENGTH_WORDS])
+ identifier = id_exp_int >> ITERATION_EXP_LENGTH_BITS
+ iteration_exponent = id_exp_int & ((1 << ITERATION_EXP_LENGTH_BITS) - 1)
+ tmp = _int_from_indices(
+ mnemonic_data[ID_EXP_LENGTH_WORDS : ID_EXP_LENGTH_WORDS + 2]
+ )
+ group_index, group_threshold, group_count, member_index, member_threshold = _int_to_indices(
+ tmp, 5, 4
+ )
+ value_data = mnemonic_data[ID_EXP_LENGTH_WORDS + 2 : -CHECKSUM_LENGTH_WORDS]
+
+ if group_count < group_threshold:
+ raise MnemonicError(
+ 'Invalid mnemonic "{} ...". Group threshold cannot be greater than group count.'.format(
+ " ".join(mnemonic.split()[: ID_EXP_LENGTH_WORDS + 2])
+ )
+ )
+
+ value_byte_count = bits_to_bytes(RADIX_BITS * len(value_data) - padding_len)
+ value_int = _int_from_indices(value_data)
+ if value_data[0] >= 1 << (RADIX_BITS - padding_len):
+ raise MnemonicError(
+ 'Invalid mnemonic padding for "{} ...".'.format(
+ " ".join(mnemonic.split()[: ID_EXP_LENGTH_WORDS + 2])
+ )
+ )
+ value = value_int.to_bytes(value_byte_count, "big")
+
+ return (
+ identifier,
+ iteration_exponent,
+ group_index,
+ group_threshold + 1,
+ group_count + 1,
+ member_index,
+ member_threshold + 1,
+ value,
+ )
+
+
+def _decode_mnemonics(mnemonics):
+ identifiers = set()
+ iteration_exponents = set()
+ group_thresholds = set()
+ group_counts = set()
+ groups = {} # { group_index : [member_threshold, set_of_member_shares] }
+ for mnemonic in mnemonics:
+ identifier, iteration_exponent, group_index, group_threshold, group_count, member_index, member_threshold, share_value = decode_mnemonic(
+ mnemonic
+ )
+ identifiers.add(identifier)
+ iteration_exponents.add(iteration_exponent)
+ group_thresholds.add(group_threshold)
+ group_counts.add(group_count)
+ group = groups.setdefault(group_index, [member_threshold, set()])
+ if group[0] != member_threshold:
+ raise MnemonicError(
+ "Invalid set of mnemonics. All mnemonics in a group must have the same member threshold."
+ )
+ group[1].add((member_index, share_value))
+
+ if len(identifiers) != 1 or len(iteration_exponents) != 1:
+ raise MnemonicError(
+ "Invalid set of mnemonics. All mnemonics must begin with the same {} words.".format(
+ ID_EXP_LENGTH_WORDS
+ )
+ )
+
+ if len(group_thresholds) != 1:
+ raise MnemonicError(
+ "Invalid set of mnemonics. All mnemonics must have the same group threshold."
+ )
+
+ if len(group_counts) != 1:
+ raise MnemonicError(
+ "Invalid set of mnemonics. All mnemonics must have the same group count."
+ )
+
+ for group_index, group in groups.items():
+ if len(set(share[0] for share in group[1])) != len(group[1]):
+ raise MnemonicError(
+ "Invalid set of shares. Member indices in each group must be unique."
+ )
+
+ return (
+ identifiers.pop(),
+ iteration_exponents.pop(),
+ group_thresholds.pop(),
+ group_counts.pop(),
+ groups,
+ )
+
+
+def _generate_random_identifier():
+ """Returns a randomly generated integer in the range 0, ... , 2**ID_LENGTH_BITS - 1."""
+
+ identifier = int.from_bytes(random.bytes(bits_to_bytes(ID_LENGTH_BITS)), "big")
+ return identifier & ((1 << ID_LENGTH_BITS) - 1)
+
+
+def generate_mnemonics(
+ group_threshold, groups, master_secret, passphrase=b"", iteration_exponent=0
+):
+ """
+ Splits a master secret into mnemonic shares using Shamir's secret sharing scheme.
+ :param int group_threshold: The number of groups required to reconstruct the master secret.
+ :param groups: A list of (member_threshold, member_count) pairs for each group, where member_count
+ is the number of shares to generate for the group and member_threshold is the number of members required to
+ reconstruct the group secret.
+ :type groups: List of pairs of integers.
+ :param master_secret: The master secret to split.
+ :type master_secret: Array of bytes.
+ :param passphrase: The passphrase used to encrypt the master secret.
+ :type passphrase: Array of bytes.
+ :param int iteration_exponent: The iteration exponent.
+ :return: List of mnemonics.
+ :rtype: List of byte arrays.
+ """
+
+ identifier = _generate_random_identifier()
+
+ if len(master_secret) * 8 < MIN_STRENGTH_BITS:
+ raise ValueError(
+ "The length of the master secret ({} bytes) must be at least {} bytes.".format(
+ len(master_secret), bits_to_bytes(MIN_STRENGTH_BITS)
+ )
+ )
+
+ if len(master_secret) % 2 != 0:
+ raise ValueError(
+ "The length of the master secret in bytes must be an even number."
+ )
+
+ if not all(32 <= c <= 126 for c in passphrase):
+ raise ValueError(
+ "The passphrase must contain only printable ASCII characters (code points 32-126)."
+ )
+
+ if group_threshold > len(groups):
+ raise ValueError(
+ "The requested group threshold ({}) must not exceed the number of groups ({}).".format(
+ group_threshold, len(groups)
+ )
+ )
+
+ if any(
+ member_threshold == 1 and member_count > 1
+ for member_threshold, member_count in groups
+ ):
+ raise ValueError(
+ "Creating multiple member shares with member threshold 1 is not allowed. Use 1-of-1 member sharing instead."
+ )
+
+ encrypted_master_secret = _encrypt(
+ master_secret, passphrase, iteration_exponent, identifier
+ )
+
+ group_shares = _split_secret(group_threshold, len(groups), encrypted_master_secret)
+
+ return [
+ [
+ encode_mnemonic(
+ identifier,
+ iteration_exponent,
+ group_index,
+ group_threshold,
+ len(groups),
+ member_index,
+ member_threshold,
+ value,
+ )
+ for member_index, value in _split_secret(
+ member_threshold, member_count, group_secret
+ )
+ ]
+ for (member_threshold, member_count), (group_index, group_secret) in zip(
+ groups, group_shares
+ )
+ ]
+
+
+def generate_mnemonics_random(
+ group_threshold, groups, strength_bits=128, passphrase=b"", iteration_exponent=0
+):
+ """
+ Generates a random master secret and splits it into mnemonic shares using Shamir's secret
+ sharing scheme.
+ :param int group_threshold: The number of groups required to reconstruct the master secret.
+ :param groups: A list of (member_threshold, member_count) pairs for each group, where member_count
+ is the number of shares to generate for the group and member_threshold is the number of members required to
+ reconstruct the group secret.
+ :type groups: List of pairs of integers.
+ :param int strength_bits: The entropy of the randomly generated master secret in bits.
+ :param passphrase: The passphrase used to encrypt the master secret.
+ :type passphrase: Array of bytes.
+ :param int iteration_exponent: The iteration exponent.
+ :return: List of mnemonics.
+ :rtype: List of byte arrays.
+ """
+
+ if strength_bits < MIN_STRENGTH_BITS:
+ raise ValueError(
+ "The requested strength of the master secret ({} bits) must be at least {} bits.".format(
+ strength_bits, MIN_STRENGTH_BITS
+ )
+ )
+
+ if strength_bits % 16 != 0:
+ raise ValueError(
+ "The requested strength of the master secret ({} bits) must be a multiple of 16 bits.".format(
+ strength_bits
+ )
+ )
+
+ return generate_mnemonics(
+ group_threshold,
+ groups,
+ random.bytes(strength_bits // 8),
+ passphrase,
+ iteration_exponent,
+ )
+
+
+def combine_mnemonics(mnemonics):
+ """
+ Combines mnemonic shares to obtain the master secret which was previously split using
+ Shamir's secret sharing scheme.
+ :param mnemonics: List of mnemonics.
+ :type mnemonics: List of byte arrays.
+ :return: Identifier, iteration exponent, the encrypted master secret.
+ :rtype: Integer, integer, array of bytes.
+ """
+
+ if not mnemonics:
+ raise MnemonicError("The list of mnemonics is empty.")
+
+ identifier, iteration_exponent, group_threshold, group_count, groups = _decode_mnemonics(
+ mnemonics
+ )
+
+ if len(groups) != group_threshold:
+ raise MnemonicError(
+ "Wrong number of mnemonic groups. Expected {} groups, but {} were provided.".format(
+ group_threshold, len(groups)
+ )
+ )
+
+ for group_index, group in groups.items():
+ if len(group[1]) != group[0]:
+ prefix = _group_prefix(
+ identifier,
+ iteration_exponent,
+ group_index,
+ group_threshold,
+ group_count,
+ )
+ raise MnemonicError(
+ 'Wrong number of mnemonics. Expected {} mnemonics starting with "{} ...", but {} were provided.'.format(
+ group[0], mnemonic_from_indices(prefix), len(group[1])
+ )
+ )
+
+ group_shares = [
+ (group_index, _recover_secret(group[0], list(group[1])))
+ for group_index, group in groups.items()
+ ]
+
+ return (
+ identifier,
+ iteration_exponent,
+ _recover_secret(group_threshold, group_shares),
+ )
diff --git a/core/src/trezor/crypto/slip39_wordlist.py b/core/src/trezor/crypto/slip39_wordlist.py
new file mode 100644
index 0000000000..2382137ab0
--- /dev/null
+++ b/core/src/trezor/crypto/slip39_wordlist.py
@@ -0,0 +1,1026 @@
+wordlist = (
+ "academic",
+ "acid",
+ "acne",
+ "acquire",
+ "acrobat",
+ "activity",
+ "actress",
+ "adapt",
+ "adequate",
+ "adjust",
+ "admit",
+ "adorn",
+ "adult",
+ "advance",
+ "advocate",
+ "afraid",
+ "again",
+ "agency",
+ "agree",
+ "aide",
+ "aircraft",
+ "airline",
+ "airport",
+ "ajar",
+ "alarm",
+ "album",
+ "alcohol",
+ "alien",
+ "alive",
+ "alpha",
+ "already",
+ "alto",
+ "aluminum",
+ "always",
+ "amazing",
+ "ambition",
+ "amount",
+ "amuse",
+ "analysis",
+ "anatomy",
+ "ancestor",
+ "ancient",
+ "angel",
+ "angry",
+ "animal",
+ "answer",
+ "antenna",
+ "anxiety",
+ "apart",
+ "aquatic",
+ "arcade",
+ "arena",
+ "argue",
+ "armed",
+ "artist",
+ "artwork",
+ "aspect",
+ "auction",
+ "august",
+ "aunt",
+ "average",
+ "aviation",
+ "avoid",
+ "award",
+ "away",
+ "axis",
+ "axle",
+ "beam",
+ "beard",
+ "beaver",
+ "become",
+ "bedroom",
+ "behavior",
+ "being",
+ "believe",
+ "belong",
+ "benefit",
+ "best",
+ "beyond",
+ "bike",
+ "biology",
+ "birthday",
+ "bishop",
+ "black",
+ "blanket",
+ "blessing",
+ "blimp",
+ "blind",
+ "blue",
+ "body",
+ "bolt",
+ "boring",
+ "born",
+ "both",
+ "boundary",
+ "bracelet",
+ "branch",
+ "brave",
+ "breathe",
+ "briefing",
+ "broken",
+ "brother",
+ "browser",
+ "bucket",
+ "budget",
+ "building",
+ "bulb",
+ "bulge",
+ "bumpy",
+ "bundle",
+ "burden",
+ "burning",
+ "busy",
+ "buyer",
+ "cage",
+ "calcium",
+ "camera",
+ "campus",
+ "canyon",
+ "capacity",
+ "capital",
+ "capture",
+ "carbon",
+ "cards",
+ "careful",
+ "cargo",
+ "carpet",
+ "carve",
+ "category",
+ "cause",
+ "ceiling",
+ "center",
+ "ceramic",
+ "champion",
+ "change",
+ "charity",
+ "check",
+ "chemical",
+ "chest",
+ "chew",
+ "chubby",
+ "cinema",
+ "civil",
+ "class",
+ "clay",
+ "cleanup",
+ "client",
+ "climate",
+ "clinic",
+ "clock",
+ "clogs",
+ "closet",
+ "clothes",
+ "club",
+ "cluster",
+ "coal",
+ "coastal",
+ "coding",
+ "column",
+ "company",
+ "corner",
+ "costume",
+ "counter",
+ "course",
+ "cover",
+ "cowboy",
+ "cradle",
+ "craft",
+ "crazy",
+ "credit",
+ "cricket",
+ "criminal",
+ "crisis",
+ "critical",
+ "crowd",
+ "crucial",
+ "crunch",
+ "crush",
+ "crystal",
+ "cubic",
+ "cultural",
+ "curious",
+ "curly",
+ "custody",
+ "cylinder",
+ "daisy",
+ "damage",
+ "dance",
+ "darkness",
+ "database",
+ "daughter",
+ "deadline",
+ "deal",
+ "debris",
+ "debut",
+ "decent",
+ "decision",
+ "declare",
+ "decorate",
+ "decrease",
+ "deliver",
+ "demand",
+ "density",
+ "deny",
+ "depart",
+ "depend",
+ "depict",
+ "deploy",
+ "describe",
+ "desert",
+ "desire",
+ "desktop",
+ "destroy",
+ "detailed",
+ "detect",
+ "device",
+ "devote",
+ "diagnose",
+ "dictate",
+ "diet",
+ "dilemma",
+ "diminish",
+ "dining",
+ "diploma",
+ "disaster",
+ "discuss",
+ "disease",
+ "dish",
+ "dismiss",
+ "display",
+ "distance",
+ "dive",
+ "divorce",
+ "document",
+ "domain",
+ "domestic",
+ "dominant",
+ "dough",
+ "downtown",
+ "dragon",
+ "dramatic",
+ "dream",
+ "dress",
+ "drift",
+ "drink",
+ "drove",
+ "drug",
+ "dryer",
+ "duckling",
+ "duke",
+ "duration",
+ "dwarf",
+ "dynamic",
+ "early",
+ "earth",
+ "easel",
+ "easy",
+ "echo",
+ "eclipse",
+ "ecology",
+ "edge",
+ "editor",
+ "educate",
+ "either",
+ "elbow",
+ "elder",
+ "election",
+ "elegant",
+ "element",
+ "elephant",
+ "elevator",
+ "elite",
+ "else",
+ "email",
+ "emerald",
+ "emission",
+ "emperor",
+ "emphasis",
+ "employer",
+ "empty",
+ "ending",
+ "endless",
+ "endorse",
+ "enemy",
+ "energy",
+ "enforce",
+ "engage",
+ "enjoy",
+ "enlarge",
+ "entrance",
+ "envelope",
+ "envy",
+ "epidemic",
+ "episode",
+ "equation",
+ "equip",
+ "eraser",
+ "erode",
+ "escape",
+ "estate",
+ "estimate",
+ "evaluate",
+ "evening",
+ "evidence",
+ "evil",
+ "evoke",
+ "exact",
+ "example",
+ "exceed",
+ "exchange",
+ "exclude",
+ "excuse",
+ "execute",
+ "exercise",
+ "exhaust",
+ "exotic",
+ "expand",
+ "expect",
+ "explain",
+ "express",
+ "extend",
+ "extra",
+ "eyebrow",
+ "facility",
+ "fact",
+ "failure",
+ "faint",
+ "fake",
+ "false",
+ "family",
+ "famous",
+ "fancy",
+ "fangs",
+ "fantasy",
+ "fatal",
+ "fatigue",
+ "favorite",
+ "fawn",
+ "fiber",
+ "fiction",
+ "filter",
+ "finance",
+ "findings",
+ "finger",
+ "firefly",
+ "firm",
+ "fiscal",
+ "fishing",
+ "fitness",
+ "flame",
+ "flash",
+ "flavor",
+ "flea",
+ "flexible",
+ "flip",
+ "float",
+ "floral",
+ "fluff",
+ "focus",
+ "forbid",
+ "force",
+ "forecast",
+ "forget",
+ "formal",
+ "fortune",
+ "forward",
+ "founder",
+ "fraction",
+ "fragment",
+ "frequent",
+ "freshman",
+ "friar",
+ "fridge",
+ "friendly",
+ "frost",
+ "froth",
+ "frozen",
+ "fumes",
+ "funding",
+ "furl",
+ "fused",
+ "galaxy",
+ "game",
+ "garbage",
+ "garden",
+ "garlic",
+ "gasoline",
+ "gather",
+ "general",
+ "genius",
+ "genre",
+ "genuine",
+ "geology",
+ "gesture",
+ "glad",
+ "glance",
+ "glasses",
+ "glen",
+ "glimpse",
+ "goat",
+ "golden",
+ "graduate",
+ "grant",
+ "grasp",
+ "gravity",
+ "gray",
+ "greatest",
+ "grief",
+ "grill",
+ "grin",
+ "grocery",
+ "gross",
+ "group",
+ "grownup",
+ "grumpy",
+ "guard",
+ "guest",
+ "guilt",
+ "guitar",
+ "gums",
+ "hairy",
+ "hamster",
+ "hand",
+ "hanger",
+ "harvest",
+ "have",
+ "havoc",
+ "hawk",
+ "hazard",
+ "headset",
+ "health",
+ "hearing",
+ "heat",
+ "helpful",
+ "herald",
+ "herd",
+ "hesitate",
+ "hobo",
+ "holiday",
+ "holy",
+ "home",
+ "hormone",
+ "hospital",
+ "hour",
+ "huge",
+ "human",
+ "humidity",
+ "hunting",
+ "husband",
+ "hush",
+ "husky",
+ "hybrid",
+ "idea",
+ "identify",
+ "idle",
+ "image",
+ "impact",
+ "imply",
+ "improve",
+ "impulse",
+ "include",
+ "income",
+ "increase",
+ "index",
+ "indicate",
+ "industry",
+ "infant",
+ "inform",
+ "inherit",
+ "injury",
+ "inmate",
+ "insect",
+ "inside",
+ "install",
+ "intend",
+ "intimate",
+ "invasion",
+ "involve",
+ "iris",
+ "island",
+ "isolate",
+ "item",
+ "ivory",
+ "jacket",
+ "jerky",
+ "jewelry",
+ "join",
+ "judicial",
+ "juice",
+ "jump",
+ "junction",
+ "junior",
+ "junk",
+ "jury",
+ "justice",
+ "kernel",
+ "keyboard",
+ "kidney",
+ "kind",
+ "kitchen",
+ "knife",
+ "knit",
+ "laden",
+ "ladle",
+ "ladybug",
+ "lair",
+ "lamp",
+ "language",
+ "large",
+ "laser",
+ "laundry",
+ "lawsuit",
+ "leader",
+ "leaf",
+ "learn",
+ "leaves",
+ "lecture",
+ "legal",
+ "legend",
+ "legs",
+ "lend",
+ "length",
+ "level",
+ "liberty",
+ "library",
+ "license",
+ "lift",
+ "likely",
+ "lilac",
+ "lily",
+ "lips",
+ "liquid",
+ "listen",
+ "literary",
+ "living",
+ "lizard",
+ "loan",
+ "lobe",
+ "location",
+ "losing",
+ "loud",
+ "loyalty",
+ "luck",
+ "lunar",
+ "lunch",
+ "lungs",
+ "luxury",
+ "lying",
+ "lyrics",
+ "machine",
+ "magazine",
+ "maiden",
+ "mailman",
+ "main",
+ "makeup",
+ "making",
+ "mama",
+ "manager",
+ "mandate",
+ "mansion",
+ "manual",
+ "marathon",
+ "march",
+ "market",
+ "marvel",
+ "mason",
+ "material",
+ "math",
+ "maximum",
+ "mayor",
+ "meaning",
+ "medal",
+ "medical",
+ "member",
+ "memory",
+ "mental",
+ "merchant",
+ "merit",
+ "method",
+ "metric",
+ "midst",
+ "mild",
+ "military",
+ "mineral",
+ "minister",
+ "miracle",
+ "mixed",
+ "mixture",
+ "mobile",
+ "modern",
+ "modify",
+ "moisture",
+ "moment",
+ "morning",
+ "mortgage",
+ "mother",
+ "mountain",
+ "mouse",
+ "move",
+ "much",
+ "mule",
+ "multiple",
+ "muscle",
+ "museum",
+ "music",
+ "mustang",
+ "nail",
+ "national",
+ "necklace",
+ "negative",
+ "nervous",
+ "network",
+ "news",
+ "nuclear",
+ "numb",
+ "numerous",
+ "nylon",
+ "oasis",
+ "obesity",
+ "object",
+ "observe",
+ "obtain",
+ "ocean",
+ "often",
+ "olympic",
+ "omit",
+ "oral",
+ "orange",
+ "orbit",
+ "order",
+ "ordinary",
+ "organize",
+ "ounce",
+ "oven",
+ "overall",
+ "owner",
+ "paces",
+ "pacific",
+ "package",
+ "paid",
+ "painting",
+ "pajamas",
+ "pancake",
+ "pants",
+ "papa",
+ "paper",
+ "parcel",
+ "parking",
+ "party",
+ "patent",
+ "patrol",
+ "payment",
+ "payroll",
+ "peaceful",
+ "peanut",
+ "peasant",
+ "pecan",
+ "penalty",
+ "pencil",
+ "percent",
+ "perfect",
+ "permit",
+ "petition",
+ "phantom",
+ "pharmacy",
+ "photo",
+ "phrase",
+ "physics",
+ "pickup",
+ "picture",
+ "piece",
+ "pile",
+ "pink",
+ "pipeline",
+ "pistol",
+ "pitch",
+ "plains",
+ "plan",
+ "plastic",
+ "platform",
+ "playoff",
+ "pleasure",
+ "plot",
+ "plunge",
+ "practice",
+ "prayer",
+ "preach",
+ "predator",
+ "pregnant",
+ "premium",
+ "prepare",
+ "presence",
+ "prevent",
+ "priest",
+ "primary",
+ "priority",
+ "prisoner",
+ "privacy",
+ "prize",
+ "problem",
+ "process",
+ "profile",
+ "program",
+ "promise",
+ "prospect",
+ "provide",
+ "prune",
+ "public",
+ "pulse",
+ "pumps",
+ "punish",
+ "puny",
+ "pupal",
+ "purchase",
+ "purple",
+ "python",
+ "quantity",
+ "quarter",
+ "quick",
+ "quiet",
+ "race",
+ "racism",
+ "radar",
+ "railroad",
+ "rainbow",
+ "raisin",
+ "random",
+ "ranked",
+ "rapids",
+ "raspy",
+ "reaction",
+ "realize",
+ "rebound",
+ "rebuild",
+ "recall",
+ "receiver",
+ "recover",
+ "regret",
+ "regular",
+ "reject",
+ "relate",
+ "remember",
+ "remind",
+ "remove",
+ "render",
+ "repair",
+ "repeat",
+ "replace",
+ "require",
+ "rescue",
+ "research",
+ "resident",
+ "response",
+ "result",
+ "retailer",
+ "retreat",
+ "reunion",
+ "revenue",
+ "review",
+ "reward",
+ "rhyme",
+ "rhythm",
+ "rich",
+ "rival",
+ "river",
+ "robin",
+ "rocky",
+ "romantic",
+ "romp",
+ "roster",
+ "round",
+ "royal",
+ "ruin",
+ "ruler",
+ "rumor",
+ "sack",
+ "safari",
+ "salary",
+ "salon",
+ "salt",
+ "satisfy",
+ "satoshi",
+ "saver",
+ "says",
+ "scandal",
+ "scared",
+ "scatter",
+ "scene",
+ "scholar",
+ "science",
+ "scout",
+ "scramble",
+ "screw",
+ "script",
+ "scroll",
+ "seafood",
+ "season",
+ "secret",
+ "security",
+ "segment",
+ "senior",
+ "shadow",
+ "shaft",
+ "shame",
+ "shaped",
+ "sharp",
+ "shelter",
+ "sheriff",
+ "short",
+ "should",
+ "shrimp",
+ "sidewalk",
+ "silent",
+ "silver",
+ "similar",
+ "simple",
+ "single",
+ "sister",
+ "skin",
+ "skunk",
+ "slap",
+ "slavery",
+ "sled",
+ "slice",
+ "slim",
+ "slow",
+ "slush",
+ "smart",
+ "smear",
+ "smell",
+ "smirk",
+ "smith",
+ "smoking",
+ "smug",
+ "snake",
+ "snapshot",
+ "sniff",
+ "society",
+ "software",
+ "soldier",
+ "solution",
+ "soul",
+ "source",
+ "space",
+ "spark",
+ "speak",
+ "species",
+ "spelling",
+ "spend",
+ "spew",
+ "spider",
+ "spill",
+ "spine",
+ "spirit",
+ "spit",
+ "spray",
+ "sprinkle",
+ "square",
+ "squeeze",
+ "stadium",
+ "staff",
+ "standard",
+ "starting",
+ "station",
+ "stay",
+ "steady",
+ "step",
+ "stick",
+ "stilt",
+ "story",
+ "strategy",
+ "strike",
+ "style",
+ "subject",
+ "submit",
+ "sugar",
+ "suitable",
+ "sunlight",
+ "superior",
+ "surface",
+ "surprise",
+ "survive",
+ "sweater",
+ "swimming",
+ "swing",
+ "switch",
+ "symbolic",
+ "sympathy",
+ "syndrome",
+ "system",
+ "tackle",
+ "tactics",
+ "tadpole",
+ "talent",
+ "task",
+ "taste",
+ "taught",
+ "taxi",
+ "teacher",
+ "teammate",
+ "teaspoon",
+ "temple",
+ "tenant",
+ "tendency",
+ "tension",
+ "terminal",
+ "testify",
+ "texture",
+ "thank",
+ "that",
+ "theater",
+ "theory",
+ "therapy",
+ "thorn",
+ "threaten",
+ "thumb",
+ "thunder",
+ "ticket",
+ "tidy",
+ "timber",
+ "timely",
+ "ting",
+ "tofu",
+ "together",
+ "tolerate",
+ "total",
+ "toxic",
+ "tracks",
+ "traffic",
+ "training",
+ "transfer",
+ "trash",
+ "traveler",
+ "treat",
+ "trend",
+ "trial",
+ "tricycle",
+ "trip",
+ "triumph",
+ "trouble",
+ "true",
+ "trust",
+ "twice",
+ "twin",
+ "type",
+ "typical",
+ "ugly",
+ "ultimate",
+ "umbrella",
+ "uncover",
+ "undergo",
+ "unfair",
+ "unfold",
+ "unhappy",
+ "union",
+ "universe",
+ "unkind",
+ "unknown",
+ "unusual",
+ "unwrap",
+ "upgrade",
+ "upstairs",
+ "username",
+ "usher",
+ "usual",
+ "valid",
+ "valuable",
+ "vampire",
+ "vanish",
+ "various",
+ "vegan",
+ "velvet",
+ "venture",
+ "verdict",
+ "verify",
+ "very",
+ "veteran",
+ "vexed",
+ "victim",
+ "video",
+ "view",
+ "vintage",
+ "violence",
+ "viral",
+ "visitor",
+ "visual",
+ "vitamins",
+ "vocal",
+ "voice",
+ "volume",
+ "voter",
+ "voting",
+ "walnut",
+ "warmth",
+ "warn",
+ "watch",
+ "wavy",
+ "wealthy",
+ "weapon",
+ "webcam",
+ "welcome",
+ "welfare",
+ "western",
+ "width",
+ "wildlife",
+ "window",
+ "wine",
+ "wireless",
+ "wisdom",
+ "withdraw",
+ "wits",
+ "wolf",
+ "woman",
+ "work",
+ "worthy",
+ "wrap",
+ "wrist",
+ "writing",
+ "wrote",
+ "year",
+ "yelp",
+ "yield",
+ "yoga",
+ "zero",
+)
diff --git a/core/tests/slip39_vectors.py b/core/tests/slip39_vectors.py
new file mode 100644
index 0000000000..1900900769
--- /dev/null
+++ b/core/tests/slip39_vectors.py
@@ -0,0 +1,282 @@
+vectors = [
+ [
+ [
+ "duckling enlarge academic academic agency result length solution fridge kidney coal piece deal husband erode duke ajar critical decision keyboard"
+ ],
+ "bb54aac4b89dc868ba37d9cc21b2cece"
+ ],
+ [
+ [
+ "duckling enlarge academic academic agency result length solution fridge kidney coal piece deal husband erode duke ajar critical decision kidney"
+ ],
+ ""
+ ],
+ [
+ [
+ "duckling enlarge academic academic email result length solution fridge kidney coal piece deal husband erode duke ajar music cargo fitness"
+ ],
+ ""
+ ],
+ [
+ [
+ "shadow pistol academic always adequate wildlife fancy gross oasis cylinder mustang wrist rescue view short owner flip making coding armed",
+ "shadow pistol academic acid actress prayer class unknown daughter sweater depict flip twice unkind craft early superior advocate guest smoking"
+ ],
+ "b43ceb7e57a0ea8766221624d01b0864"
+ ],
+ [
+ [
+ "shadow pistol academic always adequate wildlife fancy gross oasis cylinder mustang wrist rescue view short owner flip making coding armed"
+ ],
+ ""
+ ],
+ [
+ [
+ "adequate smoking academic acid debut wine petition glen cluster slow rhyme slow simple epidemic rumor junk tracks treat olympic tolerate",
+ "adequate stay academic agency agency formal party ting frequent learn upstairs remember smear leaf damage anatomy ladle market hush corner"
+ ],
+ ""
+ ],
+ [
+ [
+ "peasant leaves academic acid desert exact olympic math alive axle trial tackle drug deny decent smear dominant desert bucket remind",
+ "peasant leader academic agency cultural blessing percent network envelope medal junk primary human pumps jacket fragment payroll ticket evoke voice"
+ ],
+ ""
+ ],
+ [
+ [
+ "liberty category beard echo animal fawn temple briefing math username various wolf aviation fancy visual holy thunder yelp helpful payment",
+ "liberty category beard email beyond should fancy romp founder easel pink holy hairy romp loyalty material victim owner toxic custody",
+ "liberty category academic easy being hazard crush diminish oral lizard reaction cluster force dilemma deploy force club veteran expect photo"
+ ],
+ ""
+ ],
+ [
+ [
+ "average senior academic leaf broken teacher expect surface hour capture obesity desire negative dynamic dominant pistol mineral mailman iris aide",
+ "average senior academic agency curious pants blimp spew clothes slice script dress wrap firm shaft regular slavery negative theater roster"
+ ],
+ ""
+ ],
+ [
+ [
+ "music husband acrobat acid artist finance center either graduate swimming object bike medical clothes station aspect spider maiden bulb welcome",
+ "music husband acrobat agency advance hunting bike corner density careful material civil evil tactics remind hawk discuss hobo voice rainbow",
+ "music husband beard academic black tricycle clock mayor estimate level photo episode exclude ecology papa source amazing salt verify divorce"
+ ],
+ ""
+ ],
+ [
+ [
+ "device stay academic always dive coal antenna adult black exceed stadium herald advance soldier busy dryer daughter evaluate minister laser",
+ "device stay academic always dwarf afraid robin gravity crunch adjust soul branch walnut coastal dream costume scholar mortgage mountain pumps"
+ ],
+ ""
+ ],
+ [
+ [
+ "hour painting academic academic device formal evoke guitar random modern justice filter withdraw trouble identify mailman insect general cover oven",
+ "hour painting academic agency artist again daisy capital beaver fiber much enjoy suitable symbolic identify photo editor romp float echo"
+ ],
+ ""
+ ],
+ [
+ [
+ "guilt walnut academic acid deliver remove equip listen vampire tactics nylon rhythm failure husband fatigue alive blind enemy teaspoon rebound",
+ "guilt walnut academic agency brave hamster hobo declare herd taste alpha slim criminal mild arcade formal romp branch pink ambition"
+ ],
+ ""
+ ],
+ [
+ [
+ "eraser senior beard romp adorn nuclear spill corner cradle style ancient family general leader ambition exchange unusual garlic promise voice"
+ ],
+ ""
+ ],
+ [
+ [
+ "eraser senior decision scared cargo theory device idea deliver modify curly include pancake both news skin realize vitamins away join",
+ "eraser senior decision roster beard treat identify grumpy salt index fake aviation theater cubic bike cause research dragon emphasis counter"
+ ],
+ ""
+ ],
+ [
+ [
+ "eraser senior decision shadow artist work morning estate greatest pipeline plan ting petition forget hormone flexible general goat admit surface",
+ "eraser senior beard romp adorn nuclear spill corner cradle style ancient family general leader ambition exchange unusual garlic promise voice"
+ ],
+ ""
+ ],
+ [
+ [
+ "eraser senior decision roster beard treat identify grumpy salt index fake aviation theater cubic bike cause research dragon emphasis counter",
+ "eraser senior ceramic snake clay various huge numb argue hesitate auction category timber browser greatest hanger petition script leaf pickup",
+ "eraser senior ceramic shaft dynamic become junior wrist silver peasant force math alto coal amazing segment yelp velvet image paces",
+ "eraser senior ceramic round column hawk trust auction smug shame alive greatest sheriff living perfect corner chest sled fumes adequate",
+ "eraser senior decision smug corner ruin rescue cubic angel tackle skin skunk program roster trash rumor slush angel flea amazing"
+ ],
+ "7c3397a292a5941682d7a4ae2d898d11"
+ ],
+ [
+ [
+ "eraser senior decision smug corner ruin rescue cubic angel tackle skin skunk program roster trash rumor slush angel flea amazing",
+ "eraser senior beard romp adorn nuclear spill corner cradle style ancient family general leader ambition exchange unusual garlic promise voice",
+ "eraser senior decision scared cargo theory device idea deliver modify curly include pancake both news skin realize vitamins away join"
+ ],
+ "7c3397a292a5941682d7a4ae2d898d11"
+ ],
+ [
+ [
+ "eraser senior beard romp adorn nuclear spill corner cradle style ancient family general leader ambition exchange unusual garlic promise voice",
+ "eraser senior acrobat romp bishop medical gesture pumps secret alive ultimate quarter priest subject class dictate spew material endless market"
+ ],
+ "7c3397a292a5941682d7a4ae2d898d11"
+ ],
+ [
+ [
+ "theory painting academic academic armed sweater year military elder discuss acne wildlife boring employer fused large satoshi bundle carbon diagnose anatomy hamster leaves tracks paces beyond phantom capital marvel lips brave detect luck"
+ ],
+ "989baf9dcaad5b10ca33dfd8cc75e42477025dce88ae83e75a230086a0e00e92"
+ ],
+ [
+ [
+ "theory painting academic academic armed sweater year military elder discuss acne wildlife boring employer fused large satoshi bundle carbon diagnose anatomy hamster leaves tracks paces beyond phantom capital marvel lips brave detect lunar"
+ ],
+ ""
+ ],
+ [
+ [
+ "theory painting academic academic campus sweater year military elder discuss acne wildlife boring employer fused large satoshi bundle carbon diagnose anatomy hamster leaves tracks paces beyond phantom capital marvel lips facility obtain sister"
+ ],
+ ""
+ ],
+ [
+ [
+ "humidity disease academic always aluminum jewelry energy woman receiver strategy amuse duckling lying evidence network walnut tactics forget hairy rebound impulse brother survive clothes stadium mailman rival ocean reward venture always armed unwrap",
+ "humidity disease academic agency actress jacket gross physics cylinder solution fake mortgage benefit public busy prepare sharp friar change work slow purchase ruler again tricycle involve viral wireless mixture anatomy desert cargo upgrade"
+ ],
+ "c938b319067687e990e05e0da0ecce1278f75ff58d9853f19dcaeed5de104aae"
+ ],
+ [
+ [
+ "humidity disease academic always aluminum jewelry energy woman receiver strategy amuse duckling lying evidence network walnut tactics forget hairy rebound impulse brother survive clothes stadium mailman rival ocean reward venture always armed unwrap"
+ ],
+ ""
+ ],
+ [
+ [
+ "smear husband academic acid deadline scene venture distance dive overall parking bracelet elevator justice echo burning oven chest duke nylon",
+ "smear isolate academic agency alpha mandate decorate burden recover guard exercise fatal force syndrome fumes thank guest drift dramatic mule"
+ ],
+ ""
+ ],
+ [
+ [
+ "finger trash academic acid average priority dish revenue academic hospital spirit western ocean fact calcium syndrome greatest plan losing dictate",
+ "finger traffic academic agency building lilac deny paces subject threaten diploma eclipse window unknown health slim piece dragon focus smirk"
+ ],
+ ""
+ ],
+ [
+ [
+ "flavor pink beard echo depart forbid retreat become frost helpful juice unwrap reunion credit math burning spine black capital lair",
+ "flavor pink beard email diet teaspoon freshman identify document rebound cricket prune headset loyalty smell emission skin often square rebound",
+ "flavor pink academic easy credit cage raisin crazy closet lobe mobile become drink human tactics valuable hand capture sympathy finger"
+ ],
+ ""
+ ],
+ [
+ [
+ "column flea academic leaf debut extra surface slow timber husky lawsuit game behavior husky swimming already paper episode tricycle scroll",
+ "column flea academic agency blessing garbage party software stadium verify silent umbrella therapy decorate chemical erode dramatic eclipse replace apart"
+ ],
+ ""
+ ],
+ [
+ [
+ "smirk pink acrobat acid auction wireless impulse spine sprinkle fortune clogs elbow guest hush loyalty crush dictate tracks airport talent",
+ "smirk pink acrobat agency dwarf emperor ajar organize legs slice harvest plastic dynamic style mobile float bulb health coding credit",
+ "smirk pink beard academic alto strategy carve shame language rapids ruin smart location spray training acquire eraser endorse submit peaceful"
+ ],
+ ""
+ ],
+ [
+ [
+ "fishing recover academic always device craft trend snapshot gums skin downtown watch device sniff hour clock public maximum garlic born",
+ "fishing recover academic always aircraft view software cradle fangs amazing package plastic evaluate intend penalty epidemic anatomy quarter cage apart"
+ ],
+ ""
+ ],
+ [
+ [
+ "evoke garden academic academic answer wolf scandal modern warmth station devote emerald market physics surface formal amazing aquatic gesture medical",
+ "evoke garden academic agency deal revenue knit reunion decrease magazine flexible company goat repair alarm military facility clogs aide mandate"
+ ],
+ ""
+ ],
+ [
+ [
+ "river deal academic acid average forbid pistol peanut custody bike class aunt hairy merit valid flexible learn ajar very easel",
+ "river deal academic agency camera amuse lungs numb isolate display smear piece traffic worthy year patrol crush fact fancy emission"
+ ],
+ ""
+ ],
+ [
+ [
+ "wildlife deal beard romp alcohol space mild usual clothes union nuclear testify course research heat listen task location thank hospital slice smell failure fawn helpful priest ambition average recover lecture process dough stadium"
+ ],
+ ""
+ ],
+ [
+ [
+ "wildlife deal decision scared acne fatal snake paces obtain election dryer dominant romp tactics railroad marvel trust helpful flip peanut theory theater photo luck install entrance taxi step oven network dictate intimate listen",
+ "wildlife deal decision smug ancestor genuine move huge cubic strategy smell game costume extend swimming false desire fake traffic vegan senior twice timber submit leader payroll fraction apart exact forward pulse tidy install"
+ ],
+ ""
+ ],
+ [
+ [
+ "wildlife deal decision shadow analysis adjust bulb skunk muscle mandate obesity total guitar coal gravity carve slim jacket ruin rebuild ancestor numerous hour mortgage require herd maiden public ceiling pecan pickup shadow club",
+ "wildlife deal beard romp alcohol space mild usual clothes union nuclear testify course research heat listen task location thank hospital slice smell failure fawn helpful priest ambition average recover lecture process dough stadium"
+ ],
+ ""
+ ],
+ [
+ [
+ "wildlife deal ceramic round aluminum pitch goat racism employer miracle percent math decision episode dramatic editor lily prospect program scene rebuild display sympathy have single mustang junction relate often chemical society wits estate",
+ "wildlife deal decision scared acne fatal snake paces obtain election dryer dominant romp tactics railroad marvel trust helpful flip peanut theory theater photo luck install entrance taxi step oven network dictate intimate listen",
+ "wildlife deal ceramic scatter argue equip vampire together ruin reject literary rival distance aquatic agency teammate rebound false argue miracle stay again blessing peaceful unknown cover beard acid island language debris industry idle",
+ "wildlife deal ceramic snake agree voter main lecture axis kitchen physics arcade velvet spine idea scroll promise platform firm sharp patrol divorce ancestor fantasy forbid goat ajar believe swimming cowboy symbolic plastic spelling",
+ "wildlife deal decision shadow analysis adjust bulb skunk muscle mandate obesity total guitar coal gravity carve slim jacket ruin rebuild ancestor numerous hour mortgage require herd maiden public ceiling pecan pickup shadow club"
+ ],
+ "5385577c8cfc6c1a8aa0f7f10ecde0a3318493262591e78b8c14c6686167123b"
+ ],
+ [
+ [
+ "wildlife deal decision scared acne fatal snake paces obtain election dryer dominant romp tactics railroad marvel trust helpful flip peanut theory theater photo luck install entrance taxi step oven network dictate intimate listen",
+ "wildlife deal beard romp alcohol space mild usual clothes union nuclear testify course research heat listen task location thank hospital slice smell failure fawn helpful priest ambition average recover lecture process dough stadium",
+ "wildlife deal decision smug ancestor genuine move huge cubic strategy smell game costume extend swimming false desire fake traffic vegan senior twice timber submit leader payroll fraction apart exact forward pulse tidy install"
+ ],
+ "5385577c8cfc6c1a8aa0f7f10ecde0a3318493262591e78b8c14c6686167123b"
+ ],
+ [
+ [
+ "wildlife deal beard romp alcohol space mild usual clothes union nuclear testify course research heat listen task location thank hospital slice smell failure fawn helpful priest ambition average recover lecture process dough stadium",
+ "wildlife deal acrobat romp anxiety axis starting require metric flexible geology game drove editor edge screw helpful have huge holy making pitch unknown carve holiday numb glasses survive already tenant adapt goat fangs"
+ ],
+ "5385577c8cfc6c1a8aa0f7f10ecde0a3318493262591e78b8c14c6686167123b"
+ ],
+ [
+ [
+ "junk necklace academic academic acne isolate join hesitate lunar roster dough calcium chemical ladybug amount mobile glasses verify cylinder"
+ ],
+ ""
+ ],
+ [
+ [
+ "fraction necklace academic academic award teammate mouse regular testify coding building member verdict purchase blind camera duration email prepare spirit quarter"
+ ],
+ ""
+ ]
+]
diff --git a/core/tests/test_trezor.crypto.slip39.py b/core/tests/test_trezor.crypto.slip39.py
new file mode 100644
index 0000000000..46db39e00d
--- /dev/null
+++ b/core/tests/test_trezor.crypto.slip39.py
@@ -0,0 +1,133 @@
+from common import *
+from trezor.crypto import slip39, random
+from slip39_vectors import vectors
+
+def combinations(iterable, r):
+ # Taken from https://docs.python.org/3.7/library/itertools.html#itertools.combinations
+ pool = tuple(iterable)
+ n = len(pool)
+ if r > n:
+ return
+ indices = list(range(r))
+ yield tuple(pool[i] for i in indices)
+ while True:
+ for i in reversed(range(r)):
+ if indices[i] != i + n - r:
+ break
+ else:
+ return
+ indices[i] += 1
+ for j in range(i+1, r):
+ indices[j] = indices[j-1] + 1
+ yield tuple(pool[i] for i in indices)
+
+class TestCryptoSlip39(unittest.TestCase):
+ MS = b"ABCDEFGHIJKLMNOP"
+
+ def test_basic_sharing_random(self):
+ mnemonics = slip39.generate_mnemonics_random(1, [(3, 5)])[0]
+ self.assertEqual(slip39.combine_mnemonics(mnemonics[:3]), slip39.combine_mnemonics(mnemonics[2:]))
+
+
+ def test_basic_sharing_fixed(self):
+ mnemonics = slip39.generate_mnemonics(1, [(3, 5)], self.MS)[0]
+ identifier, exponent, ems = slip39.combine_mnemonics(mnemonics[:3])
+ self.assertEqual(slip39.decrypt(identifier, exponent, ems, b""), self.MS)
+ self.assertEqual(slip39.combine_mnemonics(mnemonics[1:4])[2], ems)
+ with self.assertRaises(slip39.MnemonicError):
+ slip39.combine_mnemonics(mnemonics[1:3])
+
+
+ def test_passphrase(self):
+ mnemonics = slip39.generate_mnemonics(1, [(3, 5)], self.MS, b"TREZOR")[0]
+ identifier, exponent, ems = slip39.combine_mnemonics(mnemonics[1:4])
+ self.assertEqual(slip39.decrypt(identifier, exponent, ems, b"TREZOR"), self.MS)
+ self.assertNotEqual(slip39.decrypt(identifier, exponent, ems, b""), self.MS)
+
+
+ def test_iteration_exponent(self):
+ mnemonics = slip39.generate_mnemonics(1, [(3, 5)], self.MS, b"TREZOR", 1)[0]
+ identifier, exponent, ems = slip39.combine_mnemonics(mnemonics[1:4])
+ self.assertEqual(slip39.decrypt(identifier, exponent, ems, b"TREZOR"), self.MS)
+ self.assertNotEqual(slip39.decrypt(identifier, exponent, ems, b""), self.MS)
+
+ mnemonics = slip39.generate_mnemonics(1, [(3, 5)], self.MS, b"TREZOR", 2)[0]
+ identifier, exponent, ems = slip39.combine_mnemonics(mnemonics[1:4])
+ self.assertEqual(slip39.decrypt(identifier, exponent, ems, b"TREZOR"), self.MS)
+ self.assertNotEqual(slip39.decrypt(identifier, exponent, ems, b""), self.MS)
+
+
+ def test_group_sharing(self):
+ group_threshold = 2
+ group_sizes = (5, 3, 5, 1)
+ member_thresholds = (3, 2, 2, 1)
+ mnemonics = slip39.generate_mnemonics(
+ group_threshold, list(zip(member_thresholds, group_sizes)), self.MS
+ )
+
+ # Test all valid combinations of mnemonics.
+ for groups in combinations(zip(mnemonics, member_thresholds), group_threshold):
+ for group1_subset in combinations(groups[0][0], groups[0][1]):
+ for group2_subset in combinations(groups[1][0], groups[1][1]):
+ mnemonic_subset = list(group1_subset + group2_subset)
+ random.shuffle(mnemonic_subset)
+ identifier, exponent, ems = slip39.combine_mnemonics(mnemonic_subset)
+ self.assertEqual(slip39.decrypt(identifier, exponent, ems, b""), self.MS)
+
+
+ # Minimal sets of mnemonics.
+ identifier, exponent, ems = slip39.combine_mnemonics([mnemonics[2][0], mnemonics[2][2], mnemonics[3][0]])
+ self.assertEqual(slip39.decrypt(identifier, exponent, ems, b""), self.MS)
+ self.assertEqual(slip39.combine_mnemonics([mnemonics[2][3], mnemonics[3][0], mnemonics[2][4]])[2], ems)
+
+ # One complete group and one incomplete group out of two groups required.
+ with self.assertRaises(slip39.MnemonicError):
+ slip39.combine_mnemonics(mnemonics[0][2:] + [mnemonics[1][0]])
+
+ # One group of two required.
+ with self.assertRaises(slip39.MnemonicError):
+ slip39.combine_mnemonics(mnemonics[0][1:4])
+
+
+ def test_invalid_sharing(self):
+ # Short master secret.
+ with self.assertRaises(ValueError):
+ slip39.generate_mnemonics(1, [(2, 3)], self.MS[:14])
+
+ # Odd length master secret.
+ with self.assertRaises(ValueError):
+ slip39.generate_mnemonics(1, [(2, 3)], self.MS + b"X")
+
+ # Group threshold exceeds number of groups.
+ with self.assertRaises(ValueError):
+ slip39.generate_mnemonics(3, [(3, 5), (2, 5)], self.MS)
+
+ # Invalid group threshold.
+ with self.assertRaises(ValueError):
+ slip39.generate_mnemonics(0, [(3, 5), (2, 5)], self.MS)
+
+ # Member threshold exceeds number of members.
+ with self.assertRaises(ValueError):
+ slip39.generate_mnemonics(2, [(3, 2), (2, 5)], self.MS)
+
+ # Invalid member threshold.
+ with self.assertRaises(ValueError):
+ slip39.generate_mnemonics(2, [(0, 2), (2, 5)], self.MS)
+
+ # Group with multiple members and threshold 1.
+ with self.assertRaises(ValueError):
+ slip39.generate_mnemonics(2, [(3, 5), (1, 3), (2, 5)], self.MS)
+
+
+ def test_vectors(self):
+ for mnemonics, secret in vectors:
+ if secret:
+ identifier, exponent, ems = slip39.combine_mnemonics(mnemonics)
+ self.assertEqual(slip39.decrypt(identifier, exponent, ems, b"TREZOR"), unhexlify(secret))
+ else:
+ with self.assertRaises(slip39.MnemonicError):
+ slip39.combine_mnemonics(mnemonics)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/crypto/Makefile b/crypto/Makefile
index 8dd944be43..5739d1737b 100644
--- a/crypto/Makefile
+++ b/crypto/Makefile
@@ -64,6 +64,7 @@ SRCS += nem.c
SRCS += segwit_addr.c cash_addr.c
SRCS += memzero.c
SRCS += schnorr.c
+SRCS += shamir.c
OBJS = $(SRCS:.c=.o)
diff --git a/crypto/shamir.c b/crypto/shamir.c
new file mode 100644
index 0000000000..b3017bad49
--- /dev/null
+++ b/crypto/shamir.c
@@ -0,0 +1,333 @@
+/*
+ * Implementation of the hazardous parts of the SSS library
+ *
+ * Copyright (c) 2017 Daan Sprenkels
+ * Copyright (c) 2019 SatoshiLabs
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
+ * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * This code contains the actual Shamir secret sharing functionality. The
+ * implementation of this code is based on the idea that the user likes to
+ * generate/combine 32 shares (in GF(2^8)) at the same time, because a 256 bit
+ * key will be exactly 32 bytes. Therefore we bitslice all the input and
+ * unbitslice the output right before returning.
+ *
+ * This bitslice approach optimizes natively on all architectures that are 32
+ * bit or more. Care is taken to use not too many registers, to ensure that no
+ * values have to be leaked to the stack.
+ *
+ * All functions in this module are implemented constant time and constant
+ * lookup operations, as all proper crypto code must be.
+ */
+
+#include
+#include "memzero.h"
+#include "shamir.h"
+
+static void bitslice(uint32_t r[8], const uint8_t *x, size_t len) {
+ size_t bit_idx, arr_idx;
+ uint32_t cur;
+
+ memset(r, 0, sizeof(uint32_t[8]));
+ for (arr_idx = 0; arr_idx < len; arr_idx++) {
+ cur = (uint32_t)x[arr_idx];
+ for (bit_idx = 0; bit_idx < 8; bit_idx++) {
+ r[bit_idx] |= ((cur & (1 << bit_idx)) >> bit_idx) << arr_idx;
+ }
+ }
+}
+
+static void unbitslice(uint8_t *r, const uint32_t x[8], size_t len) {
+ size_t bit_idx, arr_idx;
+ uint32_t cur;
+
+ memset(r, 0, sizeof(uint8_t) * len);
+ for (bit_idx = 0; bit_idx < 8; bit_idx++) {
+ cur = (uint32_t)x[bit_idx];
+ for (arr_idx = 0; arr_idx < len; arr_idx++) {
+ r[arr_idx] |= ((cur & (1 << arr_idx)) >> arr_idx) << bit_idx;
+ }
+ }
+}
+
+static void bitslice_setall(uint32_t r[8], const uint8_t x) {
+ size_t idx;
+ for (idx = 0; idx < 8; idx++) {
+ r[idx] = -((x >> idx) & 1);
+ }
+}
+
+/*
+ * Add (XOR) `r` with `x` and store the result in `r`.
+ */
+static void gf256_add(uint32_t r[8], const uint32_t x[8]) {
+ size_t idx;
+ for (idx = 0; idx < 8; idx++) r[idx] ^= x[idx];
+}
+
+/*
+ * Safely multiply two bitsliced polynomials in GF(2^8) reduced by
+ * x^8 + x^4 + x^3 + x + 1. `r` and `a` may overlap, but overlapping of `r`
+ * and `b` will produce an incorrect result! If you need to square a polynomial
+ * use `gf256_square` instead.
+ */
+static void gf256_mul(uint32_t r[8], const uint32_t a[8], const uint32_t b[8]) {
+ /* This function implements Russian Peasant multiplication on two
+ * bitsliced polynomials.
+ *
+ * I personally think that these kinds of long lists of operations
+ * are often a bit ugly. A double for loop would be nicer and would
+ * take up a lot less lines of code.
+ * However, some compilers seem to fail in optimizing these kinds of
+ * loops. So we will just have to do this by hand.
+ */
+ uint32_t a2[8];
+ memcpy(a2, a, sizeof(uint32_t[8]));
+
+ r[0] = a2[0] & b[0]; /* add (assignment, because r is 0) */
+ r[1] = a2[1] & b[0];
+ r[2] = a2[2] & b[0];
+ r[3] = a2[3] & b[0];
+ r[4] = a2[4] & b[0];
+ r[5] = a2[5] & b[0];
+ r[6] = a2[6] & b[0];
+ r[7] = a2[7] & b[0];
+ a2[0] ^= a2[7]; /* reduce */
+ a2[2] ^= a2[7];
+ a2[3] ^= a2[7];
+
+ r[0] ^= a2[7] & b[1]; /* add */
+ r[1] ^= a2[0] & b[1];
+ r[2] ^= a2[1] & b[1];
+ r[3] ^= a2[2] & b[1];
+ r[4] ^= a2[3] & b[1];
+ r[5] ^= a2[4] & b[1];
+ r[6] ^= a2[5] & b[1];
+ r[7] ^= a2[6] & b[1];
+ a2[7] ^= a2[6]; /* reduce */
+ a2[1] ^= a2[6];
+ a2[2] ^= a2[6];
+
+ r[0] ^= a2[6] & b[2]; /* add */
+ r[1] ^= a2[7] & b[2];
+ r[2] ^= a2[0] & b[2];
+ r[3] ^= a2[1] & b[2];
+ r[4] ^= a2[2] & b[2];
+ r[5] ^= a2[3] & b[2];
+ r[6] ^= a2[4] & b[2];
+ r[7] ^= a2[5] & b[2];
+ a2[6] ^= a2[5]; /* reduce */
+ a2[0] ^= a2[5];
+ a2[1] ^= a2[5];
+
+ r[0] ^= a2[5] & b[3]; /* add */
+ r[1] ^= a2[6] & b[3];
+ r[2] ^= a2[7] & b[3];
+ r[3] ^= a2[0] & b[3];
+ r[4] ^= a2[1] & b[3];
+ r[5] ^= a2[2] & b[3];
+ r[6] ^= a2[3] & b[3];
+ r[7] ^= a2[4] & b[3];
+ a2[5] ^= a2[4]; /* reduce */
+ a2[7] ^= a2[4];
+ a2[0] ^= a2[4];
+
+ r[0] ^= a2[4] & b[4]; /* add */
+ r[1] ^= a2[5] & b[4];
+ r[2] ^= a2[6] & b[4];
+ r[3] ^= a2[7] & b[4];
+ r[4] ^= a2[0] & b[4];
+ r[5] ^= a2[1] & b[4];
+ r[6] ^= a2[2] & b[4];
+ r[7] ^= a2[3] & b[4];
+ a2[4] ^= a2[3]; /* reduce */
+ a2[6] ^= a2[3];
+ a2[7] ^= a2[3];
+
+ r[0] ^= a2[3] & b[5]; /* add */
+ r[1] ^= a2[4] & b[5];
+ r[2] ^= a2[5] & b[5];
+ r[3] ^= a2[6] & b[5];
+ r[4] ^= a2[7] & b[5];
+ r[5] ^= a2[0] & b[5];
+ r[6] ^= a2[1] & b[5];
+ r[7] ^= a2[2] & b[5];
+ a2[3] ^= a2[2]; /* reduce */
+ a2[5] ^= a2[2];
+ a2[6] ^= a2[2];
+
+ r[0] ^= a2[2] & b[6]; /* add */
+ r[1] ^= a2[3] & b[6];
+ r[2] ^= a2[4] & b[6];
+ r[3] ^= a2[5] & b[6];
+ r[4] ^= a2[6] & b[6];
+ r[5] ^= a2[7] & b[6];
+ r[6] ^= a2[0] & b[6];
+ r[7] ^= a2[1] & b[6];
+ a2[2] ^= a2[1]; /* reduce */
+ a2[4] ^= a2[1];
+ a2[5] ^= a2[1];
+
+ r[0] ^= a2[1] & b[7]; /* add */
+ r[1] ^= a2[2] & b[7];
+ r[2] ^= a2[3] & b[7];
+ r[3] ^= a2[4] & b[7];
+ r[4] ^= a2[5] & b[7];
+ r[5] ^= a2[6] & b[7];
+ r[6] ^= a2[7] & b[7];
+ r[7] ^= a2[0] & b[7];
+
+ memzero(a2, sizeof(a2));
+}
+
+/*
+ * Square `x` in GF(2^8) and write the result to `r`. `r` and `x` may overlap.
+ */
+static void gf256_square(uint32_t r[8], const uint32_t x[8]) {
+ uint32_t r8, r10, r12, r14;
+ /* Use the Freshman's Dream rule to square the polynomial
+ * Assignments are done from 7 downto 0, because this allows the user
+ * to execute this function in-place (e.g. `gf256_square(r, r);`).
+ */
+ r14 = x[7];
+ r12 = x[6];
+ r10 = x[5];
+ r8 = x[4];
+ r[6] = x[3];
+ r[4] = x[2];
+ r[2] = x[1];
+ r[0] = x[0];
+
+ /* Reduce with x^8 + x^4 + x^3 + x + 1 until order is less than 8 */
+ r[7] = r14; /* r[7] was 0 */
+ r[6] ^= r14;
+ r10 ^= r14;
+ /* Skip, because r13 is always 0 */
+ r[4] ^= r12;
+ r[5] = r12; /* r[5] was 0 */
+ r[7] ^= r12;
+ r8 ^= r12;
+ /* Skip, because r11 is always 0 */
+ r[2] ^= r10;
+ r[3] = r10; /* r[3] was 0 */
+ r[5] ^= r10;
+ r[6] ^= r10;
+ r[1] = r14; /* r[1] was 0 */
+ r[2] ^= r14; /* Substitute r9 by r14 because they will always be equal*/
+ r[4] ^= r14;
+ r[5] ^= r14;
+ r[0] ^= r8;
+ r[1] ^= r8;
+ r[3] ^= r8;
+ r[4] ^= r8;
+}
+
+/*
+ * Invert `x` in GF(2^8) and write the result to `r`
+ */
+static void gf256_inv(uint32_t r[8], uint32_t x[8]) {
+ uint32_t y[8], z[8];
+
+ gf256_square(y, x); // y = x^2
+ gf256_square(y, y); // y = x^4
+ gf256_square(r, y); // r = x^8
+ gf256_mul(z, r, x); // z = x^9
+ gf256_square(r, r); // r = x^16
+ gf256_mul(r, r, z); // r = x^25
+ gf256_square(r, r); // r = x^50
+ gf256_square(z, r); // z = x^100
+ gf256_square(z, z); // z = x^200
+ gf256_mul(r, r, z); // r = x^250
+ gf256_mul(r, r, y); // r = x^254
+
+ memzero(y, sizeof(y));
+ memzero(z, sizeof(z));
+}
+
+bool shamir_interpolate(uint8_t *result, uint8_t result_index,
+ const uint8_t *share_indices,
+ const uint8_t **share_values, uint8_t share_count,
+ size_t len) {
+ size_t i, j;
+ uint32_t x[8];
+ uint32_t xs[share_count][8];
+ uint32_t ys[share_count][8];
+ uint32_t num[8] = {~0}; /* num is the numerator (=1) */
+ uint32_t denom[8];
+ uint32_t tmp[8];
+ uint32_t secret[8] = {0};
+ bool ret = true;
+
+ if (len > SHAMIR_MAX_LEN) return false;
+
+ /* Collect the x and y values */
+ for (i = 0; i < share_count; i++) {
+ bitslice_setall(xs[i], share_indices[i]);
+ bitslice(ys[i], share_values[i], len);
+ }
+ bitslice_setall(x, result_index);
+
+ for (i = 0; i < share_count; i++) {
+ memcpy(tmp, x, sizeof(uint32_t[8]));
+ gf256_add(tmp, xs[i]);
+ gf256_mul(num, num, tmp);
+ }
+
+ /* Use Lagrange basis polynomials to calculate the secret coefficient */
+ for (i = 0; i < share_count; i++) {
+ /* The code below assumes that none of the share_indices are equal to
+ * result_index. We need to treat that as a special case. */
+ if (share_indices[i] != result_index) {
+ memcpy(denom, x, sizeof(denom));
+ gf256_add(denom, xs[i]);
+ } else {
+ bitslice_setall(denom, 1);
+ gf256_add(secret, ys[i]);
+ }
+ for (j = 0; j < share_count; j++) {
+ if (i == j) continue;
+ memcpy(tmp, xs[i], sizeof(uint32_t[8]));
+ gf256_add(tmp, xs[j]);
+ gf256_mul(denom, denom, tmp);
+ }
+ if ((denom[0] | denom[1] | denom[2] | denom[3] | denom[4] | denom[5] |
+ denom[6] | denom[7]) == 0) {
+ /* The share_indices are not unique. */
+ ret = false;
+ break;
+ }
+ gf256_inv(tmp, denom); /* inverted denominator */
+ gf256_mul(tmp, tmp, num); /* basis polynomial */
+ gf256_mul(tmp, tmp, ys[i]); /* scaled coefficient */
+ gf256_add(secret, tmp);
+ }
+
+ if (ret == true) {
+ unbitslice(result, secret, len);
+ }
+
+ memzero(x, sizeof(x));
+ memzero(xs, sizeof(xs));
+ memzero(ys, sizeof(ys));
+ memzero(num, sizeof(num));
+ memzero(denom, sizeof(denom));
+ memzero(tmp, sizeof(tmp));
+ memzero(secret, sizeof(secret));
+ return ret;
+}
diff --git a/crypto/shamir.h b/crypto/shamir.h
new file mode 100644
index 0000000000..ef04a30636
--- /dev/null
+++ b/crypto/shamir.h
@@ -0,0 +1,70 @@
+/*
+ * Low level API for Daan Sprenkels' Shamir secret sharing library
+ * Copyright (c) 2017 Daan Sprenkels
+ * Copyright (c) 2019 SatoshiLabs
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
+ * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Usage of this API is hazardous and is only reserved for beings with a
+ * good understanding of the Shamir secret sharing scheme and who know how
+ * crypto code is implemented. If you are unsure about this, use the
+ * intermediate level API. You have been warned!
+ */
+
+#ifndef __SHAMIR_H__
+#define __SHAMIR_H__
+
+#include
+#include
+#include
+
+#define SHAMIR_MAX_LEN 32
+
+/*
+ * Computes f(x) given the Shamir shares (x_1, f(x_1)), ... , (x_m, f(x_m)).
+ * The x coordinates of the shares must be pairwise distinct. Returns true on
+ * success, otherwise false.
+ * result: Array of length len where the evaluations of the polynomials in x
+ * will be written.
+ * result_index: The x coordinate of the result.
+ * share_indices: Points to the array of integers x_1, ... , x_m.
+ * share_values: Points to the array of y_1, ... , y_m, where each y_i is an
+ * array of bytes of length len representing the evaluations of the
+ * polynomials in x_i.
+ * share_count: The number of shares m.
+ * len: The length of the result array and of each of the y_1, ... , y_m arrays.
+ *
+ * The number of shares used to compute the result may be larger than the
+ * required threshold.
+ *
+ * This function does *not* do *any* checking for integrity. If any of the
+ * shares are not original, this will result in an invalid restored value.
+ * All values written to `result` should be treated as secret. Even if some of
+ * the shares that were provided as input were incorrect, the result *still*
+ * allows an attacker to gain information about the correct result.
+ *
+ * This function treats `shares_values`, `share_indices` and `result` as secret
+ * values. `share_count` is treated as a public value (for performance reasons).
+ */
+bool shamir_interpolate(uint8_t *result, uint8_t result_index,
+ const uint8_t *share_indices,
+ const uint8_t **share_values, uint8_t share_count,
+ size_t len);
+
+#endif /* __SHAMIR_H__ */
diff --git a/crypto/tests/test_check.c b/crypto/tests/test_check.c
index 310ce808dc..4327d9c8e1 100644
--- a/crypto/tests/test_check.c
+++ b/crypto/tests/test_check.c
@@ -66,6 +66,7 @@
#include "secp256k1.h"
#include "sha2.h"
#include "sha3.h"
+#include "shamir.h"
#if VALGRIND
/*
@@ -5058,6 +5059,118 @@ START_TEST(test_mnemonic_to_entropy) {
}
END_TEST
+START_TEST(test_shamir) {
+#define SHAMIR_MAX_COUNT 16
+ static const struct {
+ const uint8_t result[SHAMIR_MAX_LEN];
+ uint8_t result_index;
+ const uint8_t share_indices[SHAMIR_MAX_COUNT];
+ const uint8_t share_values[SHAMIR_MAX_COUNT][SHAMIR_MAX_LEN];
+ uint8_t share_count;
+ size_t len;
+ bool ret;
+ } vectors[] = {{{7, 151, 168, 57, 186, 104, 218, 21, 209, 96, 106,
+ 152, 252, 35, 210, 208, 43, 47, 13, 21, 142, 122,
+ 24, 42, 149, 192, 95, 24, 240, 24, 148, 110},
+ 0,
+ {2},
+ {
+ {7, 151, 168, 57, 186, 104, 218, 21, 209, 96, 106,
+ 152, 252, 35, 210, 208, 43, 47, 13, 21, 142, 122,
+ 24, 42, 149, 192, 95, 24, 240, 24, 148, 110},
+ },
+ 1,
+ 32,
+ true},
+
+ {{53},
+ 255,
+ {14, 10, 1, 13, 8, 7, 3, 11, 9, 4, 6, 0, 5, 12, 15, 2},
+ {
+ {114},
+ {41},
+ {116},
+ {67},
+ {198},
+ {109},
+ {232},
+ {39},
+ {90},
+ {241},
+ {156},
+ {75},
+ {46},
+ {181},
+ {144},
+ {175},
+ },
+ 16,
+ 1,
+ true},
+
+ {{91, 188, 226, 91, 254, 197, 225},
+ 1,
+ {5, 1, 10},
+ {
+ {129, 18, 104, 86, 236, 73, 176},
+ {91, 188, 226, 91, 254, 197, 225},
+ {69, 53, 151, 204, 224, 37, 19},
+ },
+ 3,
+ 7,
+ true},
+
+ {{0},
+ 1,
+ {5, 1, 1},
+ {
+ {129, 18, 104, 86, 236, 73, 176},
+ {91, 188, 226, 91, 254, 197, 225},
+ {69, 53, 151, 204, 224, 37, 19},
+ },
+ 3,
+ 7,
+ false},
+
+ {{0},
+ 255,
+ {3, 12, 3},
+ {
+ {100, 176, 99, 142, 115, 192, 138},
+ {54, 139, 99, 172, 29, 137, 58},
+ {216, 119, 222, 40, 87, 25, 147},
+ },
+ 3,
+ 7,
+ false},
+
+ {{163, 120, 30, 243, 179, 172, 196, 137, 119, 17},
+ 3,
+ {1, 0, 12},
+ {{80, 180, 198, 131, 111, 251, 45, 181, 2, 242},
+ {121, 9, 79, 98, 132, 164, 9, 165, 19, 230},
+ {86, 52, 173, 138, 189, 223, 122, 102, 248, 157}},
+ 3,
+ 10,
+ true}};
+
+ for (size_t i = 0; i < (sizeof(vectors) / sizeof(*vectors)); ++i) {
+ uint8_t result[SHAMIR_MAX_LEN];
+ const uint8_t *share_values[SHAMIR_MAX_COUNT];
+ for (size_t j = 0; j < vectors[i].share_count; ++j) {
+ share_values[j] = vectors[i].share_values[j];
+ }
+ ck_assert_int_eq(shamir_interpolate(result, vectors[i].result_index,
+ vectors[i].share_indices, share_values,
+ vectors[i].share_count, vectors[i].len),
+ vectors[i].ret);
+ if (vectors[i].ret == true) {
+ ck_assert_mem_eq(result, vectors[i].result, vectors[i].len);
+ }
+ }
+}
+END_TEST
+
START_TEST(test_address) {
char address[36];
uint8_t pub_key[65];
@@ -8711,6 +8824,10 @@ Suite *test_suite(void) {
tcase_add_test(tc, test_mnemonic_to_entropy);
suite_add_tcase(s, tc);
+ tc = tcase_create("shamir");
+ tcase_add_test(tc, test_shamir);
+ suite_add_tcase(s, tc);
+
tc = tcase_create("pubkey_validity");
tcase_add_test(tc, test_pubkey_validity);
suite_add_tcase(s, tc);