This commit is contained in:
Oxilic 2015-02-02 18:51:26 +00:00
commit cf06ed22de
3 changed files with 383 additions and 3 deletions

View File

@ -34,10 +34,13 @@ class Config(object):
# SiteCreate
action = subparsers.add_parser("siteCreate", help='Create a new site')
action.add_argument('-sss',help='Split private key with Shamir\'s Secret Sharing. Format: -sss parts_required:parts. Example: siteCreate -sss 3:5')
# SiteSign
action = subparsers.add_parser("siteSign", help='Update and sign content.json: address [privatekey]')
action.add_argument('address', help='Site to sign')
action.add_argument('-sss',help='Recover private key with Shamir\'s Secret Sharing. Format: siteSign address -sss part1:part2:part3', nargs="?")
action.add_argument('privatekey', help='Private key (default: ask on execute)', nargs='?')
# SitePublish

360
src/Crypt/SecretSharing.py Normal file
View File

@ -0,0 +1,360 @@
# -*- coding: utf-8 -*-
"""
Secret Sharing
~~~~~
:copyright: (c) 2014 by Halfmoon Labs
:license: MIT, see LICENSE for more details.
"""
import os
from math import ceil
import string
def dev_random_entropy(numbytes):
return open("/dev/random", "rb").read(numbytes)
def dev_urandom_entropy(numbytes):
return open("/dev/urandom", "rb").read(numbytes)
def get_entropy(numbytes):
if os.name == 'nt':
return os.urandom(numbytes)
else:
return dev_random_entropy(numbytes)
def randint(min_value, max_value):
""" Chooses a random integer between min_value and max_value, inclusive.
Range of values: [min_value, max_value]
"""
if not (isinstance(min_value, int) and isinstance(min_value, int)):
raise ValueError('min and max must be integers')
# Bounds are inclusive, so add 1 to the spread between the min and max
value_range = (max_value - min_value) + 1
# The bytes of entropy required depends on the bit length of the value range
numbytes_of_entropy = int(ceil(value_range.bit_length()/8.0)) + 1
# The entropy value range is the # of possible values of the entropy sample
entropy_value_range = 2**(numbytes_of_entropy*8)
# Any number greater than a multiple of the value range will be rejected
acceptable_sample_range = entropy_value_range - (entropy_value_range % value_range)
# Rejection sampling: Keep picking random #s until one falls in the range
while True:
byte_from_entropy = get_entropy(numbytes_of_entropy)
int_from_entropy = int(byte_from_entropy.encode('hex'), 16)
if int_from_entropy <= acceptable_sample_range:
break
# Take the sampled int and extract an int that's within the provided bounds
rand_int = min_value + (int_from_entropy % value_range)
return rand_int
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:
r = egcd(prime, -k)[2]
else:
r = egcd(prime, k)[2]
return (prime + r) % 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 = (long(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 = long(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 and 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 + mod inverse of the denominator
lagrange_polynomial = numerator * mod_inverse(denominator, prime)
# multiply the current y and the evaluated polynomial and add it to f(x)
f_x = (prime + f_x + (y_values[i] * lagrange_polynomial)) % prime
return f_x
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 = long(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:
return prime
return None
def int_to_charset(x, charset):
""" Turn a non-negative integer into a string.
"""
if not (isinstance(x, (int, long)) and x >= 0):
raise ValueError("x must be a non-negative integer.")
if x == 0:
return charset[0]
output = ""
while x > 0:
x, digit = divmod(x, len(charset))
output += charset[digit]
# reverse the characters in the output and return
return output[::-1]
def charset_to_int(s, charset):
""" Turn a string into a non-negative integer.
"""
if not isinstance(s, (str)):
raise ValueError("s must be a string.")
if (set(s) - set(charset)):
raise ValueError("s has chars that aren't in the charset.")
output = 0
for char in s:
output = output * len(charset) + charset.index(char)
return output
def change_charset(s, original_charset, target_charset):
""" Convert a string from one charset to another.
"""
intermediate_integer = charset_to_int(s, original_charset)
output_string = int_to_charset(intermediate_integer, target_charset)
return output_string
""" Base16 includes numeric digits and the letters a through f. Here,
we use the lowecase letters.
"""
base16_chars = string.hexdigits[0:16]
""" The Base58 character set allows for strings that avoid visual ambiguity
when typed. It consists of all the alphanumeric characters except for
"0", "O", "I", and "l", which look similar in some fonts.
https://en.bitcoin.it/wiki/Base58Check_encoding
"""
base58_chars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
""" The Base32 character set allows for accurate transcribing by hand.
It consists of uppercase letters + numerals, excluding "0", "1", + "8",
which could look similar to "O", "I", and "B" and so are omitted.
http://en.wikipedia.org/wiki/Base32
"""
base32_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
""" The z-base-32 character set is similar to the standard Base32 character
set, except it uses lowercase letters + numerals and chooses to exclude
"0", "l", "v", + "2". The set is also permuted so that easier chars
occur more frequently.
http://philzimmermann.com/docs/human-oriented-base-32-encoding.txt
"""
zbase32_chars = "ybndrfg8ejkmcpqxot1uwisza345h769"
""" The Base64 character set is a popular encoding for transmitting data
over media that are designed for textual data. It includes all alphanumeric
characters plus two bonus characters, usually "+" and "/".
http://en.wikipedia.org/wiki/Base64
"""
base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
def secret_int_to_points(secret_int, point_threshold, num_points):
""" Split a secret (integer) into shares (pair of integers / x,y coords).
Sample the points of a random polynomial with the y intercept equal to
the secret int.
"""
if point_threshold < 2:
raise ValueError("Threshold must be >= 2.")
if point_threshold > num_points:
raise ValueError("Threshold must be < the total number of points.")
prime = get_large_enough_prime([secret_int, num_points])
if not prime:
raise ValueError("Error! Secret is too long for share calculation!")
coefficients = random_polynomial(point_threshold-1, secret_int, prime)
points = get_polynomial_points(coefficients, num_points, prime)
return points
def points_to_secret_int(points):
""" Join int points into a secret int.
Get the intercept of a random polynomial defined by the given points.
"""
if not isinstance(points, list):
raise ValueError("Points must be in list form.")
for point in points:
if not isinstance(point, tuple) and len(point) == 2:
raise ValueError("Each point must be a tuple of two values.")
if not isinstance(point[0], (int, long)) and \
isinstance(point[1], (int, long)):
raise ValueError("Each value in the point must be an int.")
x_values, y_values = zip(*points)
prime = get_large_enough_prime(y_values)
free_coefficient = modular_lagrange_interpolation(0, points, prime)
secret_int = free_coefficient # the secret int is the free coefficient
return secret_int
def point_to_share_string(point, charset):
""" Convert a point (a tuple of two integers) into a share string - that is,
a representation of the point that uses the charset provided.
"""
# point should be in the format (1, 4938573982723...)
if '-' in charset:
raise ValueError('The character "-" cannot be in the supplied charset.')
if not isinstance(point, tuple) and len(point) == 2 and \
isinstance(point[0], (int, long)) and isinstance(point[1], (int, long)):
raise ValueError('Point format is invalid. Must be a pair of integers.')
x,y = point
x_string = int_to_charset(x, charset)
y_string = int_to_charset(y, charset)
share_string = x_string + '-' + y_string
return share_string
def share_string_to_point(share_string, charset):
""" Convert a share string to a point (a tuple of integers).
"""
# share should be in the format "01-d051080de7..."
if '-' in charset:
raise ValueError('The character "-" cannot be in the supplied charset.')
if not isinstance(share_string, str) and share_string.count('-') == 1:
raise ValueError('Share format is invalid.')
x_string, y_string = share_string.split('-')
if (set(x_string) - set(charset)) or (set(y_string) - set(charset)):
raise ValueError("Share has characters that aren't in the charset.")
x = charset_to_int(x_string, charset)
y = charset_to_int(y_string, charset)
return (x, y)
class SecretSharer():
""" Creates a secret sharer, which can convert from a secret string to a
list of shares and vice versa. The splitter is initialized with the
character set of the secrets and the character set of the shares that it
expects to be dealing with.
"""
secret_charset = string.hexdigits[0:16]
share_charset = string.hexdigits[0:16]
def __init__(self):
pass
@classmethod
def split_secret(cls, secret_string, share_threshold, num_shares):
secret_int = charset_to_int(secret_string, cls.secret_charset)
points = secret_int_to_points(secret_int, share_threshold, num_shares)
shares = []
for point in points:
shares.append(point_to_share_string(point, cls.share_charset))
return shares
@classmethod
def recover_secret(cls, shares):
points = []
for share in shares:
points.append(share_string_to_point(share, cls.share_charset))
secret_int = points_to_secret_int(points)
secret_string = int_to_charset(secret_int, cls.secret_charset)
return secret_string
class HexToHexSecretSharer(SecretSharer):
""" Standard sharer for converting hex secrets to hex shares.
"""
secret_charset = string.hexdigits[0:16]
share_charset = string.hexdigits[0:16]
class PlaintextToHexSecretSharer(SecretSharer):
""" Good for converting secret messages into standard hex shares.
"""
secret_charset = string.printable
share_charset = string.hexdigits[0:16]
class BitcoinToB58SecretSharer(SecretSharer):
""" Good for converting Bitcoin secret keys into shares that can be
reliably printed out in any font.
"""
secret_charset = base58_chars
share_charset = base58_chars
class BitcoinToB32SecretSharer(SecretSharer):
""" Good for converting Bitcoin secret keys into shares that can be
reliably and conveniently transcribed.
"""
secret_charset = base58_chars
share_charset = base32_chars
class BitcoinToZB32SecretSharer(SecretSharer):
""" Good for converting Bitcoin secret keys into shares that can be
reliably and conveniently transcribed.
"""
secret_charset = base58_chars
share_charset = zbase32_chars

View File

@ -69,12 +69,22 @@ def main():
# Site commands
def siteCreate():
def siteCreate(sss):
if sss:
from Crypt.SecretSharing import PlaintextToHexSecretSharer as PTHSS
if ":" not in sss or sss.count(":") > 1: raise Exception("Incorrect secret sharing format")
k=int(sss.split(":")[0])
n=int(sss.split(":")[1])
if k>n: raise Exception("Required parts can't be bigger than parts amount.")
logging.info("Generating new privatekey...")
from src.Crypt import CryptBitcoin
privatekey = CryptBitcoin.newPrivatekey()
logging.info("----------------------------------------------------------------------")
logging.info("Site private key: %s" % privatekey)
if sss:
sharedkey = PTHSS.split_secret(privatekey,k,n)
for key in sharedkey: logging.info(key)
else: logging.info("Site private key: %s" % privatekey)
logging.info(" !!! ^ Save it now, required to modify the site ^ !!!")
address = CryptBitcoin.privatekeyToAddress(privatekey)
logging.info("Site address: %s" % address)
@ -98,7 +108,14 @@ def siteCreate():
logging.info("Site created!")
def siteSign(address, privatekey=None):
def siteSign(address,sss, privatekey=None):
if sss and privatekey==None:
from Crypt.SecretSharing import PlaintextToHexSecretSharer as PTHSS
if ":" not in sss: raise Exception("Incorrect secret sharing format")
sss = sss.split(":")
privatekey = PTHSS.recover_secret(sss)
from Site import Site
logging.info("Signing site: %s..." % address)
site = Site(address, allow_create = False)