Merge 2f25b71783
into cfe2cfaf09
This commit is contained in:
commit
cf06ed22de
|
@ -34,10 +34,13 @@ class Config(object):
|
||||||
|
|
||||||
# SiteCreate
|
# SiteCreate
|
||||||
action = subparsers.add_parser("siteCreate", help='Create a new site')
|
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
|
# SiteSign
|
||||||
action = subparsers.add_parser("siteSign", help='Update and sign content.json: address [privatekey]')
|
action = subparsers.add_parser("siteSign", help='Update and sign content.json: address [privatekey]')
|
||||||
action.add_argument('address', help='Site to sign')
|
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='?')
|
action.add_argument('privatekey', help='Private key (default: ask on execute)', nargs='?')
|
||||||
|
|
||||||
# SitePublish
|
# SitePublish
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
23
src/main.py
23
src/main.py
|
@ -69,12 +69,22 @@ def main():
|
||||||
|
|
||||||
# Site commands
|
# 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...")
|
logging.info("Generating new privatekey...")
|
||||||
from src.Crypt import CryptBitcoin
|
from src.Crypt import CryptBitcoin
|
||||||
privatekey = CryptBitcoin.newPrivatekey()
|
privatekey = CryptBitcoin.newPrivatekey()
|
||||||
logging.info("----------------------------------------------------------------------")
|
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 ^ !!!")
|
logging.info(" !!! ^ Save it now, required to modify the site ^ !!!")
|
||||||
address = CryptBitcoin.privatekeyToAddress(privatekey)
|
address = CryptBitcoin.privatekeyToAddress(privatekey)
|
||||||
logging.info("Site address: %s" % address)
|
logging.info("Site address: %s" % address)
|
||||||
|
@ -98,7 +108,14 @@ def siteCreate():
|
||||||
logging.info("Site created!")
|
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
|
from Site import Site
|
||||||
logging.info("Signing site: %s..." % address)
|
logging.info("Signing site: %s..." % address)
|
||||||
site = Site(address, allow_create = False)
|
site = Site(address, allow_create = False)
|
||||||
|
|
Loading…
Reference in New Issue