Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
nginsburg committed Sep 24, 2019
1 parent d6d28f1 commit 2ac41fa
Show file tree
Hide file tree
Showing 9 changed files with 577 additions and 0 deletions.
15 changes: 15 additions & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Development Lead
````````````````

- Noah Ginsburg (https://github.com/ginsburgnm) <[email protected]>

Initially Dev Work
``````````````````

- Ryan Shea (https://github.com/rxl) <[email protected]>
- Muneeb Ali (https://github.com/muneeb-ali) <[email protected]>

Patches and Suggestions
````````````````

- Michael Flaxman (https://github.com/mflaxman)
106 changes: 106 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
Secret Sharing
=============

[![PyPI](https://img.shields.io/pypi/v/pyseltongue.svg)](https://pypi.python.org/pypi/pyseltongue/)
[![PyPI](https://img.shields.io/pypi/dm/pyseltongue.svg)](https://pypi.python.org/pypi/pyseltongue/)
[![PyPI](https://img.shields.io/pypi/l/pyseltongue.svg)](https://github.com/ginsburgnm/pyseltongue/blob/master/LICENSE)

A library for sharding and sharing secrets (like Bitcoin private keys), using shamir's secret sharing scheme.

## Installation

>>> pip install pyseltongue

## Sample Usage

### Hex Secrets

#### Splitting into shares

>>> from pyseltongue import SecretSharer
>>> shares = SecretSharer.split_secret("c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a", 2, 3)
['1-58cbd30524507e7a198bdfeb69c8d87fd7d2c10e8d5408851404f7d258cbcea7', '2-ecdbdaea89d75f8e73bde77a46db821cd40f430d39a11c864e5a4868dcb403ed', '3-80ebe2cfef5e40a2cdefef0923ee2bb9d04bc50be5ee308788af98ff609c380a']

#### Recovering from shares

>>> SecretSharer.recover_secret(shares[0:2])
'c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a'

### Plaintext Secrets

#### Splitting into shares

>>> from pyseltongue import PlaintextToHexSecretSharer
>>> shares = PlaintextToHexSecretSharer.split_secret("correct horse battery staple", 2, 3)
['1-7da6b11af146449675780434f6589230a3435d9ab59910354205996f508b8d0d', '2-fb4d6235e28c892cea70367c15ec3cbfed4cf4a417bd01e9812980f3ac97ddc8', '3-78f41350d3d2cdc35f6868c3357fe74f37568bad79e0f39dc04d687808a42d5a']

#### Recovering from shares

>>> PlaintextToHexSecretSharer.recover_secret(shares[0:2])
'correct horse battery staple'

### Bitcoin Private Keys

Note: Bitcoin private keys are in [Base58 check](https://en.bitcoin.it/wiki/Base58Check_encoding) format.

#### Splitting into reliably printable base58 shares

>>> from pyseltongue import BitcoinToB58SecretSharer
>>> shares = BitcoinToB58SecretSharer.split_secret("5KJvsngHeMpm884wtkJNzQGaCErckhHJBGFsvd3VyK5qMZXj3hS", 2, 3)
['2-Bqni1ysZcXhFBhVVJLQgPimDUJrjBrzuvBmc6gPNPh1jyDcvM6uYUuH', '3-9xpMBerBCdHLKzCQ82fjVLfZ3Qt48sqa6nz1E3cc6eu3qUe58vaogU3', '4-85qzMKpnnisRUGuJwivnaxZtcWuP5tgEHQCQMQqqocnMhjfDvkG4t2o']

#### Recovering from base58 shares

>>> BitcoinToB58SecretSharer.recover_secret(shares[0:2])
'5KJvsngHeMpm884wtkJNzQGaCErckhHJBGFsvd3VyK5qMZXj3hS'

#### Splitting into reliably transcribable [base32](http://en.wikipedia.org/wiki/Base32) shares

>>> from pyseltongue import BitcoinToB32SecretSharer
>>> shares = BitcoinToB32SecretSharer.split_secret("5KJvsngHeMpm884wtkJNzQGaCErckhHJBGFsvd3VyK5qMZXj3hS", 2, 3)
['B-RJ6Y56OSUWDY5VAAGC6XLSTM64CAJ2LPBNB7NKATJCWC7VSHIP5DQIVMR6OGJ4GB', 'C-CT5R24XAR5B732JWYQKSYOYBSF5VHI73HLY24QCFRJR5XUW64C4JWYN6SRGWVCUG', 'D-T54KX27OPEAGZ7TNK5WOFK4WFPZKEXUHNKPWLWDXZQNYPT3WPV3P5IGQTD7HAJDG']

#### Recovering from base32 shares

>>> BitcoinToB32SecretSharer.recover_secret(shares[0:2])
'5KJvsngHeMpm884wtkJNzQGaCErckhHJBGFsvd3VyK5qMZXj3hS'

#### Splitting into reliably transcribable [zbase32](http://philzimmermann.com/docs/human-oriented-base-32-encoding.txt) shares

>>> from pyseltongue import BitcoinToZB32SecretSharer
>>> shares = BitcoinToZB32SecretSharer.split_secret("5KJvsngHeMpm884wtkJNzQGaCErckhHJBGFsvd3VyK5qMZXj3hS", 2, 3)
['b-aweuzkm9jmfgd7x4k595bzcm3er3epf4dprfwzpprqa3exbuocs9byn4owfuqbo', 'n-btetgqqu8doacarsbyfdzpyycyj6gfdeaaxrpfx33pdjk4ou1d5owjdmdi1iegm9', 'd-njh33f14q7smucmh8iq8uaewc8mzub3mzptrwsegfiz3hc1fozkkjtguc4trh6sq']

#### Recovering from zbase32 shares

>>> BitcoinToZB32SecretSharer.recover_secret(shares[0:2])
'5KJvsngHeMpm884wtkJNzQGaCErckhHJBGFsvd3VyK5qMZXj3hS'

### Raw integers

#### Splitting into shares

>>> from pyseltongue import secret_int_to_points, points_to_secret_int
>>> secret = 88985120633792790105905686761572077713049967498756747774697023364147812997770L
>>> shares = secret_int_to_points(secret, 2, 3)
[(1, 108834987130598118322155382953070549297972563210322923466700361825476188819879L), (2, 12892764390087251114834094135881113029625174256248535119246116278891435001755L), (3, 32742630886892579331083790327379584614547769967814710811249454740219810823864L)]

#### Recovering from shares

>>> points_to_secret_int(shares[0:2])
88985120633792790105905686761572077713049967498756747774697023364147812997770L

### Custom formats

#### Splitting into shares

>>> from pyseltongue import SecretSharer, base64_chars
>>> sharer_class = SecretSharer
>>> sharer_class.share_charset = base64_chars
>>> shares = sharer_class.split_secret("c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a", 2, 3)
['B-JpxCTUQ9D+q93JglQM9yRinI2Cyxe92FTBSYa93ppfY', 'C-HAmR0pjHuHwL4rozXnFY05ysIJVqtf3pob1HCMaaZUm', 'D-EXbhV+1SYQ1Z6NxBfBM/YQ+PaP4j8B5N92X1pa9LJJ0']

#### Recovering from shares

>>> sharer_class.recover_secret(shares[0:2])
'c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a'
17 changes: 17 additions & 0 deletions pyseltongue/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
"""
Secret Sharing
~~~~~
:copyright: (c) 2014 by Halfmoon Labs
:license: MIT, see LICENSE for more details.
"""
name = "nginsecretsharing"

__version__ = '0.3.0'

from .sharing import secret_int_to_points, points_to_secret_int, \
point_to_share_string, share_string_to_point, SecretSharer, \
HexToHexSecretSharer, PlaintextToHexSecretSharer, \
BitcoinToB58SecretSharer, BitcoinToB32SecretSharer, \
BitcoinToZB32SecretSharer
79 changes: 79 additions & 0 deletions pyseltongue/polynomials.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
"""
Secret Sharing
~~~~~
:copyright: (c) 2014 by Halfmoon Labs
:license: MIT, see LICENSE for more details.
"""

from utilitybelt import secure_randint as randint


def egcd(a, b):
if a == 0:
return (b, 0, 1)
else:
g, y, x = egcd(b % a, a)
return (g, x - (b // a) * y, y)


def mod_inverse(k, prime):
k = k % prime
if k < 0:
ret = egcd(prime, -k)[2]
else:
ret = egcd(prime, k)[2]
return (prime + ret) % prime


def random_polynomial(degree, intercept, upper_bound):
""" Generates a random polynomial with positive coefficients.
"""
if degree < 0:
raise ValueError('Degree must be a non-negative number.')
coefficients = [intercept]
for i in range(degree):
random_coeff = randint(0, upper_bound-1)
coefficients.append(random_coeff)
return coefficients


def get_polynomial_points(coefficients, num_points, prime):
""" Calculates the first n polynomial points.
[ (1, f(1)), (2, f(2)), ... (n, f(n)) ]
"""
points = []
for x in range(1, num_points+1):
# start with x=1 and calculate the value of y
y = coefficients[0]
# calculate each term and add it to y, using modular math
for i in range(1, len(coefficients)):
exponentiation = (x**i) % prime
term = (coefficients[i] * exponentiation) % prime
y = (y + term) % prime
# add the point to the list of points
points.append((x, y))
return points


def modular_lagrange_interpolation(x, points, prime):
# break the points up into lists of x and y values
x_values, y_values = zip(*points)
# initialize f(x) and begin the calculation: f(x) = SUM( y_i * l_i(x) )
f_x = 0
for i in range(len(points)):
# evaluate the lagrange basis polynomial l_i(x)
numerator, denominator = 1, 1
for j in range(len(points)):
# don't compute a polynomial fraction if i equals j
if i == j:
continue
# compute a fraction & update the existing numerator + denominator
numerator = (numerator * (x - x_values[j])) % prime
denominator = (denominator * (x_values[i] - x_values[j])) % prime
# get the polynomial from the numerator + denominator mod inverse
lagrange_polynomial = numerator * mod_inverse(denominator, prime)
# multiply the current y & the evaluated polynomial & add it to f(x)
f_x = (prime + f_x + (y_values[i] * lagrange_polynomial)) % prime
return f_x
53 changes: 53 additions & 0 deletions pyseltongue/primes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
"""
Secret Sharing
~~~~~
:copyright: (c) 2014 by Halfmoon Labs
:license: MIT, see LICENSE for more details.
"""


def calculate_mersenne_primes():
""" Returns all the mersenne primes with less than 500 digits.
All primes:
3, 7, 31, 127, 8191, 131071, 524287, 2147483647L, 2305843009213693951L,
618970019642690137449562111L, 162259276829213363391578010288127L,
170141183460469231731687303715884105727L,
68647976601306097149...12574028291115057151L, (157 digits)
53113799281676709868...70835393219031728127L, (183 digits)
10407932194664399081...20710555703168729087L, (386 digits)
"""
mersenne_prime_exponents = [
2, 3, 5, 7, 13, 17, 19, 31, 61, 89, 107, 127, 521, 607, 1279
]
primes = []
for exp in mersenne_prime_exponents:
prime = 1
for i in range(exp):
prime *= 2
prime -= 1
primes.append(prime)
return primes

SMALLEST_257BIT_PRIME = (2**256 + 297)
SMALLEST_321BIT_PRIME = (2**320 + 27)
SMALLEST_385BIT_PRIME = (2**384 + 231)
STANDARD_PRIMES = calculate_mersenne_primes() + [
SMALLEST_257BIT_PRIME, SMALLEST_321BIT_PRIME, SMALLEST_385BIT_PRIME
]
STANDARD_PRIMES.sort()


def get_large_enough_prime(batch):
""" Returns a prime number that is greater all the numbers in the batch.
"""
# build a list of primes
primes = STANDARD_PRIMES
# find a prime that is greater than all the numbers in the batch
for prime in primes:
numbers_greater_than_prime = [i for i in batch if i > prime]
# if len(numbers_greater_than_prime) == 0:
if not numbers_greater_than_prime:
return prime
return None
Loading

0 comments on commit 2ac41fa

Please sign in to comment.