Files
@ d19e877af29d
Branch filter:
Location: Shamira/src/shamira/condensed.py
d19e877af29d
3.0 KiB
text/x-python
finite field discrete fourier transform
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | # GNU GPLv3, see LICENSE
# easier to print, single file, reconstruct-only version of the code
# encoding fixed to Base32
# Python 3.6
# full version available at https://bitbucket.org/Scharlach/shamira
"""Arithmetic operations on Galois Field 2**8. See https://en.wikipedia.org/wiki/Finite_field_arithmetic"""
def gfmul(a, b):
"""Basic multiplication. Russian peasant algorithm."""
res = 0
while a and b:
if b&1: res ^= a
if a&0x80: a = 0xff&(a<<1)^0x1b
else: a <<= 1
b >>= 1
return res
g = 3 # generator
E = [None]*256 # exponentials
L = [None]*256 # logarithms
acc = 1
for i in range(256):
E[i] = acc
L[acc] = i
acc = gfmul(acc, g)
L[1] = 0
INV = [E[255-L[i]] if i!=0 else None for i in range(256)] # multiplicative inverse
def get_constant_coef(*points):
"""Compute constant polynomial coefficient given the points.
See https://en.wikipedia.org/wiki/Shamir's_Secret_Sharing#Computationally_Efficient_Approach"""
k = len(points)
res = 0
for i in range(k):
(x, y) = points[i]
prod = 1
for j in range(k):
if i==j: continue
(xj, yj) = points[j]
prod = gfmul(prod, (gfmul(xj, INV[xj^x])))
res ^= gfmul(y, prod)
return res
###
import base64
import binascii
class SException(Exception): pass
def reconstruct_raw(*shares):
"""Tries to recover the secret from its shares.
:param shares: (((int) i, (bytes) share), ...)
:return: (bytes) reconstructed secret. Too few shares returns garbage."""
if len({x for (x, _) in shares}) < len(shares):
raise SException("Found a non-unique share. Please check your inputs.")
secret_len = len(shares[0][1])
res = [None]*secret_len
for i in range(secret_len):
points = [(x, s[i]) for (x, s) in shares]
res[i] = (get_constant_coef(*points))
return bytes(res)
def reconstruct(*shares):
"""Wraps reconstruct_raw.
:param shares: ((str) share, ...)
:return: (str) reconstructed secret. Too few shares returns garbage."""
bs = reconstruct_raw(*(decode(s) for s in shares))
try:
return bs.decode(encoding="utf-8")
except UnicodeDecodeError:
raise SException('Failed to decode bytes to utf-8. Either you supplied invalid shares, or you missed the "raw" flag. Offending value: "{0}"'.format(bs))
def decode(share):
try:
(*_, i, share_str) = share.split(".")
i = int(i)
if not 1<=i<=255:
raise SException("Malformed share: Failed 1<=k<=255, k={0}".format(i))
share_bytes = base64.b32decode(share_str)
return (i, share_bytes)
except (ValueError, binascii.Error):
raise SException('Malformed share: share="{0}"'.format(share))
###
from argparse import ArgumentParser
def run():
parser = ArgumentParser()
subparsers = parser.add_subparsers()
joiner = subparsers.add_parser("join")
joiner.add_argument("share", nargs="+", help="shares to be joined")
joiner.set_defaults(func=_reconstruct)
parser.set_defaults(func=lambda: parser.error("missing command"))
args = parser.parse_args()
args.func(args)
def _reconstruct(args):
try: print(reconstruct(*args.share))
except SException as e: print(e)
if __name__=="__main__":
run()
|