1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-24 15:28:10 +00:00

slip39: Add Shamir secret sharing using Daan Sprenkels C implementation for interpolation.

This commit is contained in:
Andrew Kozlik 2019-04-12 11:41:32 +02:00
parent 5321cb1890
commit e9a02ebc76
10 changed files with 2427 additions and 0 deletions

View File

@ -38,6 +38,7 @@ CPPDEFINES_MOD += [
SOURCE_MOD += [ SOURCE_MOD += [
'embed/extmod/modtrezorcrypto/modtrezorcrypto.c', 'embed/extmod/modtrezorcrypto/modtrezorcrypto.c',
'embed/extmod/modtrezorcrypto/crc.c', 'embed/extmod/modtrezorcrypto/crc.c',
'embed/extmod/modtrezorcrypto/shamir.c',
'embed/extmod/modtrezorcrypto/rand.c', 'embed/extmod/modtrezorcrypto/rand.c',
'vendor/trezor-crypto/address.c', 'vendor/trezor-crypto/address.c',
'vendor/trezor-crypto/aes/aescrypt.c', 'vendor/trezor-crypto/aes/aescrypt.c',

View File

@ -36,6 +36,7 @@ CPPDEFINES_MOD += [
SOURCE_MOD += [ SOURCE_MOD += [
'embed/extmod/modtrezorcrypto/modtrezorcrypto.c', 'embed/extmod/modtrezorcrypto/modtrezorcrypto.c',
'embed/extmod/modtrezorcrypto/crc.c', 'embed/extmod/modtrezorcrypto/crc.c',
'embed/extmod/modtrezorcrypto/shamir.c',
'vendor/trezor-crypto/address.c', 'vendor/trezor-crypto/address.c',
'vendor/trezor-crypto/aes/aescrypt.c', 'vendor/trezor-crypto/aes/aescrypt.c',
'vendor/trezor-crypto/aes/aeskey.c', 'vendor/trezor-crypto/aes/aeskey.c',

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "py/obj.h"
#include "py/objstr.h"
#include "embed/extmod/trezorobj.h"
#include "shamir.h"
#define MAX_SHARE_COUNT 32
/// def interpolate(shares, x) -> bytes:
/// '''
/// Interpolate
/// '''
mp_obj_t mod_trezorcrypto_shamir_interpolate(mp_obj_t shares, mp_obj_t x) {
size_t share_count;
mp_obj_t *share_items;
if (!MP_OBJ_IS_TYPE(shares, &mp_type_list)) {
mp_raise_TypeError("Expected a list.");
}
mp_obj_list_get(shares, &share_count, &share_items);
if (share_count < 1 || share_count > MAX_SHARE_COUNT) {
mp_raise_ValueError("Invalid number of shares.");
}
uint8_t x_uint8 = trezor_obj_get_uint8(x);
uint8_t share_indices[MAX_SHARE_COUNT];
const uint8_t *share_values[MAX_SHARE_COUNT];
size_t value_len = 0;
for (int i = 0; i < share_count; ++i) {
if (!MP_OBJ_IS_TYPE(share_items[i], &mp_type_tuple)) {
mp_raise_TypeError("Expected a tuple.");
}
size_t tuple_len;
mp_obj_t *share;
mp_obj_tuple_get(share_items[i], &tuple_len, &share);
if (tuple_len != 2) {
mp_raise_ValueError("Expected a tuple of length 2.");
}
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);
shamir_interpolate((uint8_t*) vstr.buf, x_uint8, share_indices, share_values, share_count, value_len);
vstr_cut_tail_bytes(&vstr, vstr_len(&vstr) - value_len);
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,
};

View File

@ -49,6 +49,7 @@
#include "modtrezorcrypto-sha3-256.h" #include "modtrezorcrypto-sha3-256.h"
#include "modtrezorcrypto-sha3-512.h" #include "modtrezorcrypto-sha3-512.h"
#include "modtrezorcrypto-sha512.h" #include "modtrezorcrypto-sha512.h"
#include "modtrezorcrypto-shamir.h"
STATIC const mp_rom_map_elem_t mp_module_trezorcrypto_globals_table[] = { STATIC const mp_rom_map_elem_t mp_module_trezorcrypto_globals_table[] = {
{MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_trezorcrypto)}, {MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_trezorcrypto)},
@ -86,6 +87,7 @@ STATIC const mp_rom_map_elem_t mp_module_trezorcrypto_globals_table[] = {
MP_ROM_PTR(&mod_trezorcrypto_Sha3_256_type)}, MP_ROM_PTR(&mod_trezorcrypto_Sha3_256_type)},
{MP_ROM_QSTR(MP_QSTR_sha3_512), {MP_ROM_QSTR(MP_QSTR_sha3_512),
MP_ROM_PTR(&mod_trezorcrypto_Sha3_512_type)}, 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, STATIC MP_DEFINE_CONST_DICT(mp_module_trezorcrypto_globals,
mp_module_trezorcrypto_globals_table); mp_module_trezorcrypto_globals_table);

View File

@ -0,0 +1,326 @@
/*
* Implementation of the hazardous parts of the SSS library
*
* Copyright (c) 2017 Daan Sprenkels <hello@dsprenkels.com>
*
* 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 "shamir.h"
#include <string.h>
#include <stdio.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] = ((int32_t) ((x & (1 << idx)) << (31 - idx))) >> 31;
}
}
/*
* 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];
}
/*
* 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
}
void 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 xs[share_count][8], ys[share_count][8];
uint32_t x[8];
uint32_t denom[8], tmp[8];
uint32_t num[8] = {~0}; /* num is the numerator (=1) */
uint32_t secret[8] = {0};
if (len > SHAMIR_MAX_LEN)
return;
/* 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++) {
memcpy(denom, x, sizeof(denom));
gf256_add(denom, xs[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);
}
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);
}
unbitslice(result, secret, len);
}

View File

@ -0,0 +1,62 @@
/*
* Low level API for Daan Sprenkels' Shamir secret sharing library
* Copyright (c) 2017 Daan Sprenkels <hello@dsprenkels.com>
*
* 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 <stddef.h>
#include <stdint.h>
#define SHAMIR_MAX_LEN 32
/*
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.
The x coordinate of the result.
Evaluations of the polynomials in x.
* Interpolate the `m` shares provided in `shares` and write the evaluation at
* point `x` to `result`. The number of shares used to compute the result may
* be larger than the threshold needed to .
*
* 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` and `result` as secret values. `m` is treated as
* a public value (for performance reasons).
*/
void 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__ */

623
src/trezor/crypto/slip39.py Normal file
View File

@ -0,0 +1,623 @@
# 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 pbkdf2
from trezor.crypto import hmac
from trezor.crypto import hashlib
import math
from trezor.crypto.slip39_wordlist import wordlist
from trezor.crypto import random
from trezorcrypto import shamir
class ConfigurationError(Exception):
pass
class InterpolationError(Exception):
pass
class MnemonicError(Exception):
pass
class ShamirMnemonic(object):
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 = (ID_LENGTH_BITS + ITERATION_EXP_LENGTH_BITS) // RADIX_BITS
"""The length of the random identifier and iteration exponent in words."""
MAX_SHARE_COUNT = 2 ** (RADIX_BITS // 2)
"""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 + math.ceil(
MIN_STRENGTH_BITS / 10
)
"""The minimum allowed length of the mnemonic in words."""
MIN_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 __init__(self):
# Load the word list.
if len(wordlist) != self.RADIX:
raise ConfigurationError(
"The wordlist should contain {} words, but it contains {} words.".format(
self.RADIX, len(wordlist)
)
)
self.word_index_map = {word: i for i, word in enumerate(wordlist)}
def _interpolate(self, shares, x):
"""
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.
"""
x_coordinates = set(share[0] for share in shares)
if len(x_coordinates) != len(shares):
raise InterpolationError(
"Invalid set of shares. Share indices must be unique."
)
share_value_lengths = set(len(share[1]) for share in shares)
if len(share_value_lengths) != 1:
raise InterpolationError(
"Invalid set of shares. All share values must have the same length."
)
if x in x_coordinates:
for share in shares:
if share[0] == x:
return share[1]
return shamir.interpolate(shares, x)
@classmethod
def _rs1024_polymod(cls, 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
@classmethod
def rs1024_create_checksum(cls, data):
values = (
tuple(cls.CUSTOMIZATION_STRING) + data + cls.CHECKSUM_LENGTH_WORDS * (0,)
)
polymod = cls._rs1024_polymod(values) ^ 1
return tuple(
(polymod >> 10 * i) & 1023
for i in reversed(range(cls.CHECKSUM_LENGTH_WORDS))
)
@classmethod
def rs1024_verify_checksum(cls, data):
return cls._rs1024_polymod(tuple(cls.CUSTOMIZATION_STRING) + data) == 1
@staticmethod
def xor(a, b):
return bytes(x ^ y for x, y in zip(a, b))
@classmethod
def _int_from_indices(cls, indices):
"""Converts a list of base 1024 indices in big endian order to an integer value."""
value = 0
for index in indices:
value = value * cls.RADIX + index
return value
@classmethod
def _int_to_indices(cls, value, length):
"""Converts an integer value to base 1024 indices in big endian order."""
return (
(value >> (i * cls.RADIX_BITS)) % cls.RADIX for i in reversed(range(length))
)
def mnemonic_from_indices(self, indices):
return " ".join(wordlist[i] for i in indices)
def mnemonic_to_indices(self, mnemonic):
try:
return (self.word_index_map[word.lower()] for word in mnemonic.split())
except KeyError as key_error:
raise MnemonicError("Invalid mnemonic word {}.".format(key_error)) from None
@classmethod
def _round_function(cls, i, passphrase, e, salt, r):
"""The round function used internally by the Feistel cipher."""
return pbkdf2(pbkdf2.HMAC_SHA256, bytes([i]) + passphrase, salt + r, (cls.MIN_ITERATION_COUNT << e) // cls.ROUND_COUNT).key()[:len(r)]
@classmethod
def _get_salt(cls, identifier):
return cls.CUSTOMIZATION_STRING + identifier.to_bytes(
math.ceil(cls.ID_LENGTH_BITS / 8), "big"
)
@classmethod
def _encrypt(cls, master_secret, passphrase, iteration_exponent, identifier):
l = master_secret[: len(master_secret) // 2]
r = master_secret[len(master_secret) // 2 :]
salt = cls._get_salt(identifier)
for i in range(cls.ROUND_COUNT):
(l, r) = (
r,
cls.xor(
l, cls._round_function(i, passphrase, iteration_exponent, salt, r)
),
)
return r + l
@classmethod
def _decrypt(
cls, encrypted_master_secret, passphrase, iteration_exponent, identifier
):
l = encrypted_master_secret[: len(encrypted_master_secret) // 2]
r = encrypted_master_secret[len(encrypted_master_secret) // 2 :]
salt = cls._get_salt(identifier)
for i in reversed(range(cls.ROUND_COUNT)):
(l, r) = (
r,
cls.xor(
l, cls._round_function(i, passphrase, iteration_exponent, salt, r)
),
)
return r + l
@classmethod
def _create_digest(cls, random_data, shared_secret):
return hmac.new(random_data, shared_secret, hashlib.sha256).digest()[
: cls.DIGEST_LENGTH_BYTES
]
def _split_secret(self, threshold, share_count, shared_secret):
assert 0 < threshold <= share_count <= self.MAX_SHARE_COUNT
# If the threshold is 1, then the digest of the shared secret is not used.
if threshold == 1:
return [(i, shared_secret) for i in range(share_count)]
random_share_count = threshold - 2
if share_count > self.MAX_SHARE_COUNT:
raise ValueError(
"The requested number of shares ({}) must not exceed {}.".format(
share_count, self.MAX_SHARE_COUNT
)
)
shares = [
(i, random.bytes(len(shared_secret)))
for i in range(random_share_count)
]
random_part = random.bytes(len(shared_secret) - self.DIGEST_LENGTH_BYTES)
digest = self._create_digest(random_part, shared_secret)
base_shares = shares + [
(self.DIGEST_INDEX, digest + random_part),
(self.SECRET_INDEX, shared_secret),
]
for i in range(random_share_count, share_count):
shares.append((i, self._interpolate(base_shares, i)))
return shares
def _recover_secret(self, threshold, shares):
shared_secret = self._interpolate(shares, self.SECRET_INDEX)
# If the threshold is 1, then the digest of the shared secret is not used.
if threshold != 1:
digest_share = self._interpolate(shares, self.DIGEST_INDEX)
digest = digest_share[: self.DIGEST_LENGTH_BYTES]
random_part = digest_share[self.DIGEST_LENGTH_BYTES :]
if digest != self._create_digest(random_part, shared_secret):
raise MnemonicError("Invalid digest of the shared secret.")
return shared_secret
@classmethod
def _group_prefix(
cls, identifier, iteration_exponent, group_index, group_threshold
):
id_exp_int = (identifier << cls.ITERATION_EXP_LENGTH_BITS) + iteration_exponent
return tuple(cls._int_to_indices(id_exp_int, cls.ID_EXP_LENGTH_WORDS)) + (
group_index * cls.MAX_SHARE_COUNT + (group_threshold - 1),
)
def encode_mnemonic(
self,
identifier,
iteration_exponent,
group_index,
group_threshold,
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 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 = math.ceil(len(value) * 8 / self.RADIX_BITS)
value_int = int.from_bytes(value, "big")
share_data = (
self._group_prefix(
identifier, iteration_exponent, group_index, group_threshold
)
+ (member_index * self.MAX_SHARE_COUNT + (member_threshold - 1),)
+ tuple(self._int_to_indices(value_int, value_word_count))
)
checksum = self.rs1024_create_checksum(share_data)
return self.mnemonic_from_indices(share_data + checksum)
def decode_mnemonic(self, mnemonic):
"""Converts a share mnemonic to share data."""
mnemonic_data = tuple(self.mnemonic_to_indices(mnemonic))
if len(mnemonic_data) < self.MIN_MNEMONIC_LENGTH_WORDS:
raise MnemonicError(
"Invalid mnemonic length. The length of each mnemonic must be at least {} words.".format(
self.MIN_MNEMONIC_LENGTH_WORDS
)
)
if (10 * (len(mnemonic_data) - self.METADATA_LENGTH_WORDS)) % 16 > 8:
raise MnemonicError("Invalid mnemonic length.")
if not self.rs1024_verify_checksum(mnemonic_data):
raise MnemonicError(
'Invalid mnemonic checksum for "{} ...".'.format(
" ".join(mnemonic.split()[: self.ID_EXP_LENGTH_WORDS + 2])
)
)
id_exp_int = self._int_from_indices(mnemonic_data[: self.ID_EXP_LENGTH_WORDS])
identifier = id_exp_int >> self.ITERATION_EXP_LENGTH_BITS
iteration_exponent = id_exp_int & ((1 << self.ITERATION_EXP_LENGTH_BITS) - 1)
group_index = mnemonic_data[self.ID_EXP_LENGTH_WORDS] // self.MAX_SHARE_COUNT
group_threshold = (
mnemonic_data[self.ID_EXP_LENGTH_WORDS] % self.MAX_SHARE_COUNT
) + 1
member_index = (
mnemonic_data[self.ID_EXP_LENGTH_WORDS + 1] // self.MAX_SHARE_COUNT
)
member_threshold = (
mnemonic_data[self.ID_EXP_LENGTH_WORDS + 1] % self.MAX_SHARE_COUNT
) + 1
value_data = mnemonic_data[
self.ID_EXP_LENGTH_WORDS + 2 : -self.CHECKSUM_LENGTH_WORDS
]
# The length of the master secret in bytes is required to be even, so find the largest even
# integer, which is less than or equal to value_word_count * 10 / 8.
value_byte_count = 2 * math.floor(len(value_data) * 5 / 8)
value_int = self._int_from_indices(value_data)
try:
value = value_int.to_bytes(value_byte_count, "big")
except OverflowError:
raise MnemonicError("Invalid mnemonic padding.") from None
return (
identifier,
iteration_exponent,
group_index,
group_threshold,
member_index,
member_threshold,
value,
)
def _decode_mnemonics(self, mnemonics):
identifiers = set()
iteration_exponents = set()
group_thresholds = set()
groups = {} # { group_index : [member_threshold, set_of_member_shares] }
for mnemonic in mnemonics:
identifier, iteration_exponent, group_index, group_threshold, member_index, member_threshold, share_value = self.decode_mnemonic(
mnemonic
)
identifiers.add(identifier)
iteration_exponents.add(iteration_exponent)
group_thresholds.add(group_threshold)
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(
self.ID_EXP_LENGTH_WORDS
)
)
if len(group_thresholds) != 1:
raise MnemonicError(
"Invalid set of mnemonics. All mnemonics must have the same group threshold."
)
return (
identifiers.pop(),
iteration_exponents.pop(),
group_thresholds.pop(),
groups,
)
def _generate_random_identifier(self):
"""Returns a randomly generated integer in the range 0, ... , 2**ID_LENGTH_BITS - 1."""
identifier = int.from_bytes(
random.bytes(math.ceil(self.ID_LENGTH_BITS / 8)), "big"
)
return identifier & ((1 << self.ID_LENGTH_BITS) - 1)
def generate_mnemonics(
self,
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 = self._generate_random_identifier()
if len(master_secret) * 8 < self.MIN_STRENGTH_BITS:
raise ValueError(
"The length of the master secret ({} bytes) must be at least {} bytes.".format(
len(master_secret), math.ceil(self.MIN_STRENGTH_BITS / 8)
)
)
if len(master_secret) % 2 != 0:
raise ValueError(
"The length of the master secret in bytes must be an even number."
)
if group_threshold > len(groups):
raise ValueError(
"The requested group threshold ({}) must not exceed the number of groups ({}).".format(
group_threshold, len(groups)
)
)
encrypted_master_secret = self._encrypt(
master_secret, passphrase, iteration_exponent, identifier
)
group_shares = self._split_secret(
group_threshold, len(groups), encrypted_master_secret
)
return [
[
self.encode_mnemonic(
identifier,
iteration_exponent,
group_index,
group_threshold,
member_index,
member_threshold,
value,
)
for member_index, value in self._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(
self,
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 < self.MIN_STRENGTH_BITS:
raise ValueError(
"The requested strength of the master secret ({} bits) must be at least {} bits.".format(
strength_bits, self.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 self.generate_mnemonics(
group_threshold,
groups,
random.bytes(strength_bits // 8),
passphrase,
iteration_exponent,
)
def combine_mnemonics(self, mnemonics, passphrase=b""):
"""
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.
:param passphrase: The passphrase used to encrypt the master secret.
:type passphrase: Array of bytes.
:return: The master secret.
:rtype: Array of bytes.
"""
if not mnemonics:
raise MnemonicError("The list of mnemonics is empty.")
identifier, iteration_exponent, group_threshold, groups = self._decode_mnemonics(
mnemonics
)
if len(groups) < group_threshold:
raise MnemonicError(
"Insufficient number of mnemonic groups ({}). The required number of groups is {}.".format(
len(groups), group_threshold
)
)
# Remove the groups, where the number of shares is below the member threshold.
bad_groups = {
group_index: group
for group_index, group in groups.items()
if len(group[1]) < group[0]
}
for group_index in bad_groups:
groups.pop(group_index)
if len(groups) < group_threshold:
group_index, group = next(iter(bad_groups.items()))
prefix = self._group_prefix(
identifier, iteration_exponent, group_index, group_threshold
)
raise MnemonicError(
'Insufficient number of mnemonics. At least {} mnemonics starting with "{} ..." are required.'.format(
group[0], self.mnemonic_from_indices(prefix)
)
)
group_shares = [
(group_index, self._recover_secret(group[0], list(group[1])))
for group_index, group in groups.items()
]
return self._decrypt(
self._recover_secret(group_threshold, group_shares),
passphrase,
iteration_exponent,
identifier,
)

File diff suppressed because it is too large Load Diff

274
tests/slip39_vectors.py Normal file
View File

@ -0,0 +1,274 @@
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 axis adequate wildlife fancy gross oasis cylinder mustang wrist rescue view short owner flip usual wolf desktop",
"shadow pistol academic acid actress prayer class unknown daughter sweater depict flip twice unkind craft early superior advocate guest smoking",
"shadow pistol academic always acid license obtain preach firefly permit flavor library learn tension flea unusual nylon funding civil armed"
],
"b43ceb7e57a0ea8766221624d01b0864"
],
[
[
"shadow pistol academic axis adequate wildlife fancy gross oasis cylinder mustang wrist rescue view short owner flip usual wolf desktop",
"shadow pistol academic always acid license obtain preach firefly permit flavor library learn tension flea unusual nylon funding civil armed"
],
"b43ceb7e57a0ea8766221624d01b0864"
],
[
[
"shadow pistol academic axis adequate wildlife fancy gross oasis cylinder mustang wrist rescue view short owner flip usual wolf desktop"
],
""
],
[
[
"numb stay academic acid aide deny bulge romp kernel rumor flavor estate traffic video email living grief move corner laundry",
"numb smoking academic always cleanup false acne luxury withdraw swing ceramic adjust forget editor remind bedroom practice forget dynamic yelp"
],
""
],
[
[
"enlarge pants academic acid alien become warmth exclude unfold yield square unusual increase chest wireless rainbow duration cause wrote taste",
"enlarge painting academic always anatomy infant carve platform nail already flavor mailman iris video beam rapids smoking frequent animal exclude"
],
""
],
[
[
"script smoking academic away diet theater twin guard already image increase trash lend pink general crowd chest mailman dough wavy",
"script smoking always acid blimp diminish pencil curious vampire devote valuable skin mama starting tension explain elegant formal maximum uncover",
"script smoking always always charity clock join peasant acquire tidy dress drove width depend smart trust guitar coding eyebrow realize"
],
""
],
[
[
"scroll leader academic academic become alcohol adapt tactics born orange eraser mobile fragment playoff easel nail edge climate exotic sack",
"scroll leader academic always alcohol rhyme excuse image wolf sidewalk index yoga upgrade flip inmate blue relate fitness tension desktop"
],
""
],
[
[
"client isolate academic acid destroy advance depart legs behavior drug spirit aquatic cultural explain arena debut metric round slavery engage",
"client isolate academic always campus verdict wine lunch rapids born warn spend patent surface elevator satoshi upgrade identify calcium salary"
],
""
],
[
[
"fancy merchant always academic blimp primary adorn hormone display pencil laundry olympic midst omit wits pink frequent single blind taxi"
],
""
],
[
[
"fancy merchant brave cause cinema category game include vintage withdraw hush loud aluminum spark garbage ranked priest reaction piece threaten",
"fancy merchant brave brave cricket starting employer corner slap alien username grief artist timber golden famous unusual march terminal skin"
],
""
],
[
[
"fancy merchant brave axis depict equip display friendly pumps vanish fraction traffic multiple unusual jump lunch ruin quantity lamp volume",
"fancy merchant always academic blimp primary adorn hormone display pencil laundry olympic midst omit wits pink frequent single blind taxi"
],
""
],
[
[
"fancy merchant brave axis depict equip display friendly pumps vanish fraction traffic multiple unusual jump lunch ruin quantity lamp volume",
"fancy merchant axis axle ajar depart gross hearing rich arena favorite calcium verdict mild teacher parking pile activity flavor agree",
"fancy merchant brave brave cricket starting employer corner slap alien username grief artist timber golden famous unusual march terminal skin",
"fancy merchant axis acne adult regular petition that advance carve dilemma pulse venture academic fantasy union escape expand meaning fused",
"fancy merchant axis ceiling acne manager ceiling credit easy ocean maiden manual unkind guilt length solution sugar deploy improve weapon"
],
"dfd7cbf25806fe5e6125d9602d50d571"
],
[
[
"fancy merchant always academic blimp primary adorn hormone display pencil laundry olympic midst omit wits pink frequent single blind taxi",
"fancy merchant acid academic aluminum march plains friendly belong civil formal check shrimp arena wits texture smell detailed exclude sympathy"
],
"dfd7cbf25806fe5e6125d9602d50d571"
],
[
[
"fancy merchant brave always breathe upgrade response salon income view slim task welcome valid freshman ending desert usher general emerald",
"fancy merchant axis axle ajar depart gross hearing rich arena favorite calcium verdict mild teacher parking pile activity flavor agree",
"fancy merchant brave cause cinema category game include vintage withdraw hush loud aluminum spark garbage ranked priest reaction piece threaten",
"fancy merchant always academic blimp primary adorn hormone display pencil laundry olympic midst omit wits pink frequent single blind taxi",
"fancy merchant axis ceiling acne manager ceiling credit easy ocean maiden manual unkind guilt length solution sugar deploy improve weapon",
"fancy merchant brave acid actress hormone royal receiver average adapt gray family genuine wildlife elegant oral bulge smell wrap laser",
"fancy merchant axis breathe clay repeat greatest making liquid flame verify trash champion realize floral golden fatal news plains advocate",
"fancy merchant acid academic aluminum march plains friendly belong civil formal check shrimp arena wits texture smell detailed exclude sympathy",
"fancy merchant brave axis depict equip display friendly pumps vanish fraction traffic multiple unusual jump lunch ruin quantity lamp volume",
"fancy merchant brave brave cricket starting employer corner slap alien username grief artist timber golden famous unusual march terminal skin",
"fancy merchant axis acne adult regular petition that advance carve dilemma pulse venture academic fantasy union escape expand meaning fused",
"fancy merchant axis amazing chew decision physics adjust dismiss email method emperor ceramic column trouble critical rival standard hush flea",
"fancy merchant brave costume document rhythm cinema album lyrics amazing suitable angry moisture story educate human likely lyrics dough talent"
],
"dfd7cbf25806fe5e6125d9602d50d571"
],
[
[
"wrap walnut academic academic acrobat duration expand loan premium lend inside prepare tricycle admit boundary visitor manual national skunk distance math admit curious involve width judicial excuse talent tension guest isolate switch research"
],
"9e6620d8c1ce2e665eb86e1d7c3397a292a5941682d7a4ae2d898d11a51947db"
],
[
[
"wrap walnut academic academic acrobat duration expand loan premium lend inside prepare tricycle admit boundary visitor manual national skunk distance math admit curious involve width judicial excuse talent tension guest isolate switch resident"
],
""
],
[
[
"wrap walnut academic academic beard duration expand loan premium lend inside prepare tricycle admit boundary visitor manual national skunk distance math admit curious involve width judicial excuse talent tension guest debut increase upstairs"
],
""
],
[
[
"kind husky academic axis aspect segment scholar true dress dream evening snake unfold traveler peaceful domestic body jump purchase frozen away alcohol ultimate task lamp huge pulse literary pharmacy gather stay alarm fangs",
"kind husky academic always activity body adapt damage evening seafood brother join ceramic writing earth benefit busy luck pumps garden benefit have axis busy golden pulse maiden aspect identify improve dilemma enlarge prospect",
"kind husky academic acid animal client racism ranked alarm umbrella tactics scroll veteran smart evidence always literary prize provide inside mason sister emission jewelry orbit require guitar invasion saver edge stay space avoid"
],
"af56c00ae12561c49e6afad0e741c8c1c65b2785a942808a8036335f42b2d342"
],
[
[
"kind husky academic axis aspect segment scholar true dress dream evening snake unfold traveler peaceful domestic body jump purchase frozen away alcohol ultimate task lamp huge pulse literary pharmacy gather stay alarm fangs",
"kind husky academic acid animal client racism ranked alarm umbrella tactics scroll veteran smart evidence always literary prize provide inside mason sister emission jewelry orbit require guitar invasion saver edge stay space avoid"
],
"af56c00ae12561c49e6afad0e741c8c1c65b2785a942808a8036335f42b2d342"
],
[
[
"kind husky academic acid animal client racism ranked alarm umbrella tactics scroll veteran smart evidence always literary prize provide inside mason sister emission jewelry orbit require guitar invasion saver edge stay space avoid"
],
""
],
[
[
"racism away academic acid dough home taxi merchant research library huge problem playoff vegan velvet union slim silent walnut adapt",
"racism branch academic always crush budget grin traffic decent regret bike divorce cinema humidity dining therapy pajamas teammate birthday market"
],
""
],
[
[
"bumpy royal academic acid downtown smear pregnant smith modern helpful explain welfare should ocean goat prayer rocky bike license either",
"bumpy romp academic always document task modify havoc sharp memory intend threaten much prayer adapt civil legal work morning trouble"
],
""
],
[
[
"sugar pink academic away dilemma bulb task bolt kitchen dive stadium step verdict stay easel valid station animal railroad raspy",
"sugar pink always acid document sprinkle emperor decision climate presence hanger member unknown crazy center bundle filter criminal sniff taste",
"sugar pink always always depend crystal require ounce physics away fake smirk costume apart teacher unwrap resident ruler herald gather"
],
""
],
[
[
"practice easy academic academic born verdict dominant cylinder earth bracelet dining resident public garden desktop course fact ecology screw fangs",
"practice easy academic always advance broken firm elder exact axle plunge dream glance depict username flexible airline hazard cards pulse"
],
""
],
[
[
"expand easy academic acid dryer coastal forbid sister blanket salary unusual fridge phrase amuse hesitate repair much taught zero deploy",
"expand easy academic always column lungs dilemma chest depict trial member intimate gums rich mayor grin husband acrobat mustang crystal"
],
""
],
[
[
"pencil recover always academic adequate aluminum acquire party various diet strategy ounce problem check category knit terminal hazard calcium response daisy edge extra exact snapshot warmth chemical permit faint revenue diagnose syndrome luxury"
],
""
],
[
[
"pencil recover brave acid acrobat impact weapon credit legend velvet black trouble slim merit cubic marathon group failure alien slap aquatic software lift clothes shrimp overall black zero goat cinema duration response emission",
"pencil recover brave brave acne hairy exhaust leaf aide easy piece decrease jury enjoy year black identify therapy national boundary typical advocate that pants velvet prospect legend identify evening dance merit nuclear vintage"
],
""
],
[
[
"pencil recover brave axis academic garden cinema drug lunar type early fused invasion invasion prospect regular license escape bedroom froth survive exclude satisfy tackle inherit national vegan voice dream credit desert peanut example",
"pencil recover always academic adequate aluminum acquire party various diet strategy ounce problem check category knit terminal hazard calcium response daisy edge extra exact snapshot warmth chemical permit faint revenue diagnose syndrome luxury"
],
""
],
[
[
"pencil recover brave costume advocate fused dragon smug rhyme glen subject likely parcel trash educate closet frost shadow prize phrase olympic piece civil scholar class hearing provide guest repeat viral submit satisfy boundary",
"pencil recover axis acne argue decent puny round easel parking deploy slush wildlife alarm impulse pacific fantasy single crisis valuable smear velvet toxic numerous subject grant crystal mental fitness says fraction moisture upgrade",
"pencil recover axis axle artwork transfer obesity surface aircraft evidence pile papa forward machine hanger fiction garbage coastal space aluminum acid senior receiver reaction benefit findings lily nuclear paper royal video lair hazard",
"pencil recover axis breathe agency camera modern pupal august suitable mental dough multiple crazy picture silent guest wealthy force rocky drove nylon moment luck width fused decision express smug nail shadow ugly luxury",
"pencil recover brave acid acrobat impact weapon credit legend velvet black trouble slim merit cubic marathon group failure alien slap aquatic software lift clothes shrimp overall black zero goat cinema duration response emission"
],
"bee676abf5d1764175b683684999f5cf198397a70d57a33da71a4d2e6bff61bf"
],
[
[
"pencil recover always academic adequate aluminum acquire party various diet strategy ounce problem check category knit terminal hazard calcium response daisy edge extra exact snapshot warmth chemical permit faint revenue diagnose syndrome luxury",
"pencil recover acid academic album devote market modern plastic huge thunder deploy timely roster earth tracks plastic emperor radar oral leaf lyrics switch prune lair luck dictate tidy index theory junk blind unfair"
],
"bee676abf5d1764175b683684999f5cf198397a70d57a33da71a4d2e6bff61bf"
],
[
[
"pencil recover always academic adequate aluminum acquire party various diet strategy ounce problem check category knit terminal hazard calcium response daisy edge extra exact snapshot warmth chemical permit faint revenue diagnose syndrome luxury",
"pencil recover axis ceiling agency orbit afraid spirit cowboy orbit drug analysis museum pencil adequate bedroom physics lilac coastal orbit glance withdraw careful voice flavor capacity damage dive founder duke regular trouble single",
"pencil recover axis axle artwork transfer obesity surface aircraft evidence pile papa forward machine hanger fiction garbage coastal space aluminum acid senior receiver reaction benefit findings lily nuclear paper royal video lair hazard",
"pencil recover acid academic album devote market modern plastic huge thunder deploy timely roster earth tracks plastic emperor radar oral leaf lyrics switch prune lair luck dictate tidy index theory junk blind unfair",
"pencil recover brave costume advocate fused dragon smug rhyme glen subject likely parcel trash educate closet frost shadow prize phrase olympic piece civil scholar class hearing provide guest repeat viral submit satisfy boundary",
"pencil recover axis breathe agency camera modern pupal august suitable mental dough multiple crazy picture silent guest wealthy force rocky drove nylon moment luck width fused decision express smug nail shadow ugly luxury",
"pencil recover axis amazing agree science recover plan desert decision burning fiction devote permit result acne grant being peasant ending vanish pants scared pitch depict home numerous fluff clothes mortgage grownup acrobat beyond",
"pencil recover brave axis academic garden cinema drug lunar type early fused invasion invasion prospect regular license escape bedroom froth survive exclude satisfy tackle inherit national vegan voice dream credit desert peanut example",
"pencil recover brave always actress junk morning nail admit furl manual lilac salary pancake geology category mountain zero leaves loyalty dream package peasant flexible exchange remember guard join biology company obesity losing work",
"pencil recover brave acid acrobat impact weapon credit legend velvet black trouble slim merit cubic marathon group failure alien slap aquatic software lift clothes shrimp overall black zero goat cinema duration response emission",
"pencil recover brave brave acne hairy exhaust leaf aide easy piece decrease jury enjoy year black identify therapy national boundary typical advocate that pants velvet prospect legend identify evening dance merit nuclear vintage",
"pencil recover axis acne argue decent puny round easel parking deploy slush wildlife alarm impulse pacific fantasy single crisis valuable smear velvet toxic numerous subject grant crystal mental fitness says fraction moisture upgrade",
"pencil recover brave cause adult firefly firefly holiday drink smell election umbrella priority stay aviation magazine program gross counter ultimate pregnant trend afraid rescue pecan evil satoshi unhappy slavery valid lamp webcam prisoner"
],
"bee676abf5d1764175b683684999f5cf198397a70d57a33da71a4d2e6bff61bf"
],
[
[
"lobe walnut academic academic acid rich both twin award sniff group category credit daisy mortgage island ocean likely ending"
],
""
],
[
[
"march taught academic academic award vexed worthy surprise texture facility idea capacity wisdom agree injury execute finance tadpole artist dramatic bracelet"
],
""
]
]

View File

@ -0,0 +1,22 @@
from common import *
from trezor.crypto import slip39
from slip39_vectors import vectors
from trezorcrypto import shamir
class TestCryptoSlip39(unittest.TestCase):
def test_shamir(self):
shamir_mnemonic = slip39.ShamirMnemonic()
for mnemonics, secret in vectors:
print(mnemonics, secret)
if secret:
self.assertEqual(shamir_mnemonic.combine_mnemonics(mnemonics, b"TREZOR"), unhexlify(secret))
else:
with self.assertRaises(slip39.MnemonicError):
shamir_mnemonic.combine_mnemonics(mnemonics, b"TREZOR")
if __name__ == '__main__':
unittest.main()